diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2018-09-11 17:27:47 +0200 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2018-09-11 17:27:47 +0200 |
commit | f7d1902043c1bc70ba0bb159a3e8c71b78947ad7 (patch) | |
tree | 96adf0fef3b9f1af4b318c1b639797dcca185874 | |
parent | 0495e6cf3a1cf0f5f71622a8408d24fbc27642a0 (diff) | |
parent | af94946517d4e07e91b5c5ca21d58645f6da86c4 (diff) | |
download | Nim-f7d1902043c1bc70ba0bb159a3e8c71b78947ad7.tar.gz |
fixes merge conflicts
887 files changed, 48825 insertions, 43099 deletions
diff --git a/.gitignore b/.gitignore index fc090130b..eb29dfc04 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,10 @@ xcuserdata/ /compiler/nimrod.dot /reject.json /run.json -/testresults.html +# for `nim doc foo.nim` +/*.html +#/testresults.html #covered by /*.html + /testresults.json testament.db /csources @@ -58,6 +61,8 @@ dist/ .*/ ~* -# testament cruft +# testament cruft; TODO: generate these in a gitignore'd dir in the first place. testresults/ test.txt +/test.ini + diff --git a/.travis.yml b/.travis.yml index a3fa3f1da..0f74c56ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,11 +25,12 @@ before_install: before_script: - set -e + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then unset -f cd; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then shell_session_update() { :; }; fi - git clone --depth 1 https://github.com/nim-lang/csources.git - cd csources - sh build.sh - cd .. - - sed -i -e 's,cc = gcc,cc = clang,' config/nim.cfg - export PATH=$(pwd)/bin${PATH:+:$PATH} script: - nim c koch @@ -40,13 +41,13 @@ script: - nimble install zip -y - nimble install opengl - nimble install sdl1 - - nimble install jester@#head + - nimble install jester@#head -y - nimble install niminst - nim c --taintMode:on -d:nimCoroutines tests/testament/tester - tests/testament/tester --pedantic all -d:nimCoroutines + - nim c -o:bin/nimpretty nimpretty/nimpretty.nim + - nim c -r nimpretty/tester.nim - ./koch web - ./koch csource - ./koch nimsuggest -# - nim c -r nimsuggest/tester - - ( ! grep -F '.. code-block' -l -r --include '*.html' --exclude contributing.html --exclude docgen.html --exclude tut2.html ) - - ( ! grep -F '..code-block' -l -r --include '*.html' --exclude contributing.html --exclude docgen.html --exclude tut2.html ) + - nim c -r nimsuggest/tester diff --git a/appveyor.yml b/appveyor.yml index a79d32e41..cb4ac9e00 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -47,12 +47,15 @@ build_script: - koch boot -d:release - koch nimble - nim e tests/test_nimscript.nims + - nim c -o:bin/nimpretty.exe nimpretty/nimpretty.nim + - nim c -r nimpretty/tester.nim - nimble install zip -y - nimble install opengl - nimble install sdl1 - - nimble install jester@#head + - nimble install jester@#head -y - nimble install niminst - nim c --taintMode:on -d:nimCoroutines tests/testament/tester + - nim c --taintMode:on -d:nimCoroutines --os:genode -d:posix --compileOnly tests/testament/tester test_script: - tests\testament\tester --pedantic all -d:nimCoroutines diff --git a/bin/nim-gdb b/bin/nim-gdb new file mode 100644 index 000000000..e7b41094d --- /dev/null +++ b/bin/nim-gdb @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Exit if anything fails +set -e + +# Find out where the pretty printer Python module is +NIM_SYSROOT=$(dirname $(dirname $(readlink -e $(which nim)))) +GDB_PYTHON_MODULE_PATH="$NIM_SYSROOT/tools/nim-gdb.py" + +# Run GDB with the additional arguments that load the pretty printers +# Set the environment variable `NIM_GDB` to overwrite the call to a +# different/specific command (defaults to `gdb`). +NIM_GDB="${NIM_GDB:-gdb}" +# exec replaces the new process of bash with gdb. It is always good to +# have fewer processes. +exec ${NIM_GDB} \ + -eval-command "source $GDB_PYTHON_MODULE_PATH" \ + "$@" diff --git a/build_all.sh b/build_all.sh new file mode 100644 index 000000000..701d7d204 --- /dev/null +++ b/build_all.sh @@ -0,0 +1,31 @@ +#! /bin/sh + +# build development version of the compiler; can be rerun safely + +set -u # error on undefined variables +set -e # exit on first error + +echo_run(){ + echo "\n$@" + "$@" +} + +[ -d csources ] || echo_run git clone --depth 1 https://github.com/nim-lang/csources.git + +nim_csources=bin/nim_csources +build_nim_csources(){ + ## avoid changing dir in case of failure + ( + echo_run cd csources + echo_run sh build.sh + ) + # keep $nim_csources in case needed to investigate bootstrap issues + # without having to rebuild from csources + echo_run cp bin/nim $nim_csources +} + +[ -f $nim_csources ] || echo_run build_nim_csources + +echo_run bin/nim c koch +echo_run ./koch boot -d:release +echo_run ./koch tools # Compile Nimble and other tools. diff --git a/changelog.md b/changelog.md index e8a0f893a..0fe2f6099 100644 --- a/changelog.md +++ b/changelog.md @@ -1,268 +1,253 @@ -## v0.18.0 - dd/mm/yyyy +## v0.19.X - XX/XX/2018 ### Changes affecting backwards compatibility - -- Arrays of char cannot be converted to ``cstring`` anymore, pointers to - arrays of char can! This means ``$`` for arrays can finally exist - in ``system.nim`` and do the right thing. -- ``echo`` now works with strings that contain ``\0`` (the binary zero is not - shown) and ``nil`` strings are equal to empty strings. -- JSON: Deprecated `getBVal`, `getFNum`, and `getNum` in favour to - `getBool`, `getFloat`, `getBiggestInt`. Also `getInt` procedure was added. -- `reExtended` is no longer default for the `re` constructor in the `re` - module. -- `newAsyncSocket` taking an `AsyncFD` now runs `setBlocking(false)` on the - fd. -- The `ReadyKey` type in the selectors module now contains an ``errorCode`` - field to help distinguish between ``Event.Error`` events. -- Implemented an `accept` proc that works on a `SocketHandle` in - ``nativesockets``. -- Implemented ``getIoHandler`` proc in the ``asyncdispatch`` module that allows - you to retrieve the underlying IO Completion Port or ``Selector[AsyncData]`` - object in the specified dispatcher. -- The overloading rules changed slightly so that constrained generics are - preferred over unconstrained generics. (Bug #6526) -- It is now possible to forward declare object types so that mutually - recursive types can be created across module boundaries. See - [package level objects](https://nim-lang.org/docs/manual.html#package-level-objects) - for more information. -- The **unary** ``<`` is now deprecated, for ``.. <`` use ``..<`` for other usages - use the ``pred`` proc. -- We changed how array accesses "from backwards" like ``a[^1]`` or ``a[0..^1]`` are - implemented. These are now implemented purely in ``system.nim`` without compiler - support. There is a new "heterogenous" slice type ``system.HSlice`` that takes 2 - generic parameters which can be ``BackwardsIndex`` indices. ``BackwardsIndex`` is - produced by ``system.^``. - This means if you overload ``[]`` or ``[]=`` you need to ensure they also work - with ``system.BackwardsIndex`` (if applicable for the accessors). -- ``mod`` and bitwise ``and`` do not produce ``range`` subtypes anymore. This - turned out to be more harmful than helpful and the language is simpler - without this special typing rule. -- Added ``algorithm.rotateLeft``. -- ``rationals.toRational`` now uses an algorithm based on continued fractions. - This means its results are more precise and it can't run into an infinite loop - anymore. -- Added ``typetraits.$`` as an alias for ``typetraits.name``. -- ``os.getEnv`` now takes an optional ``default`` parameter that tells ``getEnv`` - what to return if the environment variable does not exist. -- Bodies of ``for`` loops now get their own scope: - -```nim - # now compiles: - for i in 0..4: - let i = i + 1 - echo i -``` - -- The parsing rules of ``if`` expressions were changed so that multiple - statements are allowed in the branches. We found few code examples that - now fail because of this change, but here is one: - -```nim - t[ti] = if exp_negative: '-' else: '+'; inc(ti) -``` - -This now needs to be written as: - -```nim - t[ti] = (if exp_negative: '-' else: '+'); inc(ti) -``` - -- To make Nim even more robust the system iterators ``..`` and ``countup`` - now only accept a single generic type ``T``. This means the following code - doesn't die with an "out of range" error anymore: - -```nim - var b = 5.Natural - var a = -5 - for i in a..b: - echo i -``` - -- ``formatFloat``/``formatBiggestFloat`` now support formatting floats with zero - precision digits. The previous ``precision = 0`` behavior (default formatting) - is now available via ``precision = -1``. -- The ``nim doc`` command is now an alias for ``nim doc2``, the second version of - the documentation generator. The old version 1 can still be accessed - via the new ``nim doc0`` command. -- Added ``system.getStackTraceEntries`` that allows you to access the stack - trace in a structured manner without string parsing. -- Added ``sequtils.mapLiterals`` for easier construction of array and tuple - literals. -- Added ``parseutils.parseSaturatedNatural``. -- ``atomic`` and ``generic`` are no longer keywords in Nim. ``generic`` used to be - an alias for ``concept``, ``atomic`` was not used for anything. -- Moved from stdlib into Nimble packages: - - [``basic2d``](https://github.com/nim-lang/basic2d) - _deprecated: use ``glm``, ``arraymancer``, ``neo``, or another package instead_ - - [``basic3d``](https://github.com/nim-lang/basic3d) - _deprecated: use ``glm``, ``arraymancer``, ``neo``, or another package instead_ - - [``gentabs``](https://github.com/lcrees/gentabs) - - [``libuv``](https://github.com/lcrees/libuv) - - [``numeric``](https://github.com/lcrees/polynumeric) - - [``poly``](https://github.com/lcrees/polynumeric) - - [``pdcurses``](https://github.com/lcrees/pdcurses) - - [``romans``](https://github.com/lcrees/romans) - - [``libsvm``](https://github.com/nim-lang/libsvm_legacy) - - [``joyent_http_parser``](https://github.com/nim-lang/joyent_http_parser) - -- Added ``system.runnableExamples`` to make examples in Nim's documentation easier - to write and test. The examples are tested as the last step of - ``nim doc``. -- Nim's ``rst2html`` command now supports the testing of code snippets via an RST - extension that we called ``:test:``:: - - ```rst - .. code-block:: nim - :test: - # shows how the 'if' statement works - if true: echo "yes" - ``` -- The ``[]`` proc for strings now raises an ``IndexError`` exception when - the specified slice is out of bounds. See issue - [#6223](https://github.com/nim-lang/Nim/issues/6223) for more details. - You can use ``substr(str, start, finish)`` to get the old behaviour back, - see [this commit](https://github.com/nim-lang/nimbot/commit/98cc031a27ea89947daa7f0bb536bcf86462941f) for an example. -- ``strutils.split`` and ``strutils.rsplit`` with an empty string and a - separator now returns that empty string. - See issue [#4377](https://github.com/nim-lang/Nim/issues/4377). -- The experimental overloading of the dot ``.`` operators now take - an ``untyped``` parameter as the field name, it used to be - a ``static[string]``. You can use ``when defined(nimNewDot)`` to make - your code work with both old and new Nim versions. - See [special-operators](https://nim-lang.org/docs/manual.html#special-operators) +- The stdlib module ``future`` has been renamed to ``sugar``. +- ``macros.callsite`` is now deprecated. Since the introduction of ``varargs`` + parameters this became unnecessary. +- Anonymous tuples with a single element can now be written as ``(1,)`` with a + trailing comma. The underlying AST is ``nnkTupleConstr(newLit 1)`` for this + example. ``nnkTupleConstr`` is a new node kind your macros need to be able + to deal with! +- Indexing into a ``cstring`` for the JS target is now mapped + to ``charCodeAt``. +- Assignments that would "slice" an object into its supertype are now prevented + at runtime. Use ``ref object`` with inheritance rather than ``object`` with + inheritance to prevent this issue. +- The ``not nil`` type annotation now has to be enabled explicitly + via ``{.experimental: "notnil"}`` as we are still not pleased with how this + feature works with Nim's containers. +- The parser now warns about inconsistent spacing around binary operators as + these can easily be confused with unary operators. This warning will likely + become an error in the future. +- The ``'c`` and ``'C'`` suffix for octal literals is now deprecated to + bring the language in line with the standard library (e.g. ``parseOct``). +- The dot style for import paths (e.g ``import path.to.module`` instead of + ``import path/to/module``) has been deprecated. + +#### Breaking changes in the standard library + +- ``re.split`` for empty regular expressions now yields every character in + the string which is what other programming languages chose to do. +- The returned tuple of ``system.instantiationInfo`` now has a third field + containing the column of the instantiation. + +- ``cookies.setCookie`` no longer assumes UTC for the expiration date. +- ``strutils.formatEng`` does not distinguish between ``nil`` and ``""`` + strings anymore for its ``unit`` parameter. Instead the space is controlled + by a new parameter ``useUnitSpace``. + +- The ``times.parse`` and ``times.format`` procs have been rewritten. + The proc signatures are the same so it should generally not break anything. + However, the new implementation is a bit stricter, which is a breaking change. + For example ``parse("2017-01-01 foo", "yyyy-MM-dd")`` will now raise an error. + +- ``proc `-`*(a, b: Time): int64`` in the ``times`` module has changed return type + to ``times.Duration`` in order to support higher time resolutions. + The proc is no longer deprecated. + +- The ``times.Timezone`` is now an immutable ref-type that must be initialized + with an explicit constructor (``newTimezone``). + +- ``posix.Timeval.tv_sec`` has changed type to ``posix.Time``. + +- ``math.`mod` `` for floats now behaves the same as ``mod`` for integers + (previously it used floor division like Python). Use ``math.floorMod`` for the old behavior. + +- For string inputs, ``unicode.isUpper`` and ``unicode.isLower`` now require a + second mandatory parameter ``skipNonAlpha``. + +- For string inputs, ``strutils.isUpperAscii`` and ``strutils.isLowerAscii`` now + require a second mandatory parameter ``skipNonAlpha``. + +- ``osLastError`` is now marked with ``sideEffect`` +- The procs ``parseHexInt`` and ``parseOctInt`` now fail on empty strings + and strings containing only valid prefixes, e.g. "0x" for hex integers. + +- ``terminal.setCursorPos`` and ``terminal.setCursorXPos`` now work correctly + with 0-based coordinates on POSIX (previously, you needed to use + 1-based coordinates on POSIX for correct behaviour; the Windows behaviour + was always correct). + +- ``lineInfoObj`` now returns absolute path instead of project path. + It's used by ``lineInfo``, ``check``, ``expect``, ``require``, etc. + +- ``net.sendTo`` no longer returns an int and now raises an ``OSError``. +- `threadpool`'s `await` and derivatives have been renamed to `blockUntil` + to avoid confusions with `await` from the `async` macro. + + +#### Breaking changes in the compiler + +- The undocumented ``#? braces`` parsing mode was removed. +- The undocumented PHP backend was removed. +- The default location of ``nimcache`` for the native code targets was + changed. Read [the compiler user guide](https://nim-lang.org/docs/nimc.html#generated-c-code-directory) for more information. -- Added ``macros.unpackVarargs``. -- The memory manager now uses a variant of the TLSF algorithm that has much - better memory fragmentation behaviour. According - to [http://www.gii.upv.es/tlsf/](http://www.gii.upv.es/tlsf/) the maximum - fragmentation measured is lower than 25%. As a nice bonus ``alloc`` and - ``dealloc`` became O(1) operations. -- The behavior of ``$`` has been changed for all standard library collections. The - collection-to-string implementations now perform proper quoting and escaping of - strings and chars. -- The ``random`` procs in ``random.nim`` have all been deprecated. Instead use - the new ``rand`` procs. The module now exports the state of the random - number generator as type ``Rand`` so multiple threads can easily use their - own random number generators that do not require locking. For more information - about this rename see issue [#6934](https://github.com/nim-lang/Nim/issues/6934) -- The compiler is now more consistent in its treatment of ambiguous symbols: - Types that shadow procs and vice versa are marked as ambiguous (bug #6693). -- ``yield`` (or ``await`` which is mapped to ``yield``) never worked reliably - in an array, seq or object constructor and is now prevented at compile-time. -- For string formatting / interpolation a new module - called [strformat](https://nim-lang.org/docs/strformat.html) has been added - to the stdlib. -- codegenDecl pragma now works for the JavaScript backend. It returns an empty string for - function return type placeholders. -- Asynchronous programming for the JavaScript backend using the `asyncjs` module. -- Extra semantic checks for procs with noreturn pragma: return type is not allowed, - statements after call to noreturn procs are no longer allowed. -- Noreturn proc calls and raising exceptions branches are now skipped during common type - deduction in if and case expressions. The following code snippets now compile: -```nim -import strutils -let str = "Y" -let a = case str: - of "Y": true - of "N": false - else: raise newException(ValueError, "Invalid boolean") -let b = case str: - of nil, "": raise newException(ValueError, "Invalid boolean") - elif str.startsWith("Y"): true - elif str.startsWith("N"): false - else: false -let c = if str == "Y": true - elif str == "N": false - else: - echo "invalid bool" - quit("this is the end") -``` -- Proc [toCountTable](https://nim-lang.org/docs/tables.html#toCountTable,openArray[A]) now produces a `CountTable` with values correspoding to the number of occurrences of the key in the input. It used to produce a table with all values set to `1`. - -Counting occurrences in a sequence used to be: - -```nim -let mySeq = @[1, 2, 1, 3, 1, 4] -var myCounter = initCountTable[int]() - -for item in mySeq: - myCounter.inc item -``` - -Now, you can simply do: - -```nim -let - mySeq = @[1, 2, 1, 3, 1, 4] - myCounter = mySeq.toCountTable() -``` - -- Added support for casting between integers of same bitsize in VM (compile time and nimscript). - This allow to among other things to reinterpret signed integers as unsigned. - -- Pragmas now support call syntax, for example: ``{.exportc"myname".}`` and ``{.exportc("myname").}`` -- Custom pragmas are now supported using pragma ``pragma``, please see language manual for details - -- Added True Color support for some terminals - Example: -```nim -import colors, terminal - -const Nim = "Efficient and expressive programming." - -var - fg = colYellow - bg = colBlue - int = 1.0 - -enableTrueColors() - -for i in 1..15: - styledEcho bgColor, bg, fgColor, fg, Nim, resetStyle - int -= 0.01 - fg = intensity(fg, int) - -setForegroundColor colRed -setBackgroundColor colGreen -styledEcho "Red on Green.", resetStyle -``` -- If you use ``--dynlibOverride:ssl`` with OpenSSL 1.0.x, you now have to - define ``openssl10`` symbol (``-d:openssl10``). By default OpenSSL 1.1.x is - assumed. - -- ``writeStackTrace`` is now proclaimed to have no IO effect (even though it does) - so that it is more useful for debugging purposes. -- ``\n`` is now only the single line feed character like in most - other programming languages. The new platform specific newline escape sequence is - written as ``\p``. This change only affects the Windows platform. -- ``newAsyncNativeSocket`` is now named ``createAsyncNativeSocket`` - and it no longer raises an OS error but returns an ``osInvalidSocket`` when - creation fails. -- ``newNativeSocket`` is now named ``createNativeSocket``. -- The ``deprecated`` pragma now supports a user-definable warning message for procs. - -```nim - -proc bar {.deprecated: "use foo instead".} = - return - -bar() -``` - -- The ``securehash`` module is now deprecated. Instead import ``std / sha1``. -- ``db_mysql`` module: ``DbConn`` is now a ``distinct`` type that doesn't expose the - details of the underlying ``PMySQL`` type. -- Standard library modules can now also be imported via the ``std`` pseudo-directory. - This is useful in order to distinguish between standard library and nimble package - imports: - -```nim - -import std / [strutils, os, osproc] -import someNimblePackage / [strutils, os] -``` - -- The ``readPasswordFromStdin`` proc has been moved from the ``rdstdin`` - to the ``terminal`` module, thus it does not depend on linenoise anymore. + +### Library additions + +- ``re.split`` now also supports the ``maxsplit`` parameter for consistency + with ``strutils.split``. +- Added ``system.toOpenArray`` in order to support zero-copy slicing + operations. This is currently not yet available for the JavaScript target. +- Added ``getCurrentDir``, ``findExe``, ``cpDir`` and ``mvDir`` procs to + ``nimscript``. +- The ``times`` module now supports up to nanosecond time resolution when available. +- Added the type ``times.Duration`` for representing fixed durations of time. +- Added the proc ``times.convert`` for converting between different time units, + e.g days to seconds. +- Added the proc ``algorithm.binarySearch[T, K]`` with the ```cmp``` parameter. +- Added the proc ``algorithm.upperBound``. +- Added inverse hyperbolic functions, ``math.arcsinh``, ``math.arccosh`` and ``math.arctanh`` procs. +- Added cotangent, secant and cosecant procs ``math.cot``, ``math.sec`` and ``math.csc``; and their hyperbolic, inverse and inverse hyperbolic functions, ``math.coth``, ``math.sech``, ``math.csch``, ``math.arccot``, ``math.arcsec``, ``math.arccsc``, ``math.arccoth``, ``math.arcsech`` and ``math.arccsch`` procs. +- Added the procs ``math.floorMod`` and ``math.floorDiv`` for floor based integer division. +- Added the procs ``rationals.`div```, ``rationals.`mod```, ``rationals.floorDiv`` and ``rationals.floorMod`` for rationals. +- Added the proc ``math.prod`` for product of elements in openArray. +- Added the proc ``parseBinInt`` to parse a binary integer from a string, which returns the value. +- ``parseOct`` and ``parseBin`` in parseutils now also support the ``maxLen`` argument similar to ``parseHexInt``. +- Added the proc ``flush`` for memory mapped files. +- Added the ``MemMapFileStream``. +- Added a simple interpreting event parser template ``eventParser`` to the ``pegs`` module. +- Added ``macros.copyLineInfo`` to copy lineInfo from other node. +- Added ``system.ashr`` an arithmetic right shift for integers. + +### Library changes + +- ``macros.astGenRepr``, ``macros.lispRepr`` and ``macros.treeRepr`` + now escapes the content of string literals consistently. +- ``macros.NimSym`` and ``macros.NimIdent`` is now deprecated in favor + of the more general ``NimNode``. +- ``macros.getImpl`` now includes the pragmas of types, instead of omitting them. +- ``macros.hasCustomPragma`` and ``macros.getCustomPragmaVal`` now + also support ``ref`` and ``ptr`` types, pragmas on types and variant + fields. +- ``system.SomeReal`` is now called ``SomeFloat`` for consistency and + correctness. +- ``algorithm.smartBinarySearch`` and ``algorithm.binarySearch`` is + now joined in ``binarySearch``. ``smartbinarySearch`` is now + deprecated. +- The `terminal` module now exports additional procs for generating ANSI color + codes as strings. +- Added the parameter ``val`` for the ``CritBitTree[int].inc`` proc. +- An exception raised from a ``test`` block of ``unittest`` now shows its type in + error message. +- The ``compiler/nimeval`` API was rewritten to simplify the "compiler as an + API". Using the Nim compiler and its VM as a scripting engine has never been + easier. See ``tests/compilerapi/tcompilerapi.nim`` for an example of how to + use the Nim VM in a native Nim application. +- Added the parameter ``val`` for the ``CritBitTree[T].incl`` proc. +- The proc ``tgamma`` was renamed to ``gamma``. ``tgamma`` is deprecated. +- The ``pegs`` module now exports getters for the fields of its ``Peg`` and ``NonTerminal`` + object types. ``Peg``s with child nodes now have the standard ``items`` and ``pairs`` + iterators. +- The ``accept`` socket procedure defined in the ``net`` module can now accept + a nil socket. + +### Language additions + +- Dot calls combined with explicit generic instantiations can now be written + as ``x.y[:z]`` which is transformed into ``y[z](x)`` by the parser. +- ``func`` is now an alias for ``proc {.noSideEffect.}``. +- In order to make ``for`` loops and iterators more flexible to use Nim now + supports so called "for-loop macros". See + the [manual](manual.html#macros-for-loop-macros) for more details. + This feature enables a Python-like generic ``enumerate`` implementation. + +- Case statements can now be rewritten via macros. See the [manual](manual.html#macros-case-statement-macros) for more information. + This feature enables custom pattern matchers. + + +- the `typedesc` special type has been renamed to just `type`. +- `static` and `type` are now also modifiers similar to `ref` and `ptr`. + They denote the special types `static[T]` and `type[T]`. +- Forcing compile-time evaluation with `static` now supports specifying + the desired target type (as a concrete type or as a type class) +- The `type` operator now supports checking that the supplied expression + matches an expected type constraint. + +### Language changes + +- The `importcpp` pragma now allows importing the listed fields of generic + C++ types. Support for numeric parameters have also been added through + the use of `static[T]` types. + (#6415) + +- Native C++ exceptions can now be imported with `importcpp` pragma. + Imported exceptions can be raised and caught just like Nim exceptions. + More details in language manual. + +- ``nil`` for strings/seqs is finally gone. Instead the default value for + these is ``"" / @[]``. Use ``--nilseqs:on`` for a transition period. + +- Accessing the binary zero terminator in Nim's native strings + is now invalid. Internally a Nim string still has the trailing zero for + zero-copy interoperability with ``cstring``. Compile your code with the + new switch ``--laxStrings:on`` if you need a transition period. + +- The command syntax now supports keyword arguments after the first comma. + +- Thread-local variables can now be declared inside procs. This implies all + the effects of the ``global`` pragma. + +- Nim now supports the ``except`` clause in the export statement. + +- Range float types, example ``range[0.0 .. Inf]``. More details in language manual. +- The ``{.this.}`` pragma has been deprecated. It never worked within generics and + we found the resulting code harder to read than the more explicit ``obj.field`` + syntax. +- "Memory regions" for pointer types have been deprecated, they were hardly used + anywhere. Note that this has **nothing** to do with the ``--gc:regions`` switch + of managing memory. + +- The exception hierarchy was slightly reworked, ``SystemError`` was renamed to + ``CatchableError`` and is the new base class for any exception that is guaranteed to + be catchable. This change should have minimal impact on most existing Nim code. + + +### Tool changes + +- ``jsondoc2`` has been renamed ``jsondoc``, similar to how ``doc2`` was renamed + ``doc``. The old ``jsondoc`` can still be invoked with ``jsondoc0``. + +### Compiler changes + +- The VM's instruction count limit was raised to 3 million instructions in + order to support more complex computations at compile-time. + +- Support for hot code reloading has been implemented for the JavaScript + target. To use it, compile your code with `--hotCodeReloading:on` and use a + helper library such as LiveReload or BrowserSync. + +- A new compiler option `--cppCompileToNamespace` puts the generated C++ code + into the namespace "Nim" in order to avoid naming conflicts with existing + C++ code. This is done for all Nim code - internal and exported. + +- Added ``macros.getProjectPath`` and ``ospaths.putEnv`` procs to Nim's virtual + machine. + +- The ``deadCodeElim`` option is now always turned on and the switch has no + effect anymore, but is recognized for backwards compatibility. + +- ``experimental`` is now a pragma / command line switch that can enable specific + language extensions, it is not an all-or-nothing switch anymore. + +- Nintendo Switch was added as a new platform target. See [the compiler user guide](https://nim-lang.org/docs/nimc.html) + for more info. + +- macros.bindSym now capable to accepts not only literal string or string constant expression. + bindSym enhancement make it also can accepts computed string or ident node inside macros / + compile time functions / static blocks. Only in templates / regular code it retains it's old behavior. + This new feature can be accessed via {.experimental: "dynamicBindSym".} pragma/switch. + +- On Posix systems the global system wide configuration is now put under ``/etc/nim/nim.cfg``, + it used to be ``/etc/nim.cfg``. Usually it does not exist, however. + +- On Posix systems the user configuration is now looked under ``$XDG_CONFIG_HOME/nim/nim.cfg`` + (if ``XDG_CONFIG_HOME`` is not defined, then under ``~/.config/nim/nim.cfg``). It used to be + ``$XDG_CONFIG_DIR/nim.cfg`` (and ``~/.config/nim.cfg``). + + Similarly, on Windows, the user configuration is now looked under ``%APPDATA%/nim/nim.cfg``. + This used to be ``%APPDATA%/nim.cfg``. + +### Bugfixes diff --git a/compiler/aliases.nim b/compiler/aliases.nim index 490ac987a..f79210dd7 100644 --- a/compiler/aliases.nim +++ b/compiler/aliases.nim @@ -34,10 +34,10 @@ proc isPartOfAux(n: PNode, b: PType, marker: var IntSet): TAnalysisResult = of nkOfBranch, nkElse: result = isPartOfAux(lastSon(n.sons[i]), b, marker) if result == arYes: return - else: internalError("isPartOfAux(record case branch)") + else: discard "isPartOfAux(record case branch)" of nkSym: result = isPartOfAux(n.sym.typ, b, marker) - else: internalError(n.info, "isPartOfAux()") + else: discard proc isPartOfAux(a, b: PType, marker: var IntSet): TAnalysisResult = result = arNo diff --git a/compiler/ast.nim b/compiler/ast.nim index 8286e3bb7..694944631 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -10,7 +10,7 @@ # abstract syntax tree + symbol table import - msgs, hashes, nversion, options, strutils, std / sha1, ropes, idents, + lineinfos, hashes, nversion, options, strutils, std / sha1, ropes, idents, intsets, idgen type @@ -221,7 +221,8 @@ type nkGotoState, # used for the state machine (for iterators) nkState, # give a label to a code section (for iterators) nkBreakState, # special break statement for easier code generation - nkFuncDef # a func + nkFuncDef, # a func + nkTupleConstr # a tuple constructor TNodeKinds* = set[TNodeKind] @@ -261,7 +262,8 @@ type # variable is a thread variable sfCompileTime, # proc can be evaluated at compile time sfConstructor, # proc is a C++ constructor - sfDeadCodeElim, # dead code elimination for the module is turned on + sfDispatcher, # copied method symbol is the dispatcher + # deprecated and unused, except for the con sfBorrow, # proc is borrowed sfInfixCall, # symbol needs infix call syntax in target language; # for interfacing with C++, JS @@ -274,10 +276,9 @@ type TSymFlags* = set[TSymFlag] const - sfDispatcher* = sfDeadCodeElim # copied method symbol is the dispatcher sfNoInit* = sfMainModule # don't generate code to init the variable - sfImmediate* = sfDeadCodeElim + sfImmediate* = sfDispatcher # macro or template is immediately expanded # without considering any possible overloads sfAllUntyped* = sfVolatile # macro or template is immediately expanded \ @@ -292,6 +293,10 @@ const # the compiler will avoid printing such names # in user messages. + sfHoisted* = sfForward + # an expression was hoised to an anonymous variable. + # the flag is applied to the var/let symbol + sfNoForward* = sfRegister # forward declarations are not required (per module) sfReorder* = sfForward @@ -317,7 +322,8 @@ const usesEffects* = 1 # read effects at position 1 writeEffects* = 2 # write effects at position 2 tagEffects* = 3 # user defined tag ('gc', 'time' etc.) - effectListLen* = 4 # list of effects list + pragmasEffects* = 4 # not an effect, but a slot for pragmas in proc type + effectListLen* = 5 # list of effects list type TTypeKind* = enum # order is important! @@ -453,6 +459,9 @@ type nfPreventCg # this node should be ignored by the codegen nfBlockArg # this a stmtlist appearing in a call (e.g. a do block) nfFromTemplate # a top-level node returned from a template + nfDefaultParam # an automatically inserter default parameter + nfDefaultRefsParam # a default param value references another parameter + # the flag is applied to proc default values and to calls TNodeFlags* = set[TNodeFlag] TTypeFlag* = enum # keep below 32 for efficiency reasons (now: beyond that) @@ -497,6 +506,7 @@ type tfGenericTypeParam tfImplicitTypeParam tfInferrableStatic + tfConceptMatchedTypeSym tfExplicit # for typedescs, marks types explicitly prefixed with the # `type` operator (e.g. type int) tfWildcard # consider a proc like foo[T, I](x: Type[T, I]) @@ -570,8 +580,8 @@ type TMagic* = enum # symbols that require compiler magic: mNone, mDefined, mDefinedInScope, mCompiles, mArrGet, mArrPut, mAsgn, - mLow, mHigh, mSizeOf, mTypeTrait, mIs, mOf, mAddr, mTypeOf, mRoof, mPlugin, - mEcho, mShallowCopy, mSlurp, mStaticExec, + mLow, mHigh, mSizeOf, mTypeTrait, mIs, mOf, mAddr, mType, mTypeOf, + mRoof, mPlugin, mEcho, mShallowCopy, mSlurp, mStaticExec, mStatic, mParseExprToAst, mParseStmtToAst, mExpandToAst, mQuoteAst, mUnaryLt, mInc, mDec, mOrd, mNew, mNewFinalize, mNewSeq, mNewSeqOfCap, @@ -582,7 +592,7 @@ type mAddI, mSubI, mMulI, mDivI, mModI, mSucc, mPred, mAddF64, mSubF64, mMulF64, mDivF64, - mShrI, mShlI, mBitandI, mBitorI, mBitxorI, + mShrI, mShlI, mAshrI, mBitandI, mBitorI, mBitxorI, mMinI, mMaxI, mMinF64, mMaxF64, mAddU, mSubU, mMulU, mDivU, mModU, @@ -618,6 +628,7 @@ type mIsPartOf, mAstToStr, mParallel, mSwap, mIsNil, mArrToSeq, mCopyStr, mCopyStrLast, mNewString, mNewStringOfCap, mParseBiggestFloat, + mMove, mWasMoved, mReset, mArray, mOpenArray, mRange, mSet, mSeq, mOpt, mVarargs, mRef, mPtr, mVar, mDistinct, mVoid, mTuple, @@ -632,16 +643,21 @@ type mCpuEndian, mHostOS, mHostCPU, mBuildOS, mBuildCPU, mAppType, mNaN, mInf, mNegInf, mCompileOption, mCompileOptionArg, - mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel, mNKind, + mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel, + mNKind, mNSymKind, + + mNccValue, mNccInc, mNcsAdd, mNcsIncl, mNcsLen, mNcsAt, + mNctPut, mNctLen, mNctGet, mNctHasNext, mNctNext, + mNIntVal, mNFloatVal, mNSymbol, mNIdent, mNGetType, mNStrVal, mNSetIntVal, mNSetFloatVal, mNSetSymbol, mNSetIdent, mNSetType, mNSetStrVal, mNLineInfo, - mNNewNimNode, mNCopyNimNode, mNCopyNimTree, mStrToIdent, mIdentToStr, + mNNewNimNode, mNCopyNimNode, mNCopyNimTree, mStrToIdent, mNBindSym, mLocals, mNCallSite, - mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl, + mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl, mNGenSym, mNHint, mNWarning, mNError, - mInstantiationInfo, mGetTypeInfo, mNGenSym, + mInstantiationInfo, mGetTypeInfo, mNimvm, mIntDefine, mStrDefine, mRunnableExamples, - mException, mBuiltinType + mException, mBuiltinType, mSymOwner # things that we can evaluate safely at compile time, even if not asked for it: const @@ -732,7 +748,7 @@ type locOther # location is something other TLocFlag* = enum lfIndirect, # backend introduced a pointer - lfFullExternalName, # only used when 'gCmd == cmdPretty': Indicates + lfFullExternalName, # only used when 'conf.cmd == cmdPretty': Indicates # that the symbol has been imported via 'importc: "fullname"' and # no format string. lfNoDeepCopy, # no need for a deep copy @@ -847,6 +863,8 @@ type constraint*: PNode # additional constraints like 'lit|result'; also # misused for the codegenDecl pragma in the hope # it won't cause problems + # for skModule the string literal to output for + # deprecated modules. when defined(nimsuggest): allUsages*: seq[TLineInfo] @@ -955,15 +973,15 @@ const tyFloat..tyFloat128, tyUInt..tyUInt64} ConstantDataTypes*: TTypeKinds = {tyArray, tySet, tyTuple, tySequence} - NilableTypes*: TTypeKinds = {tyPointer, tyCString, tyRef, tyPtr, tySequence, - tyProc, tyString, tyError} + NilableTypes*: TTypeKinds = {tyPointer, tyCString, tyRef, tyPtr, + tyProc, tyError} ExportableSymKinds* = {skVar, skConst, skProc, skFunc, skMethod, skType, skIterator, skMacro, skTemplate, skConverter, skEnumField, skLet, skStub, skAlias} PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16, nfDotSetter, nfDotField, nfIsRef, nfPreventCg, nfLL, - nfFromTemplate} + nfFromTemplate, nfDefaultRefsParam} namePos* = 0 patternPos* = 1 # empty except for term rewriting macros genericParamsPos* = 2 @@ -981,6 +999,7 @@ const nkPragmaCallKinds* = {nkExprColonExpr, nkCall, nkCallStrLit} nkLiterals* = {nkCharLit..nkTripleStrLit} + nkFloatLiterals* = {nkFloatLit..nkFloat128Lit} nkLambdaKinds* = {nkLambda, nkDo} declarativeDefs* = {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef, nkConverterDef} procDefs* = nkLambdaKinds + declarativeDefs @@ -993,8 +1012,8 @@ const skMethod, skConverter} var ggDebug* {.deprecated.}: bool ## convenience switch for trying out things -var - gMainPackageId*: int +#var +# gMainPackageId*: int proc isCallExpr*(n: PNode): bool = result = n.kind in nkCallKinds @@ -1002,13 +1021,16 @@ proc isCallExpr*(n: PNode): bool = proc discardSons*(father: PNode) proc len*(n: PNode): int {.inline.} = - if isNil(n.sons): result = 0 - else: result = len(n.sons) + when defined(nimNoNilSeqs): + result = len(n.sons) + else: + if isNil(n.sons): result = 0 + else: result = len(n.sons) proc safeLen*(n: PNode): int {.inline.} = ## works even for leaves. - if n.kind in {nkNone..nkNilLit} or isNil(n.sons): result = 0 - else: result = len(n.sons) + if n.kind in {nkNone..nkNilLit}: result = 0 + else: result = len(n) proc safeArrLen*(n: PNode): int {.inline.} = ## works for array-like objects (strings passed as openArray in VM). @@ -1018,7 +1040,8 @@ proc safeArrLen*(n: PNode): int {.inline.} = proc add*(father, son: PNode) = assert son != nil - if isNil(father.sons): father.sons = @[] + when not defined(nimNoNilSeqs): + if isNil(father.sons): father.sons = @[] add(father.sons, son) type Indexable = PNode | PType @@ -1037,9 +1060,9 @@ proc newNode*(kind: TNodeKind): PNode = new(result) result.kind = kind #result.info = UnknownLineInfo() inlined: - result.info.fileIndex = int32(-1) + result.info.fileIndex = InvalidFileIdx result.info.col = int16(-1) - result.info.line = int16(-1) + result.info.line = uint16(0) when defined(useNodeIds): result.id = gNodeId if result.id == nodeIdToDebug: @@ -1053,45 +1076,26 @@ proc newTree*(kind: TNodeKind; children: varargs[PNode]): PNode = result.info = children[0].info result.sons = @children -proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode = - result = newNode(kind) - result.intVal = intVal - -proc newIntTypeNode*(kind: TNodeKind, intVal: BiggestInt, typ: PType): PNode = - result = newIntNode(kind, intVal) - result.typ = typ - -proc newFloatNode*(kind: TNodeKind, floatVal: BiggestFloat): PNode = - result = newNode(kind) - result.floatVal = floatVal - -proc newStrNode*(kind: TNodeKind, strVal: string): PNode = - result = newNode(kind) - result.strVal = strVal - template previouslyInferred*(t: PType): PType = if t.sons.len > 1: t.lastSon else: nil proc newSym*(symKind: TSymKind, name: PIdent, owner: PSym, - info: TLineInfo): PSym = + info: TLineInfo; options: TOptions = {}): PSym = # generates a symbol and initializes the hash field too new(result) result.name = name result.kind = symKind result.flags = {} result.info = info - result.options = gOptions + result.options = options result.owner = owner result.offset = -1 result.id = getID() when debugIds: registerId(result) - #if result.id == 93289: + #if result.id == 77131: # writeStacktrace() - # MessageOut(name.s & " has id: " & toString(result.id)) - -var emptyNode* = newNode(nkEmpty) -# There is a single empty node that is shared! Do not overwrite it! + # echo name.s proc isMetaType*(t: PType): bool = return t.kind in tyMetaTypes or @@ -1111,13 +1115,13 @@ proc linkTo*(s: PSym, t: PType): PSym {.discardable.} = s.typ = t result = s -template fileIdx*(c: PSym): int32 = +template fileIdx*(c: PSym): FileIndex = # XXX: this should be used only on module symbols - c.position.int32 + c.position.FileIndex template filename*(c: PSym): string = # XXX: this should be used only on module symbols - c.position.int32.toFilename + c.position.FileIndex.toFilename proc appendToModule*(m: PSym, n: PNode) = ## The compiler will use this internally to add nodes that will be @@ -1135,24 +1139,24 @@ const # for all kind of hash tables: proc copyStrTable*(dest: var TStrTable, src: TStrTable) = dest.counter = src.counter - if isNil(src.data): return setLen(dest.data, len(src.data)) for i in countup(0, high(src.data)): dest.data[i] = src.data[i] proc copyIdTable*(dest: var TIdTable, src: TIdTable) = dest.counter = src.counter - if isNil(src.data): return newSeq(dest.data, len(src.data)) for i in countup(0, high(src.data)): dest.data[i] = src.data[i] proc copyObjectSet*(dest: var TObjectSet, src: TObjectSet) = dest.counter = src.counter - if isNil(src.data): return setLen(dest.data, len(src.data)) for i in countup(0, high(src.data)): dest.data[i] = src.data[i] proc discardSons*(father: PNode) = - father.sons = nil + when defined(nimNoNilSeqs): + father.sons = @[] + else: + father.sons = nil proc withInfo*(n: PNode, info: TLineInfo): PNode = n.info = info @@ -1219,18 +1223,36 @@ proc newNodeIT*(kind: TNodeKind, info: TLineInfo, typ: PType): PNode = result.info = info result.typ = typ +proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode = + result = newNode(kind) + result.intVal = intVal + +proc newIntTypeNode*(kind: TNodeKind, intVal: BiggestInt, typ: PType): PNode = + result = newIntNode(kind, intVal) + result.typ = typ + +proc newFloatNode*(kind: TNodeKind, floatVal: BiggestFloat): PNode = + result = newNode(kind) + result.floatVal = floatVal + +proc newStrNode*(kind: TNodeKind, strVal: string): PNode = + result = newNode(kind) + result.strVal = strVal + +proc newStrNode*(strVal: string; info: TLineInfo): PNode = + result = newNodeI(nkStrLit, info) + result.strVal = strVal + proc addSon*(father, son: PNode) = assert son != nil - if isNil(father.sons): father.sons = @[] + when not defined(nimNoNilSeqs): + if isNil(father.sons): father.sons = @[] add(father.sons, son) -var emptyParams = newNode(nkFormalParams) -emptyParams.addSon(emptyNode) - proc newProcNode*(kind: TNodeKind, info: TLineInfo, body: PNode, - params = emptyParams, + params, name, pattern, genericParams, - pragmas, exceptions = ast.emptyNode): PNode = + pragmas, exceptions: PNode): PNode = result = newNodeI(kind, info) result.sons = @[name, pattern, genericParams, params, pragmas, exceptions, body] @@ -1249,14 +1271,14 @@ proc newType*(kind: TTypeKind, owner: PSym): PType = new(result) result.kind = kind result.owner = owner - result.size = - 1 + result.size = -1 result.align = 2 # default alignment result.id = getID() result.lockLevel = UnspecifiedLockLevel when debugIds: registerId(result) when false: - if result.id == 205734: + if result.id == 76426: echo "KNID ", kind writeStackTrace() @@ -1268,16 +1290,22 @@ proc mergeLoc(a: var TLoc, b: TLoc) = if a.r == nil: a.r = b.r proc newSons*(father: PNode, length: int) = - if isNil(father.sons): - newSeq(father.sons, length) - else: + when defined(nimNoNilSeqs): setLen(father.sons, length) + else: + if isNil(father.sons): + newSeq(father.sons, length) + else: + setLen(father.sons, length) proc newSons*(father: PType, length: int) = - if isNil(father.sons): - newSeq(father.sons, length) - else: + when defined(nimNoNilSeqs): setLen(father.sons, length) + else: + if isNil(father.sons): + newSeq(father.sons, length) + else: + setLen(father.sons, length) proc sonsLen*(n: PType): int = n.sons.len proc len*(n: PType): int = n.sons.len @@ -1320,7 +1348,7 @@ proc copyType*(t: PType, owner: PSym, keepId: bool): PType = proc exactReplica*(t: PType): PType = copyType(t, t.owner, true) proc copySym*(s: PSym, keepId: bool = false): PSym = - result = newSym(s.kind, s.name, s.owner, s.info) + result = newSym(s.kind, s.name, s.owner, s.info, s.options) #result.ast = nil # BUGFIX; was: s.ast which made problems result.typ = s.typ if keepId: @@ -1339,8 +1367,9 @@ proc copySym*(s: PSym, keepId: bool = false): PSym = if result.kind in {skVar, skLet, skField}: result.guard = s.guard -proc createModuleAlias*(s: PSym, newIdent: PIdent, info: TLineInfo): PSym = - result = newSym(s.kind, newIdent, s.owner, info) +proc createModuleAlias*(s: PSym, newIdent: PIdent, info: TLineInfo; + options: TOptions): PSym = + result = newSym(s.kind, newIdent, s.owner, info, options) # keep ID! result.ast = s.ast result.id = s.id @@ -1351,7 +1380,7 @@ proc createModuleAlias*(s: PSym, newIdent: PIdent, info: TLineInfo): PSym = result.loc = s.loc result.annex = s.annex # XXX once usedGenerics is used, ensure module aliases keep working! - assert s.usedGenerics == nil + assert s.usedGenerics.len == 0 proc initStrTable*(x: var TStrTable) = x.counter = 0 @@ -1444,20 +1473,26 @@ proc propagateToOwner*(owner, elem: PType) = owner.flags.incl tfHasGCedMem proc rawAddSon*(father, son: PType) = - if isNil(father.sons): father.sons = @[] + when not defined(nimNoNilSeqs): + if isNil(father.sons): father.sons = @[] add(father.sons, son) if not son.isNil: propagateToOwner(father, son) proc rawAddSonNoPropagationOfTypeFlags*(father, son: PType) = - if isNil(father.sons): father.sons = @[] + when not defined(nimNoNilSeqs): + if isNil(father.sons): father.sons = @[] add(father.sons, son) proc addSonNilAllowed*(father, son: PNode) = - if isNil(father.sons): father.sons = @[] + when not defined(nimNoNilSeqs): + if isNil(father.sons): father.sons = @[] add(father.sons, son) proc delSon*(father: PNode, idx: int) = - if isNil(father.sons): return + when defined(nimNoNilSeqs): + if father.len == 0: return + else: + if isNil(father.sons): return var length = sonsLen(father) for i in countup(idx, length - 2): father.sons[i] = father.sons[i + 1] setLen(father.sons, length - 1) @@ -1476,7 +1511,7 @@ proc copyNode*(src: PNode): PNode = echo "COMES FROM ", src.id case src.kind of nkCharLit..nkUInt64Lit: result.intVal = src.intVal - of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal + of nkFloatLiterals: result.floatVal = src.floatVal of nkSym: result.sym = src.sym of nkIdent: result.ident = src.ident of nkStrLit..nkTripleStrLit: result.strVal = src.strVal @@ -1495,7 +1530,7 @@ proc shallowCopy*(src: PNode): PNode = echo "COMES FROM ", src.id case src.kind of nkCharLit..nkUInt64Lit: result.intVal = src.intVal - of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal + of nkFloatLiterals: result.floatVal = src.floatVal of nkSym: result.sym = src.sym of nkIdent: result.ident = src.ident of nkStrLit..nkTripleStrLit: result.strVal = src.strVal @@ -1515,7 +1550,7 @@ proc copyTree*(src: PNode): PNode = echo "COMES FROM ", src.id case src.kind of nkCharLit..nkUInt64Lit: result.intVal = src.intVal - of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal + of nkFloatLiterals: result.floatVal = src.floatVal of nkSym: result.sym = src.sym of nkIdent: result.ident = src.ident of nkStrLit..nkTripleStrLit: result.strVal = src.strVal @@ -1559,33 +1594,44 @@ proc getInt*(a: PNode): BiggestInt = case a.kind of nkCharLit..nkUInt64Lit: result = a.intVal else: - internalError(a.info, "getInt") - result = 0 + raiseRecoverableError("cannot extract number from invalid AST node") + #internalError(a.info, "getInt") + #doAssert false, "getInt" + #result = 0 proc getFloat*(a: PNode): BiggestFloat = case a.kind - of nkFloatLit..nkFloat128Lit: result = a.floatVal + of nkFloatLiterals: result = a.floatVal else: - internalError(a.info, "getFloat") - result = 0.0 + raiseRecoverableError("cannot extract number from invalid AST node") + #doAssert false, "getFloat" + #internalError(a.info, "getFloat") + #result = 0.0 proc getStr*(a: PNode): string = case a.kind of nkStrLit..nkTripleStrLit: result = a.strVal of nkNilLit: # let's hope this fixes more problems than it creates: - result = nil + when defined(nimNoNilSeqs): + result = "" + else: + result = nil else: - internalError(a.info, "getStr") - result = "" + raiseRecoverableError("cannot extract string from invalid AST node") + #doAssert false, "getStr" + #internalError(a.info, "getStr") + #result = "" proc getStrOrChar*(a: PNode): string = case a.kind of nkStrLit..nkTripleStrLit: result = a.strVal of nkCharLit..nkUInt64Lit: result = $chr(int(a.intVal)) else: - internalError(a.info, "getStrOrChar") - result = "" + raiseRecoverableError("cannot extract string from invalid AST node") + #doAssert false, "getStrOrChar" + #internalError(a.info, "getStrOrChar") + #result = "" proc isGenericRoutine*(s: PSym): bool = case s.kind @@ -1610,6 +1656,19 @@ proc originatingModule*(s: PSym): PSym = proc isRoutine*(s: PSym): bool {.inline.} = result = s.kind in skProcKinds +proc isCompileTimeProc*(s: PSym): bool {.inline.} = + result = s.kind == skMacro or + s.kind == skProc and sfCompileTime in s.flags + +proc requiredParams*(s: PSym): int = + # Returns the number of required params (without default values) + # XXX: Perhaps we can store this in the `offset` field of the + # symbol instead? + for i in 1 ..< s.typ.len: + if s.typ.n[i].sym.ast != nil: + return i - 1 + return s.typ.len - 1 + proc hasPattern*(s: PSym): bool {.inline.} = result = isRoutine(s) and s.ast.sons[patternPos].kind != nkEmpty @@ -1617,7 +1676,7 @@ iterator items*(n: PNode): PNode = for i in 0..<n.safeLen: yield n.sons[i] iterator pairs*(n: PNode): tuple[i: int, n: PNode] = - for i in 0..<n.len: yield (i, n.sons[i]) + for i in 0..<n.safeLen: yield (i, n.sons[i]) proc isAtom*(n: PNode): bool {.inline.} = result = n.kind >= nkNone and n.kind <= nkNilLit @@ -1641,6 +1700,14 @@ proc skipStmtList*(n: PNode): PNode = else: result = n +proc toVar*(typ: PType): PType = + ## If ``typ`` is not a tyVar then it is converted into a `var <typ>` and + ## returned. Otherwise ``typ`` is simply returned as-is. + result = typ + if typ.kind != tyVar: + result = newType(tyVar, typ.owner) + rawAddSon(result, typ) + proc toRef*(typ: PType): PType = ## If ``typ`` is a tyObject then it is converted into a `ref <typ>` and ## returned. Otherwise ``typ`` is simply returned as-is. @@ -1666,11 +1733,24 @@ proc isException*(t: PType): bool = var base = t while base != nil: - if base.sym.magic == mException: + if base.sym != nil and base.sym.magic == mException: return true base = base.lastSon return false +proc isImportedException*(t: PType; conf: ConfigRef): bool = + assert(t != nil) + if optNoCppExceptions in conf.globalOptions: + return false + + let base = t.skipTypes({tyAlias, tyPtr, tyDistinct, tyGenericInst}) + + if base.sym != nil and sfCompileToCpp in base.sym.flags: + result = true + +proc isInfixAs*(n: PNode): bool = + return n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s == "as" + proc findUnresolvedStatic*(n: PNode): PNode = if n.kind == nkSym and n.typ.kind == tyStatic and n.typ.n == nil: return n @@ -1694,3 +1774,5 @@ template incompleteType*(t: PType): bool = template typeCompleted*(s: PSym) = incl s.flags, sfNoForward + +template getBody*(s: PSym): PNode = s.ast[bodyPos] diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index 196ac8690..34963ee83 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -12,81 +12,58 @@ # the data structures here are used in various places of the compiler. import - ast, hashes, intsets, strutils, options, msgs, ropes, idents, rodutils + ast, hashes, intsets, strutils, options, lineinfos, ropes, idents, rodutils, + msgs proc hashNode*(p: RootRef): Hash -proc treeToYaml*(n: PNode, indent: int = 0, maxRecDepth: int = - 1): Rope +proc treeToYaml*(conf: ConfigRef; n: PNode, indent: int = 0, maxRecDepth: int = - 1): Rope # Convert a tree into its YAML representation; this is used by the # YAML code generator and it is invaluable for debugging purposes. # If maxRecDepht <> -1 then it won't print the whole graph. -proc typeToYaml*(n: PType, indent: int = 0, maxRecDepth: int = - 1): Rope -proc symToYaml*(n: PSym, indent: int = 0, maxRecDepth: int = - 1): Rope -proc lineInfoToStr*(info: TLineInfo): Rope - -# ----------------------- node sets: --------------------------------------- -proc objectSetContains*(t: TObjectSet, obj: RootRef): bool - # returns true whether n is in t -proc objectSetIncl*(t: var TObjectSet, obj: RootRef) - # include an element n in the table t -proc objectSetContainsOrIncl*(t: var TObjectSet, obj: RootRef): bool - # more are not needed ... - -# ----------------------- str table ----------------------------------------- -proc strTableContains*(t: TStrTable, n: PSym): bool -proc strTableAdd*(t: var TStrTable, n: PSym) -proc strTableGet*(t: TStrTable, name: PIdent): PSym - -type - TTabIter*{.final.} = object # consider all fields here private - h*: Hash # current hash - -proc initTabIter*(ti: var TTabIter, tab: TStrTable): PSym -proc nextIter*(ti: var TTabIter, tab: TStrTable): PSym - # usage: - # var - # i: TTabIter - # s: PSym - # s = InitTabIter(i, table) - # while s != nil: - # ... - # s = NextIter(i, table) - # - -type - TIdentIter*{.final.} = object # iterator over all syms with same identifier - h*: Hash # current hash - name*: PIdent - - -proc initIdentIter*(ti: var TIdentIter, tab: TStrTable, s: PIdent): PSym -proc nextIdentIter*(ti: var TIdentIter, tab: TStrTable): PSym - -# these are for debugging only: They are not really deprecated, but I want -# the warning so that release versions do not contain debugging statements: -proc debug*(n: PSym) {.deprecated.} -proc debug*(n: PType) {.deprecated.} -proc debug*(n: PNode) {.deprecated.} - -template mdbg*: bool {.dirty.} = - when compiles(c.module): - c.module.fileIdx == gProjectMainIdx - elif compiles(c.c.module): - c.c.module.fileIdx == gProjectMainIdx - elif compiles(m.c.module): - m.c.module.fileIdx == gProjectMainIdx - elif compiles(cl.c.module): - cl.c.module.fileIdx == gProjectMainIdx - elif compiles(p): - when compiles(p.lex): - p.lex.fileIdx == gProjectMainIdx +proc typeToYaml*(conf: ConfigRef; n: PType, indent: int = 0, maxRecDepth: int = - 1): Rope +proc symToYaml*(conf: ConfigRef; n: PSym, indent: int = 0, maxRecDepth: int = - 1): Rope +proc lineInfoToStr*(conf: ConfigRef; info: TLineInfo): Rope + +when declared(echo): + # these are for debugging only: They are not really deprecated, but I want + # the warning so that release versions do not contain debugging statements: + proc debug*(n: PSym; conf: ConfigRef = nil) {.exportc: "debugSym", deprecated.} + proc debug*(n: PType; conf: ConfigRef = nil) {.exportc: "debugType", deprecated.} + proc debug*(n: PNode; conf: ConfigRef = nil) {.exportc: "debugNode", deprecated.} + + template debug*(x: PSym|PType|PNode) {.deprecated.} = + when compiles(c.config): + debug(c.config, x) + elif compiles(c.graph.config): + debug(c.graph.config, x) else: - p.module.module.fileIdx == gProjectMainIdx - elif compiles(m.module.fileIdx): - m.module.fileIdx == gProjectMainIdx - elif compiles(L.fileIdx): - L.fileIdx == gProjectMainIdx - else: - error() + error() + + template debug*(x: auto) {.deprecated.} = + echo x + + template mdbg*: bool {.deprecated.} = + when compiles(c.graph): + c.module.fileIdx == c.graph.config.projectMainIdx + elif compiles(c.module): + c.module.fileIdx == c.config.projectMainIdx + elif compiles(c.c.module): + c.c.module.fileIdx == c.c.config.projectMainIdx + elif compiles(m.c.module): + m.c.module.fileIdx == m.c.config.projectMainIdx + elif compiles(cl.c.module): + cl.c.module.fileIdx == cl.c.config.projectMainIdx + elif compiles(p): + when compiles(p.lex): + p.lex.fileIdx == p.lex.config.projectMainIdx + else: + p.module.module.fileIdx == p.config.projectMainIdx + elif compiles(m.module.fileIdx): + m.module.fileIdx == m.config.projectMainIdx + elif compiles(L.fileIdx): + L.fileIdx == L.config.projectMainIdx + else: + error() # --------------------------- ident tables ---------------------------------- proc idTableGet*(t: TIdTable, key: PIdObj): RootRef @@ -102,7 +79,6 @@ proc idNodeTablePut*(t: var TIdNodeTable, key: PIdObj, val: PNode) proc getSymFromList*(list: PNode, ident: PIdent, start: int = 0): PSym proc lookupInRecord*(n: PNode, field: PIdent): PSym -proc getModule*(s: PSym): PSym proc mustRehash*(length, counter: int): bool proc nextTry*(h, maxHash: Hash): Hash {.inline.} @@ -180,7 +156,7 @@ proc lookupInRecord(n: PNode, field: PIdent): PSym = result = lookupInRecord(n.sons[i], field) if result != nil: return of nkRecCase: - if (n.sons[0].kind != nkSym): internalError(n.info, "lookupInRecord") + if (n.sons[0].kind != nkSym): return nil result = lookupInRecord(n.sons[0], field) if result != nil: return for i in countup(1, sonsLen(n) - 1): @@ -188,12 +164,12 @@ proc lookupInRecord(n: PNode, field: PIdent): PSym = of nkOfBranch, nkElse: result = lookupInRecord(lastSon(n.sons[i]), field) if result != nil: return - else: internalError(n.info, "lookupInRecord(record case branch)") + else: return nil of nkSym: if n.sym.name.id == field.id: result = n.sym - else: internalError(n.info, "lookupInRecord()") + else: return nil -proc getModule(s: PSym): PSym = +proc getModule*(s: PSym): PSym = result = s assert((result.kind == skModule) or (result.owner != result)) while result != nil and result.kind != skModule: result = result.owner @@ -203,7 +179,7 @@ proc getSymFromList(list: PNode, ident: PIdent, start: int = 0): PSym = if list.sons[i].kind == nkSym: result = list.sons[i].sym if result.name.id == ident.id: return - else: internalError(list.info, "getSymFromList") + else: return nil result = nil proc hashNode(p: RootRef): Hash = @@ -230,7 +206,7 @@ proc makeYamlString*(s: string): Rope = const MaxLineLength = 64 result = nil var res = "\"" - for i in countup(0, if s.isNil: -1 else: (len(s)-1)): + for i in 0 ..< s.len: if (i + 1) mod MaxLineLength == 0: add(res, '\"') add(res, "\n") @@ -250,16 +226,16 @@ proc flagsToStr[T](flags: set[T]): Rope = add(result, makeYamlString($x)) result = "[" & result & "]" -proc lineInfoToStr(info: TLineInfo): Rope = - result = "[$1, $2, $3]" % [makeYamlString(toFilename(info)), +proc lineInfoToStr(conf: ConfigRef; info: TLineInfo): Rope = + result = "[$1, $2, $3]" % [makeYamlString(toFilename(conf, info)), rope(toLinenumber(info)), rope(toColumn(info))] -proc treeToYamlAux(n: PNode, marker: var IntSet, +proc treeToYamlAux(conf: ConfigRef; n: PNode, marker: var IntSet, indent, maxRecDepth: int): Rope -proc symToYamlAux(n: PSym, marker: var IntSet, +proc symToYamlAux(conf: ConfigRef; n: PSym, marker: var IntSet, indent, maxRecDepth: int): Rope -proc typeToYamlAux(n: PType, marker: var IntSet, +proc typeToYamlAux(conf: ConfigRef; n: PType, marker: var IntSet, indent, maxRecDepth: int): Rope proc ropeConstr(indent: int, c: openArray[Rope]): Rope = @@ -273,7 +249,7 @@ proc ropeConstr(indent: int, c: openArray[Rope]): Rope = inc(i, 2) addf(result, "$N$1}", [rspaces(indent)]) -proc symToYamlAux(n: PSym, marker: var IntSet, indent: int, +proc symToYamlAux(conf: ConfigRef; n: PSym, marker: var IntSet, indent: int, maxRecDepth: int): Rope = if n == nil: result = rope("null") @@ -281,20 +257,20 @@ proc symToYamlAux(n: PSym, marker: var IntSet, indent: int, result = "\"$1 @$2\"" % [rope(n.name.s), rope( strutils.toHex(cast[ByteAddress](n), sizeof(n) * 2))] else: - var ast = treeToYamlAux(n.ast, marker, indent + 2, maxRecDepth - 1) + var ast = treeToYamlAux(conf, n.ast, marker, indent + 2, maxRecDepth - 1) result = ropeConstr(indent, [rope("kind"), makeYamlString($n.kind), rope("name"), makeYamlString(n.name.s), - rope("typ"), typeToYamlAux(n.typ, marker, + rope("typ"), typeToYamlAux(conf, n.typ, marker, indent + 2, maxRecDepth - 1), - rope("info"), lineInfoToStr(n.info), + rope("info"), lineInfoToStr(conf, n.info), rope("flags"), flagsToStr(n.flags), rope("magic"), makeYamlString($n.magic), rope("ast"), ast, rope("options"), flagsToStr(n.options), rope("position"), rope(n.position)]) -proc typeToYamlAux(n: PType, marker: var IntSet, indent: int, +proc typeToYamlAux(conf: ConfigRef; n: PType, marker: var IntSet, indent: int, maxRecDepth: int): Rope = if n == nil: result = rope("null") @@ -306,15 +282,15 @@ proc typeToYamlAux(n: PType, marker: var IntSet, indent: int, result = rope("[") for i in countup(0, sonsLen(n) - 1): if i > 0: add(result, ",") - addf(result, "$N$1$2", [rspaces(indent + 4), typeToYamlAux(n.sons[i], + addf(result, "$N$1$2", [rspaces(indent + 4), typeToYamlAux(conf, n.sons[i], marker, indent + 4, maxRecDepth - 1)]) addf(result, "$N$1]", [rspaces(indent + 2)]) else: result = rope("null") result = ropeConstr(indent, [rope("kind"), makeYamlString($n.kind), - rope("sym"), symToYamlAux(n.sym, marker, - indent + 2, maxRecDepth - 1), rope("n"), treeToYamlAux(n.n, marker, + rope("sym"), symToYamlAux(conf, n.sym, marker, + indent + 2, maxRecDepth - 1), rope("n"), treeToYamlAux(conf, n.n, marker, indent + 2, maxRecDepth - 1), rope("flags"), flagsToStr(n.flags), rope("callconv"), makeYamlString(CallingConvToStr[n.callConv]), @@ -322,7 +298,7 @@ proc typeToYamlAux(n: PType, marker: var IntSet, indent: int, rope("align"), rope(n.align), rope("sons"), result]) -proc treeToYamlAux(n: PNode, marker: var IntSet, indent: int, +proc treeToYamlAux(conf: ConfigRef; n: PNode, marker: var IntSet, indent: int, maxRecDepth: int): Rope = if n == nil: result = rope("null") @@ -330,7 +306,7 @@ proc treeToYamlAux(n: PNode, marker: var IntSet, indent: int, var istr = rspaces(indent + 2) result = "{$N$1\"kind\": $2" % [istr, makeYamlString($n.kind)] if maxRecDepth != 0: - addf(result, ",$N$1\"info\": $2", [istr, lineInfoToStr(n.info)]) + addf(result, ",$N$1\"info\": $2", [istr, lineInfoToStr(conf, n.info)]) case n.kind of nkCharLit..nkInt64Lit: addf(result, ",$N$1\"intVal\": $2", [istr, rope(n.intVal)]) @@ -338,13 +314,10 @@ proc treeToYamlAux(n: PNode, marker: var IntSet, indent: int, addf(result, ",$N$1\"floatVal\": $2", [istr, rope(n.floatVal.toStrMaxPrecision)]) of nkStrLit..nkTripleStrLit: - if n.strVal.isNil: - addf(result, ",$N$1\"strVal\": null", [istr]) - else: - addf(result, ",$N$1\"strVal\": $2", [istr, makeYamlString(n.strVal)]) + addf(result, ",$N$1\"strVal\": $2", [istr, makeYamlString(n.strVal)]) of nkSym: addf(result, ",$N$1\"sym\": $2", - [istr, symToYamlAux(n.sym, marker, indent + 2, maxRecDepth)]) + [istr, symToYamlAux(conf, n.sym, marker, indent + 2, maxRecDepth)]) of nkIdent: if n.ident != nil: addf(result, ",$N$1\"ident\": $2", [istr, makeYamlString(n.ident.s)]) @@ -355,27 +328,27 @@ proc treeToYamlAux(n: PNode, marker: var IntSet, indent: int, addf(result, ",$N$1\"sons\": [", [istr]) for i in countup(0, sonsLen(n) - 1): if i > 0: add(result, ",") - addf(result, "$N$1$2", [rspaces(indent + 4), treeToYamlAux(n.sons[i], + addf(result, "$N$1$2", [rspaces(indent + 4), treeToYamlAux(conf, n.sons[i], marker, indent + 4, maxRecDepth - 1)]) addf(result, "$N$1]", [istr]) addf(result, ",$N$1\"typ\": $2", - [istr, typeToYamlAux(n.typ, marker, indent + 2, maxRecDepth)]) + [istr, typeToYamlAux(conf, n.typ, marker, indent + 2, maxRecDepth)]) addf(result, "$N$1}", [rspaces(indent)]) -proc treeToYaml(n: PNode, indent: int = 0, maxRecDepth: int = - 1): Rope = +proc treeToYaml(conf: ConfigRef; n: PNode, indent: int = 0, maxRecDepth: int = - 1): Rope = var marker = initIntSet() - result = treeToYamlAux(n, marker, indent, maxRecDepth) + result = treeToYamlAux(conf, n, marker, indent, maxRecDepth) -proc typeToYaml(n: PType, indent: int = 0, maxRecDepth: int = - 1): Rope = +proc typeToYaml(conf: ConfigRef; n: PType, indent: int = 0, maxRecDepth: int = - 1): Rope = var marker = initIntSet() - result = typeToYamlAux(n, marker, indent, maxRecDepth) + result = typeToYamlAux(conf, n, marker, indent, maxRecDepth) -proc symToYaml(n: PSym, indent: int = 0, maxRecDepth: int = - 1): Rope = +proc symToYaml(conf: ConfigRef; n: PSym, indent: int = 0, maxRecDepth: int = - 1): Rope = var marker = initIntSet() - result = symToYamlAux(n, marker, indent, maxRecDepth) + result = symToYamlAux(conf, n, marker, indent, maxRecDepth) -proc debugTree*(n: PNode, indent: int, maxRecDepth: int; renderType=false): Rope -proc debugType(n: PType, maxRecDepth=100): Rope = +proc debugTree*(conf: ConfigRef; n: PNode, indent: int, maxRecDepth: int; renderType=false): Rope +proc debugType(conf: ConfigRef; n: PType, maxRecDepth=100): Rope = if n == nil: result = rope("null") else: @@ -385,7 +358,7 @@ proc debugType(n: PType, maxRecDepth=100): Rope = add(result, n.sym.name.s) if n.kind in IntegralTypes and n.n != nil: add(result, ", node: ") - add(result, debugTree(n.n, 2, maxRecDepth-1, renderType=true)) + add(result, debugTree(conf, n.n, 2, maxRecDepth-1, renderType=true)) if (n.kind != tyString) and (sonsLen(n) > 0) and maxRecDepth != 0: add(result, "(") for i in countup(0, sonsLen(n) - 1): @@ -393,13 +366,13 @@ proc debugType(n: PType, maxRecDepth=100): Rope = if n.sons[i] == nil: add(result, "null") else: - add(result, debugType(n.sons[i], maxRecDepth-1)) + add(result, debugType(conf, n.sons[i], maxRecDepth-1)) if n.kind == tyObject and n.n != nil: add(result, ", node: ") - add(result, debugTree(n.n, 2, maxRecDepth-1, renderType=true)) + add(result, debugTree(conf, n.n, 2, maxRecDepth-1, renderType=true)) add(result, ")") -proc debugTree(n: PNode, indent: int, maxRecDepth: int; +proc debugTree(conf: ConfigRef; n: PNode, indent: int, maxRecDepth: int; renderType=false): Rope = if n == nil: result = rope("null") @@ -409,7 +382,7 @@ proc debugTree(n: PNode, indent: int, maxRecDepth: int; [istr, makeYamlString($n.kind)] when defined(useNodeIds): addf(result, ",$N$1\"id\": $2", [istr, rope(n.id)]) - addf(result, ",$N$1\"info\": $2", [istr, lineInfoToStr(n.info)]) + addf(result, ",$N$1\"info\": $2", [istr, lineInfoToStr(conf, n.info)]) if maxRecDepth != 0: addf(result, ",$N$1\"flags\": $2", [istr, rope($n.flags)]) case n.kind @@ -419,48 +392,48 @@ proc debugTree(n: PNode, indent: int, maxRecDepth: int; addf(result, ",$N$1\"floatVal\": $2", [istr, rope(n.floatVal.toStrMaxPrecision)]) of nkStrLit..nkTripleStrLit: - if n.strVal.isNil: - addf(result, ",$N$1\"strVal\": null", [istr]) - else: - addf(result, ",$N$1\"strVal\": $2", [istr, makeYamlString(n.strVal)]) + addf(result, ",$N$1\"strVal\": $2", [istr, makeYamlString(n.strVal)]) of nkSym: addf(result, ",$N$1\"sym\": $2_$3", [istr, rope(n.sym.name.s), rope(n.sym.id)]) # [istr, symToYaml(n.sym, indent, maxRecDepth), # rope(n.sym.id)]) if renderType and n.sym.typ != nil: - addf(result, ",$N$1\"typ\": $2", [istr, debugType(n.sym.typ, 2)]) + addf(result, ",$N$1\"typ\": $2", [istr, debugType(conf, n.sym.typ, 2)]) of nkIdent: if n.ident != nil: addf(result, ",$N$1\"ident\": $2", [istr, makeYamlString(n.ident.s)]) else: addf(result, ",$N$1\"ident\": null", [istr]) else: + if renderType and n.typ != nil: + addf(result, ",$N$1\"typ\": $2", [istr, debugType(conf, n.typ, 2)]) if sonsLen(n) > 0: addf(result, ",$N$1\"sons\": [", [istr]) for i in countup(0, sonsLen(n) - 1): if i > 0: add(result, ",") - addf(result, "$N$1$2", [rspaces(indent + 4), debugTree(n.sons[i], + addf(result, "$N$1$2", [rspaces(indent + 4), debugTree(conf, n.sons[i], indent + 4, maxRecDepth - 1, renderType)]) addf(result, "$N$1]", [istr]) addf(result, "$N$1}", [rspaces(indent)]) -proc debug(n: PSym) = - if n == nil: - echo("null") - elif n.kind == skUnknown: - echo("skUnknown") - else: - #writeLine(stdout, $symToYaml(n, 0, 1)) - echo("$1_$2: $3, $4, $5, $6" % [ - n.name.s, $n.id, $flagsToStr(n.flags), $flagsToStr(n.loc.flags), - $lineInfoToStr(n.info), $n.kind]) +when declared(echo): + proc debug(n: PSym; conf: ConfigRef) = + if n == nil: + echo("null") + elif n.kind == skUnknown: + echo("skUnknown") + else: + #writeLine(stdout, $symToYaml(n, 0, 1)) + echo("$1_$2: $3, $4, $5, $6" % [ + n.name.s, $n.id, $flagsToStr(n.flags), $flagsToStr(n.loc.flags), + $lineInfoToStr(conf, n.info), $n.kind]) -proc debug(n: PType) = - echo($debugType(n)) + proc debug(n: PType; conf: ConfigRef) = + echo($debugType(conf, n)) -proc debug(n: PNode) = - echo($debugTree(n, 0, 100)) + proc debug(n: PNode; conf: ConfigRef) = + echo($debugTree(conf, n, 0, 100)) proc nextTry(h, maxHash: Hash): Hash = result = ((5 * h) + 1) and maxHash @@ -468,7 +441,7 @@ proc nextTry(h, maxHash: Hash): Hash = # generates each int in range(maxHash) exactly once (see any text on # random-number generation for proof). -proc objectSetContains(t: TObjectSet, obj: RootRef): bool = +proc objectSetContains*(t: TObjectSet, obj: RootRef): bool = # returns true whether n is in t var h: Hash = hashNode(obj) and high(t.data) # start with real hash value while t.data[h] != nil: @@ -492,12 +465,12 @@ proc objectSetEnlarge(t: var TObjectSet) = if t.data[i] != nil: objectSetRawInsert(n, t.data[i]) swap(t.data, n) -proc objectSetIncl(t: var TObjectSet, obj: RootRef) = +proc objectSetIncl*(t: var TObjectSet, obj: RootRef) = if mustRehash(len(t.data), t.counter): objectSetEnlarge(t) objectSetRawInsert(t.data, obj) inc(t.counter) -proc objectSetContainsOrIncl(t: var TObjectSet, obj: RootRef): bool = +proc objectSetContainsOrIncl*(t: var TObjectSet, obj: RootRef): bool = # returns true if obj is already in the string table: var h: Hash = hashNode(obj) and high(t.data) while true: @@ -515,7 +488,7 @@ proc objectSetContainsOrIncl(t: var TObjectSet, obj: RootRef): bool = inc(t.counter) result = false -proc strTableContains(t: TStrTable, n: PSym): bool = +proc strTableContains*(t: TStrTable, n: PSym): bool = var h: Hash = n.name.h and high(t.data) # start with real hash value while t.data[h] != nil: if (t.data[h] == n): @@ -571,7 +544,7 @@ proc strTableEnlarge(t: var TStrTable) = if t.data[i] != nil: strTableRawInsert(n, t.data[i]) swap(t.data, n) -proc strTableAdd(t: var TStrTable, n: PSym) = +proc strTableAdd*(t: var TStrTable, n: PSym) = if mustRehash(len(t.data), t.counter): strTableEnlarge(t) strTableRawInsert(t.data, n) inc(t.counter) @@ -607,7 +580,7 @@ proc strTableIncl*(t: var TStrTable, n: PSym; onConflictKeepOld=false): bool {.d inc(t.counter) result = false -proc strTableGet(t: TStrTable, name: PIdent): PSym = +proc strTableGet*(t: TStrTable, name: PIdent): PSym = var h: Hash = name.h and high(t.data) while true: result = t.data[h] @@ -615,13 +588,13 @@ proc strTableGet(t: TStrTable, name: PIdent): PSym = if result.name.id == name.id: break h = nextTry(h, high(t.data)) -proc initIdentIter(ti: var TIdentIter, tab: TStrTable, s: PIdent): PSym = - ti.h = s.h - ti.name = s - if tab.counter == 0: result = nil - else: result = nextIdentIter(ti, tab) -proc nextIdentIter(ti: var TIdentIter, tab: TStrTable): PSym = +type + TIdentIter* = object # iterator over all syms with same identifier + h*: Hash # current hash + name*: PIdent + +proc nextIdentIter*(ti: var TIdentIter, tab: TStrTable): PSym = var h = ti.h and high(tab.data) var start = h result = tab.data[h] @@ -634,6 +607,12 @@ proc nextIdentIter(ti: var TIdentIter, tab: TStrTable): PSym = result = tab.data[h] ti.h = nextTry(h, high(tab.data)) +proc initIdentIter*(ti: var TIdentIter, tab: TStrTable, s: PIdent): PSym = + ti.h = s.h + ti.name = s + if tab.counter == 0: result = nil + else: result = nextIdentIter(ti, tab) + proc nextIdentExcluding*(ti: var TIdentIter, tab: TStrTable, excluding: IntSet): PSym = var h: Hash = ti.h and high(tab.data) @@ -657,20 +636,33 @@ proc firstIdentExcluding*(ti: var TIdentIter, tab: TStrTable, s: PIdent, if tab.counter == 0: result = nil else: result = nextIdentExcluding(ti, tab, excluding) -proc initTabIter(ti: var TTabIter, tab: TStrTable): PSym = - ti.h = 0 # we start by zero ... - if tab.counter == 0: - result = nil # FIX 1: removed endless loop - else: - result = nextIter(ti, tab) +type + TTabIter* = object + h: Hash -proc nextIter(ti: var TTabIter, tab: TStrTable): PSym = +proc nextIter*(ti: var TTabIter, tab: TStrTable): PSym = + # usage: + # var + # i: TTabIter + # s: PSym + # s = InitTabIter(i, table) + # while s != nil: + # ... + # s = NextIter(i, table) + # result = nil while (ti.h <= high(tab.data)): result = tab.data[ti.h] inc(ti.h) # ... and increment by one always if result != nil: break +proc initTabIter*(ti: var TTabIter, tab: TStrTable): PSym = + ti.h = 0 + if tab.counter == 0: + result = nil + else: + result = nextIter(ti, tab) + iterator items*(tab: TStrTable): PSym = var it: TTabIter var s = initTabIter(it, tab) @@ -761,10 +753,6 @@ proc idNodeTableGet(t: TIdNodeTable, key: PIdObj): PNode = if index >= 0: result = t.data[index].val else: result = nil -proc idNodeTableGetLazy*(t: TIdNodeTable, key: PIdObj): PNode = - if not isNil(t.data): - result = idNodeTableGet(t, key) - proc idNodeTableRawInsert(data: var TIdNodePairSeq, key: PIdObj, val: PNode) = var h: Hash h = key.id and high(data) @@ -791,10 +779,6 @@ proc idNodeTablePut(t: var TIdNodeTable, key: PIdObj, val: PNode) = idNodeTableRawInsert(t.data, key, val) inc(t.counter) -proc idNodeTablePutLazy*(t: var TIdNodeTable, key: PIdObj, val: PNode) = - if isNil(t.data): initIdNodeTable(t) - idNodeTablePut(t, key, val) - iterator pairs*(t: TIdNodeTable): tuple[key: PIdObj, val: PNode] = for i in 0 .. high(t.data): if not isNil(t.data[i].key): yield (t.data[i].key, t.data[i].val) diff --git a/compiler/bitsets.nim b/compiler/bitsets.nim index 5454ef5e7..e38732877 100644 --- a/compiler/bitsets.nim +++ b/compiler/bitsets.nim @@ -28,6 +28,7 @@ proc bitSetExcl*(x: var TBitSet, elem: BiggestInt) proc bitSetIn*(x: TBitSet, e: BiggestInt): bool proc bitSetEquals*(x, y: TBitSet): bool proc bitSetContains*(x, y: TBitSet): bool +proc bitSetCard*(x: TBitSet): BiggestInt # implementation proc bitSetIn(x: TBitSet, e: BiggestInt): bool = @@ -69,3 +70,28 @@ proc bitSetContains(x, y: TBitSet): bool = if (x[i] and not y[i]) != int8(0): return false result = true + +# Number of set bits for all values of int8 +const populationCount: array[low(int8)..high(int8), int8] = block: + var arr: array[low(int8)..high(int8), int8] + + proc countSetBits(x: int8): int8 = + return + ( x and 0b00000001'i8) + + ((x and 0b00000010'i8) shr 1) + + ((x and 0b00000100'i8) shr 2) + + ((x and 0b00001000'i8) shr 3) + + ((x and 0b00010000'i8) shr 4) + + ((x and 0b00100000'i8) shr 5) + + ((x and 0b01000000'i8) shr 6) + + ((x and 0b10000000'i8) shr 7) + + + for it in low(int8)..high(int8): + arr[it] = countSetBits(it) + + arr + +proc bitSetCard(x: TBitSet): BiggestInt = + for it in x: + result.inc populationCount[it] diff --git a/compiler/btrees.nim b/compiler/btrees.nim new file mode 100644 index 000000000..6cd6e51f4 --- /dev/null +++ b/compiler/btrees.nim @@ -0,0 +1,233 @@ +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## BTree implementation with few features, but good enough for the +## Nim compiler's needs. + +const + M = 512 # max children per B-tree node = M-1 + # (must be even and greater than 2) + Mhalf = M div 2 + +type + Node[Key, Val] = ref object + entries: int + keys: array[M, Key] + case isInternal: bool + of false: + vals: array[M, Val] + of true: + links: array[M, Node[Key, Val]] + BTree*[Key, Val] = object + root: Node[Key, Val] + entries: int ## number of key-value pairs + +proc initBTree*[Key, Val](): BTree[Key, Val] = + BTree[Key, Val](root: Node[Key, Val](entries: 0, isInternal: false)) + +template less(a, b): bool = cmp(a, b) < 0 +template eq(a, b): bool = cmp(a, b) == 0 + +proc getOrDefault*[Key, Val](b: BTree[Key, Val], key: Key): Val = + var x = b.root + while x.isInternal: + for j in 0 ..< x.entries: + if j+1 == x.entries or less(key, x.keys[j+1]): + x = x.links[j] + break + assert(not x.isInternal) + for j in 0 ..< x.entries: + if eq(key, x.keys[j]): return x.vals[j] + +proc contains*[Key, Val](b: BTree[Key, Val], key: Key): bool = + var x = b.root + while x.isInternal: + for j in 0 ..< x.entries: + if j+1 == x.entries or less(key, x.keys[j+1]): + x = x.links[j] + break + assert(not x.isInternal) + for j in 0 ..< x.entries: + if eq(key, x.keys[j]): return true + return false + +proc copyHalf[Key, Val](h, result: Node[Key, Val]) = + for j in 0 ..< Mhalf: + result.keys[j] = h.keys[Mhalf + j] + if h.isInternal: + for j in 0 ..< Mhalf: + result.links[j] = h.links[Mhalf + j] + else: + for j in 0 ..< Mhalf: + shallowCopy(result.vals[j], h.vals[Mhalf + j]) + +proc split[Key, Val](h: Node[Key, Val]): Node[Key, Val] = + ## split node in half + result = Node[Key, Val](entries: Mhalf, isInternal: h.isInternal) + h.entries = Mhalf + copyHalf(h, result) + +proc insert[Key, Val](h: Node[Key, Val], key: Key, val: Val): Node[Key, Val] = + #var t = Entry(key: key, val: val, next: nil) + var newKey = key + var j = 0 + if not h.isInternal: + while j < h.entries: + if less(key, h.keys[j]): break + inc j + for i in countdown(h.entries, j+1): + shallowCopy(h.vals[i], h.vals[i-1]) + h.vals[j] = val + else: + var newLink: Node[Key, Val] = nil + while j < h.entries: + if j+1 == h.entries or less(key, h.keys[j+1]): + let u = insert(h.links[j], key, val) + inc j + if u == nil: return nil + newKey = u.keys[0] + newLink = u + break + inc j + for i in countdown(h.entries, j+1): + h.links[i] = h.links[i-1] + h.links[j] = newLink + + for i in countdown(h.entries, j+1): + h.keys[i] = h.keys[i-1] + h.keys[j] = newKey + inc h.entries + return if h.entries < M: nil else: split(h) + +proc add*[Key, Val](b: var BTree[Key, Val]; key: Key; val: Val) = + let u = insert(b.root, key, val) + inc b.entries + if u == nil: return + + # need to split root + let t = Node[Key, Val](entries: 2, isInternal: true) + t.keys[0] = b.root.keys[0] + t.links[0] = b.root + t.keys[1] = u.keys[0] + t.links[1] = u + b.root = t + +proc toString[Key, Val](h: Node[Key, Val], indent: string; result: var string) = + if not h.isInternal: + for j in 0..<h.entries: + result.add(indent) + result.add($h.keys[j] & " " & $h.vals[j] & "\n") + else: + for j in 0..<h.entries: + if j > 0: result.add(indent & "(" & $h.keys[j] & ")\n") + toString(h.links[j], indent & " ", result) + +proc `$`[Key, Val](b: BTree[Key, Val]): string = + result = "" + toString(b.root, "", result) + +proc hasNext*[Key, Val](b: BTree[Key, Val]; index: int): bool = + result = index < b.entries + +proc countSubTree[Key, Val](it: Node[Key, Val]): int = + if it.isInternal: + result = 0 + for k in 0..<it.entries: + inc result, countSubTree(it.links[k]) + else: + result = it.entries + +proc next*[Key, Val](b: BTree[Key, Val]; index: int): (Key, Val, int) = + var it = b.root + var i = index + # navigate to the right leaf: + while it.isInternal: + var sum = 0 + for k in 0..<it.entries: + let c = countSubTree(it.links[k]) + inc sum, c + if sum > i: + it = it.links[k] + dec i, (sum - c) + break + result = (it.keys[i], it.vals[i], index+1) + +iterator pairs*[Key, Val](b: BTree[Key, Val]): (Key, Val) = + var i = 0 + while hasNext(b, i): + let (k, v, i2) = next(b, i) + i = i2 + yield (k, v) + +proc len*[Key, Val](b: BTree[Key, Val]): int {.inline.} = b.entries + +when isMainModule: + + import random, tables + + proc main = + var st = initBTree[string, string]() + st.add("www.cs.princeton.edu", "abc") + st.add("www.princeton.edu", "128.112.128.15") + st.add("www.yale.edu", "130.132.143.21") + st.add("www.simpsons.com", "209.052.165.60") + st.add("www.apple.com", "17.112.152.32") + st.add("www.amazon.com", "207.171.182.16") + st.add("www.ebay.com", "66.135.192.87") + st.add("www.cnn.com", "64.236.16.20") + st.add("www.google.com", "216.239.41.99") + st.add("www.nytimes.com", "199.239.136.200") + st.add("www.microsoft.com", "207.126.99.140") + st.add("www.dell.com", "143.166.224.230") + st.add("www.slashdot.org", "66.35.250.151") + st.add("www.espn.com", "199.181.135.201") + st.add("www.weather.com", "63.111.66.11") + st.add("www.yahoo.com", "216.109.118.65") + + assert st.getOrDefault("www.cs.princeton.edu") == "abc" + assert st.getOrDefault("www.harvardsucks.com") == nil + + assert st.getOrDefault("www.simpsons.com") == "209.052.165.60" + assert st.getOrDefault("www.apple.com") == "17.112.152.32" + assert st.getOrDefault("www.ebay.com") == "66.135.192.87" + assert st.getOrDefault("www.dell.com") == "143.166.224.230" + assert(st.entries == 16) + + for k, v in st: + echo k, ": ", v + + when false: + var b2 = initBTree[string, string]() + const iters = 10_000 + for i in 1..iters: + b2.add($i, $(iters - i)) + for i in 1..iters: + let x = b2.getOrDefault($i) + if x != $(iters - i): + echo "got ", x, ", but expected ", iters - i + echo b2.entries + + when true: + var b2 = initBTree[int, string]() + var t2 = initTable[int, string]() + const iters = 100_000 + for i in 1..iters: + let x = rand(high(int)) + if not t2.hasKey(x): + doAssert b2.getOrDefault(x).len == 0, " what, tree has this element " & $x + t2[x] = $x + b2.add(x, $x) + + doAssert b2.entries == t2.len + echo "unique entries ", b2.entries + for k, v in t2: + doAssert $k == v + doAssert b2.getOrDefault(k) == $k + + main() diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 18741732c..33b07a5a7 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -24,7 +24,7 @@ proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc, # getUniqueType() is too expensive here: var typ = skipTypes(ri.sons[0].typ, abstractInst) if typ.sons[0] != nil: - if isInvalidReturnType(typ.sons[0]): + if isInvalidReturnType(p.config, typ.sons[0]): if params != nil: pl.add(~", ") # beware of 'result = p(result)'. We may need to allocate a temporary: if d.k in {locTemp, locNone} or not leftAppearsOnRightSide(le, ri): @@ -33,13 +33,13 @@ proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc, elif d.k notin {locTemp} and not hasNoInit(ri): # reset before pass as 'result' var: discard "resetLoc(p, d)" - add(pl, addrLoc(d)) + add(pl, addrLoc(p.config, d)) add(pl, ~");$n") line(p, cpsStmts, pl) else: var tmp: TLoc getTemp(p, typ.sons[0], tmp, needsInit=true) - add(pl, addrLoc(tmp)) + add(pl, addrLoc(p.config, tmp)) add(pl, ~");$n") line(p, cpsStmts, pl) genAssignment(p, d, tmp, {}) # no need for deep copying @@ -83,6 +83,8 @@ proc isInCurrentFrame(p: BProc, n: PNode): bool = result = isInCurrentFrame(p, n.sons[0]) else: discard +proc genBoundsCheck(p: BProc; arr, a, b: TLoc) + proc openArrayLoc(p: BProc, n: PNode): Rope = var a: TLoc @@ -93,18 +95,27 @@ proc openArrayLoc(p: BProc, n: PNode): Rope = initLocExpr(p, q[1], a) initLocExpr(p, q[2], b) initLocExpr(p, q[3], c) - let fmt = - case skipTypes(a.t, abstractVar+{tyPtr}).kind - of tyOpenArray, tyVarargs, tyArray: - "($1)+($2), ($3)-($2)+1" - of tyString, tySequence: - if skipTypes(n.typ, abstractInst).kind == tyVar and - not compileToCpp(p.module): - "(*$1)->data+($2), ($3)-($2)+1" - else: - "$1->data+($2), ($3)-($2)+1" - else: (internalError("openArrayLoc: " & typeToString(a.t)); "") - result = fmt % [rdLoc(a), rdLoc(b), rdLoc(c)] + # but first produce the required index checks: + if optBoundsCheck in p.options: + genBoundsCheck(p, a, b, c) + let ty = skipTypes(a.t, abstractVar+{tyPtr}) + case ty.kind + of tyArray: + let first = firstOrd(p.config, ty) + if first == 0: + result = "($1)+($2), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c)] + else: + result = "($1)+(($2)-($4)), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c), intLiteral(first)] + of tyOpenArray, tyVarargs: + result = "($1)+($2), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c)] + of tyString, tySequence: + if skipTypes(n.typ, abstractInst).kind == tyVar and + not compileToCpp(p.module): + result = "(*$1)$4+($2), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c), dataField(p)] + else: + result = "$1$4+($2), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c), dataField(p)] + else: + internalError(p.config, "openArrayLoc: " & typeToString(a.t)) else: initLocExpr(p, n, a) case skipTypes(a.t, abstractVar).kind @@ -113,25 +124,29 @@ proc openArrayLoc(p: BProc, n: PNode): Rope = of tyString, tySequence: if skipTypes(n.typ, abstractInst).kind == tyVar and not compileToCpp(p.module): - result = "(*$1)->data, (*$1)->$2" % [a.rdLoc, lenField(p)] + var t: TLoc + t.r = "(*$1)" % [a.rdLoc] + result = "(*$1)$3, $2" % [a.rdLoc, lenExpr(p, t), dataField(p)] else: - result = "$1->data, $1->$2" % [a.rdLoc, lenField(p)] + result = "$1$3, $2" % [a.rdLoc, lenExpr(p, a), dataField(p)] of tyArray: - result = "$1, $2" % [rdLoc(a), rope(lengthOrd(a.t))] + result = "$1, $2" % [rdLoc(a), rope(lengthOrd(p.config, a.t))] of tyPtr, tyRef: case lastSon(a.t).kind of tyString, tySequence: - result = "(*$1)->data, (*$1)->$2" % [a.rdLoc, lenField(p)] + var t: TLoc + t.r = "(*$1)" % [a.rdLoc] + result = "(*$1)$3, $2" % [a.rdLoc, lenExpr(p, t), dataField(p)] of tyArray: - result = "$1, $2" % [rdLoc(a), rope(lengthOrd(lastSon(a.t)))] + result = "$1, $2" % [rdLoc(a), rope(lengthOrd(p.config, lastSon(a.t)))] else: - internalError("openArrayLoc: " & typeToString(a.t)) - else: internalError("openArrayLoc: " & typeToString(a.t)) + internalError(p.config, "openArrayLoc: " & typeToString(a.t)) + else: internalError(p.config, "openArrayLoc: " & typeToString(a.t)) proc genArgStringToCString(p: BProc, n: PNode): Rope {.inline.} = var a: TLoc initLocExpr(p, n.sons[0], a) - result = "$1->data" % [a.rdLoc] + result = ropecg(p.module, "#nimToCStringConv($1)", [a.rdLoc]) proc genArg(p: BProc, n: PNode, param: PSym; call: PNode): Rope = var a: TLoc @@ -140,9 +155,9 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode): Rope = elif skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs}: var n = if n.kind != nkHiddenAddr: n else: n.sons[0] result = openArrayLoc(p, n) - elif ccgIntroducedPtr(param): + elif ccgIntroducedPtr(p.config, param): initLocExpr(p, n, a) - result = addrLoc(a) + result = addrLoc(p.config, a) elif p.module.compileToCpp and param.typ.kind == tyVar and n.kind == nkHiddenAddr: initLocExprSingleUse(p, n.sons[0], a) @@ -152,7 +167,7 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode): Rope = if callee.kind == nkSym and {sfImportC, sfInfixCall, sfCompilerProc} * callee.sym.flags == {sfImportC} and {lfHeader, lfNoDecl} * callee.sym.loc.flags != {}: - result = addrLoc(a) + result = addrLoc(p.config, a) else: result = rdLoc(a) else: @@ -219,7 +234,7 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) = let rawProc = getRawProcType(p, typ) let callPattern = if tfIterator in typ.flags: PatIter else: PatProc if typ.sons[0] != nil: - if isInvalidReturnType(typ.sons[0]): + if isInvalidReturnType(p.config, typ.sons[0]): if sonsLen(ri) > 1: add(pl, ~", ") # beware of 'result = p(result)'. We may need to allocate a temporary: if d.k in {locTemp, locNone} or not leftAppearsOnRightSide(le, ri): @@ -229,12 +244,12 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) = elif d.k notin {locTemp} and not hasNoInit(ri): # reset before pass as 'result' var: discard "resetLoc(p, d)" - add(pl, addrLoc(d)) + add(pl, addrLoc(p.config, d)) genCallPattern() else: var tmp: TLoc getTemp(p, typ.sons[0], tmp, needsInit=true) - add(pl, addrLoc(tmp)) + add(pl, addrLoc(p.config, tmp)) genCallPattern() genAssignment(p, d, tmp, {}) # no need for deep copying else: @@ -261,7 +276,7 @@ proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType): Rope = result = genArgNoParam(p, ri.sons[i]) #, typ.n.sons[i].sym) else: if tfVarargs notin typ.flags: - localError(ri.info, "wrong argument count") + localError(p.config, ri.info, "wrong argument count") result = nil else: result = genArgNoParam(p, ri.sons[i]) @@ -325,7 +340,7 @@ proc genThisArg(p: BProc; ri: PNode; i: int; typ: PType): Rope = # for better or worse c2nim translates the 'this' argument to a 'var T'. # However manual wrappers may also use 'ptr T'. In any case we support both # for convenience. - internalAssert i < sonsLen(typ) + internalAssert p.config, i < sonsLen(typ) assert(typ.n.sons[i].kind == nkSym) # if the parameter is lying (tyVar) and thus we required an additional deref, # skip the deref: @@ -382,7 +397,7 @@ proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType): Rope = result.add genOtherArg(p, ri, k, typ) result.add(~")") else: - localError(ri.info, "call expression expected for C++ pattern") + localError(p.config, ri.info, "call expression expected for C++ pattern") inc i elif pat[i+1] == '.': result.add genThisArg(p, ri, j, typ) @@ -420,7 +435,7 @@ proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) = assert(sonsLen(typ) == sonsLen(typ.n)) # don't call '$' here for efficiency: let pat = ri.sons[0].sym.loc.r.data - internalAssert pat != nil + internalAssert p.config, pat.len > 0 if pat.contains({'#', '(', '@', '\''}): var pl = genPatternCall(p, ri, pat, typ) # simpler version of 'fixupCall' that works with the pl+params combination: @@ -469,7 +484,7 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) = # don't call '$' here for efficiency: let pat = ri.sons[0].sym.loc.r.data - internalAssert pat != nil + internalAssert p.config, pat.len > 0 var start = 3 if ' ' in pat: start = 1 @@ -489,7 +504,7 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) = for i in countup(start, length-1): assert(sonsLen(typ) == sonsLen(typ.n)) if i >= sonsLen(typ): - internalError(ri.info, "varargs for objective C method?") + internalError(p.config, ri.info, "varargs for objective C method?") assert(typ.n.sons[i].kind == nkSym) var param = typ.n.sons[i].sym add(pl, ~" ") @@ -497,20 +512,20 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) = add(pl, ~": ") add(pl, genArg(p, ri.sons[i], param, ri)) if typ.sons[0] != nil: - if isInvalidReturnType(typ.sons[0]): + if isInvalidReturnType(p.config, typ.sons[0]): if sonsLen(ri) > 1: add(pl, ~" ") # beware of 'result = p(result)'. We always allocate a temporary: if d.k in {locTemp, locNone}: # We already got a temp. Great, special case it: if d.k == locNone: getTemp(p, typ.sons[0], d, needsInit=true) add(pl, ~"Result: ") - add(pl, addrLoc(d)) + add(pl, addrLoc(p.config, d)) add(pl, ~"];$n") line(p, cpsStmts, pl) else: var tmp: TLoc getTemp(p, typ.sons[0], tmp, needsInit=true) - add(pl, addrLoc(tmp)) + add(pl, addrLoc(p.config, tmp)) add(pl, ~"];$n") line(p, cpsStmts, pl) genAssignment(p, d, tmp, {}) # no need for deep copying diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index ba0820e93..56ecf5ba3 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -13,7 +13,7 @@ proc int64Literal(i: BiggestInt): Rope = if i > low(int64): - result = rfmt(nil, "IL64($1)", rope(i)) + result = "IL64($1)" % [rope(i)] else: result = ~"(IL64(-9223372036854775807) - IL64(1))" @@ -26,18 +26,12 @@ proc intLiteral(i: BiggestInt): Rope = # Nim has the same bug for the same reasons :-) result = ~"(-2147483647 -1)" elif i > low(int64): - result = rfmt(nil, "IL64($1)", rope(i)) + result = "IL64($1)" % [rope(i)] else: result = ~"(IL64(-9223372036854775807) - IL64(1))" -proc getStrLit(m: BModule, s: string): Rope = - discard cgsym(m, "TGenericSeq") - result = getTempName(m) - addf(m.s[cfsData], "STRING_LITERAL($1, $2, $3);$n", - [result, makeCString(s), rope(len(s))]) - proc genLiteral(p: BProc, n: PNode, ty: PType): Rope = - if ty == nil: internalError(n.info, "genLiteral: ty is nil") + if ty == nil: internalError(p.config, n.info, "genLiteral: ty is nil") case n.kind of nkCharLit..nkUInt64Lit: case skipTypes(ty, abstractVarRange).kind @@ -65,17 +59,17 @@ proc genLiteral(p: BProc, n: PNode, ty: PType): Rope = else: result = rope("NIM_NIL") of nkStrLit..nkTripleStrLit: - if n.strVal.isNil: - result = ropecg(p.module, "((#NimStringDesc*) NIM_NIL)", []) - elif skipTypes(ty, abstractVarRange).kind == tyString: - let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels) - if id == p.module.labels: - # string literal not found in the cache: - result = ropecg(p.module, "((#NimStringDesc*) &$1)", - [getStrLit(p.module, n.strVal)]) + case skipTypes(ty, abstractVarRange + {tyStatic}).kind + of tyNil: + result = genNilStringLiteral(p.module, n.info) + of tyString: + # with the new semantics for 'nil' strings, we can map "" to nil and + # save tons of allocations: + if n.strVal.len == 0 and optNilSeqs notin p.options and + p.config.selectedGc != gcDestructors: + result = genNilStringLiteral(p.module, n.info) else: - result = ropecg(p.module, "((#NimStringDesc*) &$1$2)", - [p.module.tmpBase, rope(id)]) + result = genStringLiteral(p.module, n) else: result = makeCString(n.strVal) of nkFloatLit, nkFloat64Lit: @@ -83,7 +77,7 @@ proc genLiteral(p: BProc, n: PNode, ty: PType): Rope = of nkFloat32Lit: result = rope(n.floatVal.toStrMaxPrecision("f")) else: - internalError(n.info, "genLiteral(" & $n.kind & ')') + internalError(p.config, n.info, "genLiteral(" & $n.kind & ')') result = nil proc genLiteral(p: BProc, n: PNode): Rope = @@ -121,8 +115,8 @@ proc genRawSetData(cs: TBitSet, size: int): Rope = proc genSetNode(p: BProc, n: PNode): Rope = var cs: TBitSet - var size = int(getSize(n.typ)) - toBitSet(n, cs) + var size = int(getSize(p.config, n.typ)) + toBitSet(p.config, n, cs) if size > 8: let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels) result = p.module.tmpBase & rope(id) @@ -152,7 +146,7 @@ proc getStorageLoc(n: PNode): TStorageLoc = of tyVar, tyLent: result = OnUnknown of tyPtr: result = OnStack of tyRef: result = OnHeap - else: internalError(n.info, "getStorageLoc") + else: doAssert(false, "getStorageLoc") of nkBracketExpr, nkDotExpr, nkObjDownConv, nkObjUpConv: result = getStorageLoc(n.sons[0]) else: result = OnUnknown @@ -171,7 +165,7 @@ proc canMove(n: PNode): bool = # result = false proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = - if dest.storage == OnStack or not usesNativeGC(): + if dest.storage == OnStack or not usesWriteBarrier(p.config): linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) elif dest.storage == OnHeap: # location is on heap @@ -192,13 +186,13 @@ proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = # lineF(p, cpsStmts, '$1 = $2;$n', [rdLoc(dest), rdLoc(src)]) if canFormAcycle(dest.t): linefmt(p, cpsStmts, "#asgnRef((void**) $1, $2);$n", - addrLoc(dest), rdLoc(src)) + addrLoc(p.config, dest), rdLoc(src)) else: linefmt(p, cpsStmts, "#asgnRefNoCycle((void**) $1, $2);$n", - addrLoc(dest), rdLoc(src)) + addrLoc(p.config, dest), rdLoc(src)) else: linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, $2);$n", - addrLoc(dest), rdLoc(src)) + addrLoc(p.config, dest), rdLoc(src)) proc asgnComplexity(n: PNode): int = if n != nil: @@ -261,19 +255,22 @@ proc genGenericAsgn(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = # tfShallow flag for the built-in string type too! So we check only # here for this flag, where it is reasonably safe to do so # (for objects, etc.): - if needToCopy notin flags or + if p.config.selectedGC == gcDestructors: + linefmt(p, cpsStmts, + "$1.len = $2.len; $1.p = $2.p;$n", + rdLoc(dest), rdLoc(src)) + elif needToCopy notin flags or tfShallow in skipTypes(dest.t, abstractVarRange).flags: - if dest.storage == OnStack or not usesNativeGC(): - useStringh(p.module) + if dest.storage == OnStack or not usesWriteBarrier(p.config): linefmt(p, cpsStmts, - "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", - addrLoc(dest), addrLoc(src), rdLoc(dest)) + "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", + addrLoc(p.config, dest), addrLoc(p.config, src), rdLoc(dest)) else: linefmt(p, cpsStmts, "#genericShallowAssign((void*)$1, (void*)$2, $3);$n", - addrLoc(dest), addrLoc(src), genTypeInfo(p.module, dest.t, dest.lode.info)) + addrLoc(p.config, dest), addrLoc(p.config, src), genTypeInfo(p.module, dest.t, dest.lode.info)) else: linefmt(p, cpsStmts, "#genericAssign((void*)$1, (void*)$2, $3);$n", - addrLoc(dest), addrLoc(src), genTypeInfo(p.module, dest.t, dest.lode.info)) + addrLoc(p.config, dest), addrLoc(p.config, src), genTypeInfo(p.module, dest.t, dest.lode.info)) proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = # This function replaces all other methods for generating @@ -282,22 +279,26 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = # little HACK to support the new 'var T' as return type: linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) return - let ty = skipTypes(dest.t, abstractRange + tyUserTypeClasses) + let ty = skipTypes(dest.t, abstractRange + tyUserTypeClasses + {tyStatic}) case ty.kind of tyRef: genRefAssign(p, dest, src, flags) of tySequence: - if (needToCopy notin flags and src.storage != OnStatic) or canMove(src.lode): + if p.config.selectedGC == gcDestructors: + genGenericAsgn(p, dest, src, flags) + elif (needToCopy notin flags and src.storage != OnStatic) or canMove(src.lode): genRefAssign(p, dest, src, flags) else: linefmt(p, cpsStmts, "#genericSeqAssign($1, $2, $3);$n", - addrLoc(dest), rdLoc(src), + addrLoc(p.config, dest), rdLoc(src), genTypeInfo(p.module, dest.t, dest.lode.info)) of tyString: - if (needToCopy notin flags and src.storage != OnStatic) or canMove(src.lode): + if p.config.selectedGC == gcDestructors: + genGenericAsgn(p, dest, src, flags) + elif (needToCopy notin flags and src.storage != OnStatic) or canMove(src.lode): genRefAssign(p, dest, src, flags) else: - if dest.storage == OnStack or not usesNativeGC(): + if dest.storage == OnStack or not usesWriteBarrier(p.config): linefmt(p, cpsStmts, "$1 = #copyString($2);$n", dest.rdLoc, src.rdLoc) elif dest.storage == OnHeap: # we use a temporary to care for the dreaded self assignment: @@ -308,7 +309,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = linefmt(p, cpsStmts, "if ($1) #nimGCunrefNoCycle($1);$n", tmp.rdLoc) else: linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, #copyString($2));$n", - addrLoc(dest), rdLoc(src)) + addrLoc(p.config, dest), rdLoc(src)) of tyProc: if needsComplexAssignment(dest.t): # optimize closure assignment: @@ -333,7 +334,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = elif needsComplexAssignment(ty): if ty.sons[0].isNil and asgnComplexity(ty.n) <= 4: discard getTypeDesc(p.module, ty) - internalAssert ty.n != nil + internalAssert p.config, ty.n != nil genOptAsgnObject(p, dest, src, flags, ty.n, ty) else: genGenericAsgn(p, dest, src, flags) @@ -343,9 +344,8 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = if needsComplexAssignment(dest.t): genGenericAsgn(p, dest, src, flags) else: - useStringh(p.module) linefmt(p, cpsStmts, - "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", + "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", rdLoc(dest), rdLoc(src), getTypeDesc(p.module, dest.t)) of tyOpenArray, tyVarargs: # open arrays are always on the stack - really? What if a sequence is @@ -353,31 +353,31 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = if needsComplexAssignment(dest.t): linefmt(p, cpsStmts, # XXX: is this correct for arrays? "#genericAssignOpenArray((void*)$1, (void*)$2, $1Len_0, $3);$n", - addrLoc(dest), addrLoc(src), + addrLoc(p.config, dest), addrLoc(p.config, src), genTypeInfo(p.module, dest.t, dest.lode.info)) else: - useStringh(p.module) linefmt(p, cpsStmts, - "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($1[0])*$1Len_0);$n", + # bug #4799, keep the nimCopyMem for a while + #"#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($1[0])*$1Len_0);$n", + "$1 = $2;$n", rdLoc(dest), rdLoc(src)) of tySet: - if mapType(ty) == ctArray: - useStringh(p.module) - linefmt(p, cpsStmts, "memcpy((void*)$1, (NIM_CONST void*)$2, $3);$n", - rdLoc(dest), rdLoc(src), rope(getSize(dest.t))) + if mapType(p.config, ty) == ctArray: + linefmt(p, cpsStmts, "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, $3);$n", + rdLoc(dest), rdLoc(src), rope(getSize(p.config, dest.t))) else: linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) of tyPtr, tyPointer, tyChar, tyBool, tyEnum, tyCString, tyInt..tyUInt64, tyRange, tyVar, tyLent: linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) - else: internalError("genAssignment: " & $ty.kind) + else: internalError(p.config, "genAssignment: " & $ty.kind) if optMemTracker in p.options and dest.storage in {OnHeap, OnUnknown}: #writeStackTrace() #echo p.currLineInfo, " requesting" linefmt(p, cpsStmts, "#memTrackerWrite((void*)$1, $2, $3, $4);$n", - addrLoc(dest), rope getSize(dest.t), - makeCString(p.currLineInfo.toFullPath), + addrLoc(p.config, dest), rope getSize(p.config, dest.t), + makeCString(toFullPath(p.config, p.currLineInfo)), rope p.currLineInfo.safeLineNm) proc genDeepCopy(p: BProc; dest, src: TLoc) = @@ -386,37 +386,36 @@ proc genDeepCopy(p: BProc; dest, src: TLoc) = var tmp: TLoc getTemp(p, a.t, tmp) genAssignment(p, tmp, a, {}) - addrLoc(tmp) + addrLoc(p.config, tmp) else: - addrLoc(a) + addrLoc(p.config, a) - var ty = skipTypes(dest.t, abstractVarRange) + var ty = skipTypes(dest.t, abstractVarRange + {tyStatic}) case ty.kind of tyPtr, tyRef, tyProc, tyTuple, tyObject, tyArray: # XXX optimize this linefmt(p, cpsStmts, "#genericDeepCopy((void*)$1, (void*)$2, $3);$n", - addrLoc(dest), addrLocOrTemp(src), + addrLoc(p.config, dest), addrLocOrTemp(src), genTypeInfo(p.module, dest.t, dest.lode.info)) of tySequence, tyString: linefmt(p, cpsStmts, "#genericSeqDeepCopy($1, $2, $3);$n", - addrLoc(dest), rdLoc(src), + addrLoc(p.config, dest), rdLoc(src), genTypeInfo(p.module, dest.t, dest.lode.info)) of tyOpenArray, tyVarargs: linefmt(p, cpsStmts, "#genericDeepCopyOpenArray((void*)$1, (void*)$2, $1Len_0, $3);$n", - addrLoc(dest), addrLocOrTemp(src), + addrLoc(p.config, dest), addrLocOrTemp(src), genTypeInfo(p.module, dest.t, dest.lode.info)) of tySet: - if mapType(ty) == ctArray: - useStringh(p.module) - linefmt(p, cpsStmts, "memcpy((void*)$1, (NIM_CONST void*)$2, $3);$n", - rdLoc(dest), rdLoc(src), rope(getSize(dest.t))) + if mapType(p.config, ty) == ctArray: + linefmt(p, cpsStmts, "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, $3);$n", + rdLoc(dest), rdLoc(src), rope(getSize(p.config, dest.t))) else: linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) of tyPointer, tyChar, tyBool, tyEnum, tyCString, tyInt..tyUInt64, tyRange, tyVar, tyLent: linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) - else: internalError("genDeepCopy: " & $ty.kind) + else: internalError(p.config, "genDeepCopy: " & $ty.kind) proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc) = if d.k != locNone: @@ -457,14 +456,21 @@ proc putIntoDest(p: BProc, d: var TLoc, n: PNode, r: Rope; s=OnUnknown) = proc binaryStmt(p: BProc, e: PNode, d: var TLoc, frmt: string) = var a, b: TLoc - if d.k != locNone: internalError(e.info, "binaryStmt") + if d.k != locNone: internalError(p.config, e.info, "binaryStmt") initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) lineCg(p, cpsStmts, frmt, rdLoc(a), rdLoc(b)) +proc binaryStmtAddr(p: BProc, e: PNode, d: var TLoc, frmt: string) = + var a, b: TLoc + if d.k != locNone: internalError(p.config, e.info, "binaryStmtAddr") + initLocExpr(p, e.sons[1], a) + initLocExpr(p, e.sons[2], b) + lineCg(p, cpsStmts, frmt, addrLoc(p.config, a), rdLoc(b)) + proc unaryStmt(p: BProc, e: PNode, d: var TLoc, frmt: string) = var a: TLoc - if d.k != locNone: internalError(e.info, "unaryStmt") + if d.k != locNone: internalError(p.config, e.info, "unaryStmt") initLocExpr(p, e.sons[1], a) lineCg(p, cpsStmts, frmt, [rdLoc(a)]) @@ -496,15 +502,15 @@ proc unaryExprChar(p: BProc, e: PNode, d: var TLoc, frmt: string) = proc binaryArithOverflowRaw(p: BProc, t: PType, a, b: TLoc; frmt: string): Rope = - var size = getSize(t) - let storage = if size < platform.intSize: rope("NI") + var size = getSize(p.config, t) + let storage = if size < p.config.target.intSize: rope("NI") else: getTypeDesc(p.module, t) result = getTempName(p.module) linefmt(p, cpsLocals, "$1 $2;$n", storage, result) lineCg(p, cpsStmts, frmt, result, rdCharLoc(a), rdCharLoc(b)) - if size < platform.intSize or t.kind in {tyRange, tyEnum}: + if size < p.config.target.intSize or t.kind in {tyRange, tyEnum}: linefmt(p, cpsStmts, "if ($1 < $2 || $1 > $3) #raiseOverflow();$n", - result, intLiteral(firstOrd(t)), intLiteral(lastOrd(t))) + result, intLiteral(firstOrd(p.config, t)), intLiteral(lastOrd(p.config, t))) proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = const @@ -552,8 +558,8 @@ proc unaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = t = skipTypes(e.typ, abstractRange) if optOverflowCheck in p.options: linefmt(p, cpsStmts, "if ($1 == $2) #raiseOverflow();$n", - rdLoc(a), intLiteral(firstOrd(t))) - putIntoDest(p, d, e, opr[m] % [rdLoc(a), rope(getSize(t) * 8)]) + rdLoc(a), intLiteral(firstOrd(p.config, t))) + putIntoDest(p, d, e, opr[m] % [rdLoc(a), rope(getSize(p.config, t) * 8)]) proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = const @@ -562,9 +568,9 @@ proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = "(($4)($1) - ($4)($2))", # SubF64 "(($4)($1) * ($4)($2))", # MulF64 "(($4)($1) / ($4)($2))", # DivF64 - "($4)((NU$5)($1) >> (NU$3)($2))", # ShrI "($4)((NU$3)($1) << (NU$3)($2))", # ShlI + "($4)((NI$3)($1) >> (NU$3)($2))", # AshrI "($4)($1 & $2)", # BitandI "($4)($1 | $2)", # BitorI "($4)($1 ^ $2)", # BitxorI @@ -609,8 +615,8 @@ proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) # BUGFIX: cannot use result-type here, as it may be a boolean - s = max(getSize(a.t), getSize(b.t)) * 8 - k = getSize(a.t) * 8 + s = max(getSize(p.config, a.t), getSize(p.config, b.t)) * 8 + k = getSize(p.config, a.t) * 8 putIntoDest(p, d, e, binArithTab[op] % [rdLoc(a), rdLoc(b), rope(s), getSimpleTypeDesc(p.module, e.typ), rope(k)]) @@ -663,7 +669,7 @@ proc unaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = initLocExpr(p, e.sons[1], a) t = skipTypes(e.typ, abstractRange) putIntoDest(p, d, e, - unArithTab[op] % [rdLoc(a), rope(getSize(t) * 8), + unArithTab[op] % [rdLoc(a), rope(getSize(p.config, t) * 8), getSimpleTypeDesc(p.module, e.typ)]) proc isCppRef(p: BProc; typ: PType): bool {.inline.} = @@ -672,7 +678,7 @@ proc isCppRef(p: BProc; typ: PType): bool {.inline.} = tfVarIsPtr notin skipTypes(typ, abstractInst).flags proc genDeref(p: BProc, e: PNode, d: var TLoc; enforceDeref=false) = - let mt = mapType(e.sons[0].typ) + let mt = mapType(p.config, e.sons[0].typ) if mt in {ctArray, ctPtrToArray} and not enforceDeref: # XXX the amount of hacks for C's arrays is incredible, maybe we should # simply wrap them in a struct? --> Losing auto vectorization then? @@ -708,7 +714,7 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc; enforceDeref=false) = of tyPtr: d.storage = OnUnknown # BUGFIX! else: - internalError(e.info, "genDeref " & $typ.kind) + internalError(p.config, e.info, "genDeref " & $typ.kind) elif p.module.compileToCpp: if typ.kind == tyVar and tfVarIsPtr notin typ.flags and e.kind == nkHiddenDeref: @@ -731,19 +737,19 @@ proc genAddr(p: BProc, e: PNode, d: var TLoc) = initLocExpr(p, e.sons[0], a) putIntoDest(p, d, e, "&" & a.r, a.storage) #Message(e.info, warnUser, "HERE NEW &") - elif mapType(e.sons[0].typ) == ctArray or isCppRef(p, e.sons[0].typ): + elif mapType(p.config, e.sons[0].typ) == ctArray or isCppRef(p, e.sons[0].typ): expr(p, e.sons[0], d) else: var a: TLoc initLocExpr(p, e.sons[0], a) - putIntoDest(p, d, e, addrLoc(a), a.storage) + putIntoDest(p, d, e, addrLoc(p.config, a), a.storage) template inheritLocation(d: var TLoc, a: TLoc) = if d.k == locNone: d.storage = a.storage proc genRecordFieldAux(p: BProc, e: PNode, d, a: var TLoc) = initLocExpr(p, e.sons[0], a) - if e.sons[1].kind != nkSym: internalError(e.info, "genRecordFieldAux") + if e.sons[1].kind != nkSym: internalError(p.config, e.info, "genRecordFieldAux") d.inheritLocation(a) discard getTypeDesc(p.module, a.t) # fill the record's fields.loc @@ -759,7 +765,7 @@ proc genTupleElem(p: BProc, e: PNode, d: var TLoc) = var r = rdLoc(a) case e.sons[1].kind of nkIntLit..nkUInt64Lit: i = int(e.sons[1].intVal) - else: internalError(e.info, "genTupleElem") + else: internalError(p.config, e.info, "genTupleElem") addf(r, ".Field$1", [rope(i)]) putIntoDest(p, d, e, r, a.storage) @@ -776,7 +782,7 @@ proc lookupFieldAgain(p: BProc, ty: PType; field: PSym; r: var Rope; break if not p.module.compileToCpp: add(r, ".Sup") ty = ty.sons[0] - if result == nil: internalError(field.info, "genCheckedRecordField") + if result == nil: internalError(p.config, field.info, "genCheckedRecordField") proc genRecordField(p: BProc, e: PNode, d: var TLoc) = var a: TLoc @@ -793,7 +799,7 @@ proc genRecordField(p: BProc, e: PNode, d: var TLoc) = var rtyp: PType let field = lookupFieldAgain(p, ty, f, r, addr rtyp) if field.loc.r == nil and rtyp != nil: fillObjectFields(p.module, rtyp) - if field.loc.r == nil: internalError(e.info, "genRecordField 3 " & typeToString(ty)) + if field.loc.r == nil: internalError(p.config, e.info, "genRecordField 3 " & typeToString(ty)) addf(r, ".$1", [field.loc.r]) putIntoDest(p, d, e, r, a.storage) @@ -818,30 +824,30 @@ proc genFieldCheck(p: BProc, e: PNode, obj: Rope, field: PSym) = genInExprAux(p, it, u, v, test) let id = nodeTableTestOrSet(p.module.dataCache, newStrNode(nkStrLit, field.name.s), p.module.labels) - let strLit = if id == p.module.labels: getStrLit(p.module, field.name.s) + let strLit = if id == p.module.labels: genStringLiteralDataOnly(p.module, field.name.s, e.info) else: p.module.tmpBase & rope(id) if op.magic == mNot: linefmt(p, cpsStmts, - "if ($1) #raiseFieldError(((#NimStringDesc*) &$2));$n", - rdLoc(test), strLit) + "if ($1) #raiseFieldError($2);$n", + rdLoc(test), genStringLiteralFromData(p.module, strLit, e.info)) else: linefmt(p, cpsStmts, - "if (!($1)) #raiseFieldError(((#NimStringDesc*) &$2));$n", - rdLoc(test), strLit) + "if (!($1)) #raiseFieldError($2);$n", + rdLoc(test), genStringLiteralFromData(p.module, strLit, e.info)) proc genCheckedRecordField(p: BProc, e: PNode, d: var TLoc) = if optFieldCheck in p.options: var a: TLoc genRecordFieldAux(p, e.sons[0], d, a) - let ty = skipTypes(a.t, abstractInst) + let ty = skipTypes(a.t, abstractInst + tyUserTypeClasses) var r = rdLoc(a) let f = e.sons[0].sons[1].sym let field = lookupFieldAgain(p, ty, f, r) if field.loc.r == nil: fillObjectFields(p.module, ty) if field.loc.r == nil: - internalError(e.info, "genCheckedRecordField") # generate the checks: + internalError(p.config, e.info, "genCheckedRecordField") # generate the checks: genFieldCheck(p, e, r, field) - add(r, rfmt(nil, ".$1", field.loc.r)) + add(r, ropecg(p.module, ".$1", field.loc.r)) putIntoDest(p, d, e.sons[0], r, a.storage) else: genRecordField(p, e.sons[0], d) @@ -851,25 +857,25 @@ proc genArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = initLocExpr(p, x, a) initLocExpr(p, y, b) var ty = skipTypes(a.t, abstractVarRange + abstractPtrs + tyUserTypeClasses) - var first = intLiteral(firstOrd(ty)) + var first = intLiteral(firstOrd(p.config, ty)) # emit range check: if optBoundsCheck in p.options and tfUncheckedArray notin ty.flags: if not isConstExpr(y): # semantic pass has already checked for const index expressions - if firstOrd(ty) == 0: - if (firstOrd(b.t) < firstOrd(ty)) or (lastOrd(b.t) > lastOrd(ty)): + if firstOrd(p.config, ty) == 0: + if (firstOrd(p.config, b.t) < firstOrd(p.config, ty)) or (lastOrd(p.config, b.t) > lastOrd(p.config, ty)): linefmt(p, cpsStmts, "if ((NU)($1) > (NU)($2)) #raiseIndexError();$n", - rdCharLoc(b), intLiteral(lastOrd(ty))) + rdCharLoc(b), intLiteral(lastOrd(p.config, ty))) else: linefmt(p, cpsStmts, "if ($1 < $2 || $1 > $3) #raiseIndexError();$n", - rdCharLoc(b), first, intLiteral(lastOrd(ty))) + rdCharLoc(b), first, intLiteral(lastOrd(p.config, ty))) else: let idx = getOrdValue(y) - if idx < firstOrd(ty) or idx > lastOrd(ty): - localError(x.info, errIndexOutOfBounds) + if idx < firstOrd(p.config, ty) or idx > lastOrd(p.config, ty): + localError(p.config, x.info, "index out of bounds") d.inheritLocation(a) putIntoDest(p, d, n, - rfmt(nil, "$1[($2)- $3]", rdLoc(a), rdCharLoc(b), first), a.storage) + ropecg(p.module, "$1[($2)- $3]", rdLoc(a), rdCharLoc(b), first), a.storage) proc genCStringElem(p: BProc, n, x, y: PNode, d: var TLoc) = var a, b: TLoc @@ -878,7 +884,29 @@ proc genCStringElem(p: BProc, n, x, y: PNode, d: var TLoc) = var ty = skipTypes(a.t, abstractVarRange) inheritLocation(d, a) putIntoDest(p, d, n, - rfmt(nil, "$1[$2]", rdLoc(a), rdCharLoc(b)), a.storage) + ropecg(p.module, "$1[$2]", rdLoc(a), rdCharLoc(b)), a.storage) + +proc genBoundsCheck(p: BProc; arr, a, b: TLoc) = + let ty = skipTypes(arr.t, abstractVarRange) + case ty.kind + of tyOpenArray, tyVarargs: + linefmt(p, cpsStmts, + "if ($2-$1 != -1 && " & + "((NU)($1) >= (NU)($3Len_0) || (NU)($2) >= (NU)($3Len_0))) #raiseIndexError();$n", + rdLoc(a), rdLoc(b), rdLoc(arr)) + of tyArray: + let first = intLiteral(firstOrd(p.config, ty)) + if tfUncheckedArray notin ty.flags: + linefmt(p, cpsStmts, + "if ($2-$1 != -1 && " & + "($2-$1 < -1 || $1 < $3 || $1 > $4 || $2 < $3 || $2 > $4)) #raiseIndexError();$n", + rdCharLoc(a), rdCharLoc(b), first, intLiteral(lastOrd(p.config, ty))) + of tySequence, tyString: + linefmt(p, cpsStmts, + "if ($2-$1 != -1 && " & + "((NU)($1) >= (NU)$3 || (NU)($2) >= (NU)$3)) #raiseIndexError();$n", + rdLoc(a), rdLoc(b), lenExpr(p, arr)) + else: discard proc genOpenArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = var a, b: TLoc @@ -889,7 +917,7 @@ proc genOpenArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = rdLoc(b), rdLoc(a)) # BUGFIX: ``>=`` and not ``>``! inheritLocation(d, a) putIntoDest(p, d, n, - rfmt(nil, "$1[$2]", rdLoc(a), rdCharLoc(b)), a.storage) + ropecg(p.module, "$1[$2]", rdLoc(a), rdCharLoc(b)), a.storage) proc genSeqElem(p: BProc, n, x, y: PNode, d: var TLoc) = var a, b: TLoc @@ -899,19 +927,19 @@ proc genSeqElem(p: BProc, n, x, y: PNode, d: var TLoc) = if ty.kind in {tyRef, tyPtr}: ty = skipTypes(ty.lastSon, abstractVarRange) # emit range check: if optBoundsCheck in p.options: - if ty.kind == tyString: + if ty.kind == tyString and (not defined(nimNoZeroTerminator) or optLaxStrings in p.options): linefmt(p, cpsStmts, - "if ((NU)($1) > (NU)($2->$3)) #raiseIndexError();$n", - rdLoc(b), rdLoc(a), lenField(p)) + "if ((NU)($1) > (NU)$2) #raiseIndexError();$n", + rdLoc(b), lenExpr(p, a)) else: linefmt(p, cpsStmts, - "if ((NU)($1) >= (NU)($2->$3)) #raiseIndexError();$n", - rdLoc(b), rdLoc(a), lenField(p)) + "if ((NU)($1) >= (NU)$2) #raiseIndexError();$n", + rdLoc(b), lenExpr(p, a)) if d.k == locNone: d.storage = OnHeap if skipTypes(a.t, abstractVar).kind in {tyRef, tyPtr}: - a.r = rfmt(nil, "(*$1)", a.r) + a.r = ropecg(p.module, "(*$1)", a.r) putIntoDest(p, d, n, - rfmt(nil, "$1->data[$2]", rdLoc(a), rdCharLoc(b)), a.storage) + ropecg(p.module, "$1$3[$2]", rdLoc(a), rdCharLoc(b), dataField(p)), a.storage) proc genBracketExpr(p: BProc; n: PNode; d: var TLoc) = var ty = skipTypes(n.sons[0].typ, abstractVarRange + tyUserTypeClasses) @@ -922,7 +950,7 @@ proc genBracketExpr(p: BProc; n: PNode; d: var TLoc) = of tySequence, tyString: genSeqElem(p, n, n.sons[0], n.sons[1], d) of tyCString: genCStringElem(p, n, n.sons[0], n.sons[1], d) of tyTuple: genTupleElem(p, n, d) - else: internalError(n.info, "expr(nkBracketExpr, " & $ty.kind & ')') + else: internalError(p.config, n.info, "expr(nkBracketExpr, " & $ty.kind & ')') proc genAndOr(p: BProc, e: PNode, d: var TLoc, m: TMagic) = # how to generate code? @@ -967,18 +995,19 @@ proc genAndOr(p: BProc, e: PNode, d: var TLoc, m: TMagic) = proc genEcho(p: BProc, n: PNode) = # this unusal way of implementing it ensures that e.g. ``echo("hallo", 45)`` # is threadsafe. - internalAssert n.kind == nkBracket - if platform.targetOS == osGenode: - # bypass libc and print directly to the Genode LOG session + internalAssert p.config, n.kind == nkBracket + if p.config.target.targetOS == osGenode: + # echo directly to the Genode LOG session var args: Rope = nil var a: TLoc - for i in countup(0, n.len-1): - if n.sons[i].skipConv.kind == nkNilLit: - add(args, ", \"nil\"") + for it in n.sons: + if it.skipConv.kind == nkNilLit: + add(args, ", \"\"") else: - initLocExpr(p, n.sons[i], a) - addf(args, ", $1? ($1)->data:\"nil\"", [rdLoc(a)]) + initLocExpr(p, it, a) + add(args, ropecg(p.module, ", Genode::Cstring($1->data, $1->len)", [rdLoc(a)])) p.module.includeHeader("<base/log.h>") + p.module.includeHeader("<util/string.h>") linefmt(p, cpsStmts, """Genode::log(""$1);$n""", args) else: if n.len == 0: @@ -990,11 +1019,17 @@ proc genEcho(p: BProc, n: PNode) = when false: p.module.includeHeader("<stdio.h>") linefmt(p, cpsStmts, "printf($1$2);$n", - makeCString(repeat("%s", n.len) & tnl), args) + makeCString(repeat("%s", n.len) & "\L"), args) linefmt(p, cpsStmts, "fflush(stdout);$n") -proc gcUsage(n: PNode) = - if gSelectedGC == gcNone: message(n.info, warnGcMem, n.renderTree) +proc gcUsage(conf: ConfigRef; n: PNode) = + if conf.selectedGC == gcNone: message(conf, n.info, warnGcMem, n.renderTree) + +proc strLoc(p: BProc; d: TLoc): Rope = + if p.config.selectedGc == gcDestructors: + result = addrLoc(p.config, d) + else: + result = rdLoc(d) proc genStrConcat(p: BProc, e: PNode, d: var TLoc) = # <Nim code> @@ -1023,20 +1058,21 @@ proc genStrConcat(p: BProc, e: PNode, d: var TLoc) = initLocExpr(p, e.sons[i + 1], a) if skipTypes(e.sons[i + 1].typ, abstractVarRange).kind == tyChar: inc(L) - add(appends, rfmt(p.module, "#appendChar($1, $2);$n", tmp.r, rdLoc(a))) + add(appends, ropecg(p.module, "#appendChar($1, $2);$n", strLoc(p, tmp), rdLoc(a))) else: if e.sons[i + 1].kind in {nkStrLit..nkTripleStrLit}: inc(L, len(e.sons[i + 1].strVal)) else: - addf(lens, "$1->$2 + ", [rdLoc(a), lenField(p)]) - add(appends, rfmt(p.module, "#appendString($1, $2);$n", tmp.r, rdLoc(a))) + add(lens, lenExpr(p, a)) + add(lens, " + ") + add(appends, ropecg(p.module, "#appendString($1, $2);$n", strLoc(p, tmp), rdLoc(a))) linefmt(p, cpsStmts, "$1 = #rawNewString($2$3);$n", tmp.r, lens, rope(L)) add(p.s(cpsStmts), appends) if d.k == locNone: d = tmp else: genAssignment(p, d, tmp, {}) # no need for deep copying - gcUsage(e) + gcUsage(p.config, e) proc genStrAppend(p: BProc, e: PNode, d: var TLoc) = # <Nim code> @@ -1051,7 +1087,7 @@ proc genStrAppend(p: BProc, e: PNode, d: var TLoc) = # appendChar(s, 'z'); # } var - a, dest: TLoc + a, dest, call: TLoc appends, lens: Rope assert(d.k == locNone) var L = 0 @@ -1061,50 +1097,59 @@ proc genStrAppend(p: BProc, e: PNode, d: var TLoc) = initLocExpr(p, e.sons[i + 2], a) if skipTypes(e.sons[i + 2].typ, abstractVarRange).kind == tyChar: inc(L) - add(appends, rfmt(p.module, "#appendChar($1, $2);$n", - rdLoc(dest), rdLoc(a))) + add(appends, ropecg(p.module, "#appendChar($1, $2);$n", + strLoc(p, dest), rdLoc(a))) else: if e.sons[i + 2].kind in {nkStrLit..nkTripleStrLit}: inc(L, len(e.sons[i + 2].strVal)) else: - addf(lens, "$1->$2 + ", [rdLoc(a), lenField(p)]) - add(appends, rfmt(p.module, "#appendString($1, $2);$n", - rdLoc(dest), rdLoc(a))) - linefmt(p, cpsStmts, "$1 = #resizeString($1, $2$3);$n", - rdLoc(dest), lens, rope(L)) + add(lens, lenExpr(p, a)) + add(lens, " + ") + add(appends, ropecg(p.module, "#appendString($1, $2);$n", + strLoc(p, dest), rdLoc(a))) + if p.config.selectedGC == gcDestructors: + linefmt(p, cpsStmts, "#prepareAdd($1, $2$3);$n", + addrLoc(p.config, dest), lens, rope(L)) + else: + initLoc(call, locCall, e, OnHeap) + call.r = ropecg(p.module, "#resizeString($1, $2$3)", [rdLoc(dest), lens, rope(L)]) + genAssignment(p, dest, call, {}) + gcUsage(p.config, e) add(p.s(cpsStmts), appends) - gcUsage(e) proc genSeqElemAppend(p: BProc, e: PNode, d: var TLoc) = # seq &= x --> # seq = (typeof seq) incrSeq(&seq->Sup, sizeof(x)); # seq->data[seq->len-1] = x; let seqAppendPattern = if not p.module.compileToCpp: - "$1 = ($2) #incrSeqV2(&($1)->Sup, sizeof($3));$n" + "($2) #incrSeqV3(&($1)->Sup, $3)" else: - "$1 = ($2) #incrSeqV2($1, sizeof($3));$n" - var a, b, dest, tmpL: TLoc + "($2) #incrSeqV3($1, $3)" + var a, b, dest, tmpL, call: TLoc initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) - let bt = skipTypes(e.sons[2].typ, {tyVar}) - lineCg(p, cpsStmts, seqAppendPattern, [ - rdLoc(a), - getTypeDesc(p.module, e.sons[1].typ), - getTypeDesc(p.module, bt)]) + let seqType = skipTypes(e.sons[1].typ, {tyVar}) + initLoc(call, locCall, e, OnHeap) + call.r = ropecg(p.module, seqAppendPattern, [rdLoc(a), + getTypeDesc(p.module, e.sons[1].typ), + genTypeInfo(p.module, seqType, e.info)]) + # emit the write barrier if required, but we can always move here, so + # use 'genRefAssign' for the seq. + genRefAssign(p, a, call, {}) #if bt != b.t: # echo "YES ", e.info, " new: ", typeToString(bt), " old: ", typeToString(b.t) initLoc(dest, locExpr, e.sons[2], OnHeap) getIntTemp(p, tmpL) lineCg(p, cpsStmts, "$1 = $2->$3++;$n", tmpL.r, rdLoc(a), lenField(p)) - dest.r = rfmt(nil, "$1->data[$2]", rdLoc(a), tmpL.r) + dest.r = ropecg(p.module, "$1$3[$2]", rdLoc(a), tmpL.r, dataField(p)) genAssignment(p, dest, b, {needToCopy, afDestIsNil}) - gcUsage(e) + gcUsage(p.config, e) proc genReset(p: BProc, n: PNode) = var a: TLoc initLocExpr(p, n.sons[1], a) linefmt(p, cpsStmts, "#genericReset((void*)$1, $2);$n", - addrLoc(a), + addrLoc(p.config, a), genTypeInfo(p.module, skipTypes(a.t, {tyVar}), n.info)) proc rawGenNew(p: BProc, a: TLoc, sizeExpr: Rope) = @@ -1118,10 +1163,22 @@ proc rawGenNew(p: BProc, a: TLoc, sizeExpr: Rope) = if sizeExpr.isNil: sizeExpr = "sizeof($1)" % [getTypeDesc(p.module, bt)] - let args = [getTypeDesc(p.module, typ), - genTypeInfo(p.module, typ, a.lode.info), - sizeExpr] - if a.storage == OnHeap and usesNativeGC(): + + let ti = genTypeInfo(p.module, typ, a.lode.info) + if bt.destructor != nil: + # the prototype of a destructor is ``=destroy(x: var T)`` and that of a + # finalizer is: ``proc (x: ref T) {.nimcall.}``. We need to check the calling + # convention at least: + if bt.destructor.typ == nil or bt.destructor.typ.callConv != ccDefault: + localError(p.module.config, a.lode.info, + "the destructor that is turned into a finalizer needs " & + "to have the 'nimcall' calling convention") + var f: TLoc + initLocExpr(p, newSymNode(bt.destructor), f) + addf(p.module.s[cfsTypeInit3], "$1->finalizer = (void*)$2;$n", [ti, rdLoc(f)]) + + let args = [getTypeDesc(p.module, typ), ti, sizeExpr] + if a.storage == OnHeap and usesWriteBarrier(p.config): # use newObjRC1 as an optimization if canFormAcycle(a.t): linefmt(p, cpsStmts, "if ($1) { #nimGCunrefRC1($1); $1 = NIM_NIL; }$n", a.rdLoc) @@ -1144,31 +1201,43 @@ proc genNew(p: BProc, e: PNode) = rawGenNew(p, a, se.rdLoc) else: rawGenNew(p, a, nil) - gcUsage(e) + gcUsage(p.config, e) -proc genNewSeqAux(p: BProc, dest: TLoc, length: Rope) = +proc genNewSeqAux(p: BProc, dest: TLoc, length: Rope; lenIsZero: bool) = let seqtype = skipTypes(dest.t, abstractVarRange) let args = [getTypeDesc(p.module, seqtype), genTypeInfo(p.module, seqtype, dest.lode.info), length] var call: TLoc initLoc(call, locExpr, dest.lode, OnHeap) - if dest.storage == OnHeap and usesNativeGC(): + if dest.storage == OnHeap and usesWriteBarrier(p.config): if canFormAcycle(dest.t): linefmt(p, cpsStmts, "if ($1) { #nimGCunrefRC1($1); $1 = NIM_NIL; }$n", dest.rdLoc) else: linefmt(p, cpsStmts, "if ($1) { #nimGCunrefNoCycle($1); $1 = NIM_NIL; }$n", dest.rdLoc) - call.r = ropecg(p.module, "($1) #newSeqRC1($2, $3)", args) - linefmt(p, cpsStmts, "$1 = $2;$n", dest.rdLoc, call.rdLoc) + if not lenIsZero: + call.r = ropecg(p.module, "($1) #newSeqRC1($2, $3)", args) + linefmt(p, cpsStmts, "$1 = $2;$n", dest.rdLoc, call.rdLoc) else: - call.r = ropecg(p.module, "($1) #newSeq($2, $3)", args) + if lenIsZero: + call.r = rope"NIM_NIL" + else: + call.r = ropecg(p.module, "($1) #newSeq($2, $3)", args) genAssignment(p, dest, call, {}) proc genNewSeq(p: BProc, e: PNode) = var a, b: TLoc initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) - genNewSeqAux(p, a, b.rdLoc) - gcUsage(e) + if p.config.selectedGC == gcDestructors: + let seqtype = skipTypes(e.sons[1].typ, abstractVarRange) + linefmt(p, cpsStmts, "$1.len = $2; $1.p = ($4*) #newSeqPayload($2, sizeof($3));$n", + a.rdLoc, b.rdLoc, getTypeDesc(p.module, seqtype.lastSon), + getSeqPayloadType(p.module, seqtype)) + else: + let lenIsZero = optNilSeqs notin p.options and + e[2].kind == nkIntLit and e[2].intVal == 0 + genNewSeqAux(p, a, b.rdLoc, lenIsZero) + gcUsage(p.config, e) proc genNewSeqOfCap(p: BProc; e: PNode; d: var TLoc) = let seqtype = skipTypes(e.typ, abstractVarRange) @@ -1178,7 +1247,7 @@ proc genNewSeqOfCap(p: BProc; e: PNode; d: var TLoc) = "($1)#nimNewSeqOfCap($2, $3)", [ getTypeDesc(p.module, seqtype), genTypeInfo(p.module, seqtype, e.info), a.rdLoc])) - gcUsage(e) + gcUsage(p.config, e) proc genConstExpr(p: BProc, n: PNode): Rope proc handleConstExpr(p: BProc, n: PNode, d: var TLoc): bool = @@ -1220,7 +1289,7 @@ proc genObjConstr(p: BProc, e: PNode, d: var TLoc) = rawGenNew(p, tmp, nil) t = t.lastSon.skipTypes(abstractInst) r = "(*$1)" % [r] - gcUsage(e) + gcUsage(p.config, e) else: constructLoc(p, tmp) else: @@ -1234,7 +1303,7 @@ proc genObjConstr(p: BProc, e: PNode, d: var TLoc) = tmp2.r = r let field = lookupFieldAgain(p, ty, it.sons[0].sym, tmp2.r) if field.loc.r == nil: fillObjectFields(p.module, ty) - if field.loc.r == nil: internalError(e.info, "genObjConstr") + if field.loc.r == nil: internalError(p.config, e.info, "genObjConstr") if it.len == 3 and optFieldCheck in p.options: genFieldCheck(p, it.sons[2], r, field) add(tmp2.r, ".") @@ -1267,13 +1336,14 @@ proc genSeqConstr(p: BProc, n: PNode, d: var TLoc) = elif d.k == locNone: getTemp(p, n.typ, d) # generate call to newSeq before adding the elements per hand: - genNewSeqAux(p, dest[], intLiteral(sonsLen(n))) + genNewSeqAux(p, dest[], intLiteral(sonsLen(n)), + optNilSeqs notin p.options and n.len == 0) for i in countup(0, sonsLen(n) - 1): initLoc(arr, locExpr, n[i], OnHeap) - arr.r = rfmt(nil, "$1->data[$2]", rdLoc(dest[]), intLiteral(i)) + arr.r = ropecg(p.module, "$1$3[$2]", rdLoc(dest[]), intLiteral(i), dataField(p)) arr.storage = OnHeap # we know that sequences are on the heap expr(p, n[i], arr) - gcUsage(n) + gcUsage(p.config, n) if doesAlias: if d.k == locNone: d = tmp @@ -1289,28 +1359,28 @@ proc genArrToSeq(p: BProc, n: PNode, d: var TLoc) = if d.k == locNone: getTemp(p, n.typ, d) # generate call to newSeq before adding the elements per hand: - let L = int(lengthOrd(n.sons[1].typ)) - genNewSeqAux(p, d, intLiteral(L)) + let L = int(lengthOrd(p.config, n.sons[1].typ)) + genNewSeqAux(p, d, intLiteral(L), optNilSeqs notin p.options and L == 0) initLocExpr(p, n.sons[1], a) # bug #5007; do not produce excessive C source code: if L < 10: for i in countup(0, L - 1): initLoc(elem, locExpr, lodeTyp elemType(skipTypes(n.typ, abstractInst)), OnHeap) - elem.r = rfmt(nil, "$1->data[$2]", rdLoc(d), intLiteral(i)) + elem.r = ropecg(p.module, "$1$3[$2]", rdLoc(d), intLiteral(i), dataField(p)) elem.storage = OnHeap # we know that sequences are on the heap initLoc(arr, locExpr, lodeTyp elemType(skipTypes(n.sons[1].typ, abstractInst)), a.storage) - arr.r = rfmt(nil, "$1[$2]", rdLoc(a), intLiteral(i)) + arr.r = ropecg(p.module, "$1[$2]", rdLoc(a), intLiteral(i)) genAssignment(p, elem, arr, {afDestIsNil, needToCopy}) else: var i: TLoc - getTemp(p, getSysType(tyInt), i) + getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), i) let oldCode = p.s(cpsStmts) linefmt(p, cpsStmts, "for ($1 = 0; $1 < $2; $1++) {$n", i.r, L.rope) initLoc(elem, locExpr, lodeTyp elemType(skipTypes(n.typ, abstractInst)), OnHeap) - elem.r = rfmt(nil, "$1->data[$2]", rdLoc(d), rdLoc(i)) + elem.r = ropecg(p.module, "$1$3[$2]", rdLoc(d), rdLoc(i), dataField(p)) elem.storage = OnHeap # we know that sequences are on the heap initLoc(arr, locExpr, lodeTyp elemType(skipTypes(n.sons[1].typ, abstractInst)), a.storage) - arr.r = rfmt(nil, "$1[$2]", rdLoc(a), rdLoc(i)) + arr.r = ropecg(p.module, "$1[$2]", rdLoc(a), rdLoc(i)) genAssignment(p, elem, arr, {afDestIsNil, needToCopy}) lineF(p, cpsStmts, "}$n", []) @@ -1332,7 +1402,7 @@ proc genNewFinalize(p: BProc, e: PNode) = genAssignment(p, a, b, {}) # set the object type: bt = skipTypes(refType.lastSon, abstractRange) genObjectInit(p, cpsStmts, bt, a, false) - gcUsage(e) + gcUsage(p.config, e) proc genOfHelper(p: BProc; dest: PType; a: Rope; info: TLineInfo): Rope = # unfortunately 'genTypeInfo' sets tfObjHasKids as a side effect, so we @@ -1346,10 +1416,10 @@ proc genOfHelper(p: BProc; dest: PType; a: Rope; info: TLineInfo): Rope = inc p.module.labels let cache = "Nim_OfCheck_CACHE" & p.module.labels.rope addf(p.module.s[cfsVars], "static TNimType* $#[2];$n", [cache]) - result = rfmt(p.module, "#isObjWithCache($#.m_type, $#, $#)", a, ti, cache) + result = ropecg(p.module, "#isObjWithCache($#.m_type, $#, $#)", a, ti, cache) when false: # former version: - result = rfmt(p.module, "#isObj($1.m_type, $2)", + result = ropecg(p.module, "#isObj($1.m_type, $2)", a, genTypeInfo(p.module, dest, info)) proc genOf(p: BProc, x: PNode, typ: PType, d: var TLoc) = @@ -1362,19 +1432,20 @@ proc genOf(p: BProc, x: PNode, typ: PType, d: var TLoc) = while t.kind in {tyVar, tyLent, tyPtr, tyRef}: if t.kind notin {tyVar, tyLent}: nilCheck = r if t.kind notin {tyVar, tyLent} or not p.module.compileToCpp: - r = rfmt(nil, "(*$1)", r) + r = ropecg(p.module, "(*$1)", r) t = skipTypes(t.lastSon, typedescInst) + discard getTypeDesc(p.module, t) if not p.module.compileToCpp: while t.kind == tyObject and t.sons[0] != nil: add(r, ~".Sup") t = skipTypes(t.sons[0], skipPtrs) if isObjLackingTypeField(t): - globalError(x.info, errGenerated, + globalError(p.config, x.info, "no 'of' operator available for pure objects") if nilCheck != nil: - r = rfmt(p.module, "(($1) && ($2))", nilCheck, genOfHelper(p, dest, r, x.info)) + r = ropecg(p.module, "(($1) && ($2))", nilCheck, genOfHelper(p, dest, r, x.info)) else: - r = rfmt(p.module, "($1)", genOfHelper(p, dest, r, x.info)) + r = ropecg(p.module, "($1)", genOfHelper(p, dest, r, x.info)) putIntoDest(p, d, x, r, a.storage) proc genOf(p: BProc, n: PNode, d: var TLoc) = @@ -1402,7 +1473,7 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) = putIntoDest(p, d, e, ropecg(p.module, "#reprStr($1)", [rdLoc(a)]), a.storage) of tySet: putIntoDest(p, d, e, ropecg(p.module, "#reprSet($1, $2)", [ - addrLoc(a), genTypeInfo(p.module, t, e.info)]), a.storage) + addrLoc(p.config, a), genTypeInfo(p.module, t, e.info)]), a.storage) of tyOpenArray, tyVarargs: var b: TLoc case a.t.kind @@ -1410,11 +1481,11 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) = putIntoDest(p, b, e, "$1, $1Len_0" % [rdLoc(a)], a.storage) of tyString, tySequence: putIntoDest(p, b, e, - "$1->data, $1->$2" % [rdLoc(a), lenField(p)], a.storage) + "$1$3, $2" % [rdLoc(a), lenExpr(p, a), dataField(p)], a.storage) of tyArray: putIntoDest(p, b, e, - "$1, $2" % [rdLoc(a), rope(lengthOrd(a.t))], a.storage) - else: internalError(e.sons[0].info, "genRepr()") + "$1, $2" % [rdLoc(a), rope(lengthOrd(p.config, a.t))], a.storage) + else: internalError(p.config, e.sons[0].info, "genRepr()") putIntoDest(p, d, e, ropecg(p.module, "#reprOpenArray($1, $2)", [rdLoc(b), genTypeInfo(p.module, elemType(t), e.info)]), a.storage) @@ -1423,12 +1494,12 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) = ropecg(p.module, "#reprAny($1, $2)", [ rdLoc(a), genTypeInfo(p.module, t, e.info)]), a.storage) of tyEmpty, tyVoid: - localError(e.info, "'repr' doesn't support 'void' type") + localError(p.config, e.info, "'repr' doesn't support 'void' type") else: putIntoDest(p, d, e, ropecg(p.module, "#reprAny($1, $2)", - [addrLoc(a), genTypeInfo(p.module, t, e.info)]), + [addrLoc(p.config, a), genTypeInfo(p.module, t, e.info)]), a.storage) - gcUsage(e) + gcUsage(p.config, e) proc genGetTypeInfo(p: BProc, e: PNode, d: var TLoc) = let t = e.sons[1].typ @@ -1440,7 +1511,7 @@ proc genDollar(p: BProc, n: PNode, d: var TLoc, frmt: string) = a.r = ropecg(p.module, frmt, [rdLoc(a)]) if d.k == locNone: getTemp(p, n.typ, d) genAssignment(p, d, a, {}) - gcUsage(n) + gcUsage(p.config, n) proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) = var a = e.sons[1] @@ -1451,41 +1522,34 @@ proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) = if op == mHigh: unaryExpr(p, e, d, "($1Len_0-1)") else: unaryExpr(p, e, d, "$1Len_0") of tyCString: - useStringh(p.module) - if op == mHigh: unaryExpr(p, e, d, "($1 ? (strlen($1)-1) : -1)") - else: unaryExpr(p, e, d, "($1 ? strlen($1) : 0)") + if op == mHigh: unaryExpr(p, e, d, "($1 ? (#nimCStrLen($1)-1) : -1)") + else: unaryExpr(p, e, d, "($1 ? #nimCStrLen($1) : 0)") of tyString: - if not p.module.compileToCpp: - if op == mHigh: unaryExpr(p, e, d, "($1 ? ($1->Sup.len-1) : -1)") - else: unaryExpr(p, e, d, "($1 ? $1->Sup.len : 0)") - else: - if op == mHigh: unaryExpr(p, e, d, "($1 ? ($1->len-1) : -1)") - else: unaryExpr(p, e, d, "($1 ? $1->len : 0)") + var a: TLoc + initLocExpr(p, e.sons[1], a) + var x = lenExpr(p, a) + if op == mHigh: x = "($1-1)" % [x] + putIntoDest(p, d, e, x) of tySequence: + # we go through a temporary here because people write bullshit code. var a, tmp: TLoc initLocExpr(p, e[1], a) getIntTemp(p, tmp) - var frmt: FormatStr - if not p.module.compileToCpp: - if op == mHigh: - frmt = "$1 = ($2 ? ($2->Sup.len-1) : -1);$n" - else: - frmt = "$1 = ($2 ? $2->Sup.len : 0);$n" - else: - if op == mHigh: - frmt = "$1 = ($2 ? ($2->len-1) : -1);$n" - else: - frmt = "$1 = ($2 ? $2->len : 0);$n" - lineCg(p, cpsStmts, frmt, tmp.r, rdLoc(a)) + var x = lenExpr(p, a) + if op == mHigh: x = "($1-1)" % [x] + lineCg(p, cpsStmts, "$1 = $2;$n", tmp.r, x) putIntoDest(p, d, e, tmp.r) of tyArray: # YYY: length(sideeffect) is optimized away incorrectly? - if op == mHigh: putIntoDest(p, d, e, rope(lastOrd(typ))) - else: putIntoDest(p, d, e, rope(lengthOrd(typ))) - else: internalError(e.info, "genArrayLen()") + if op == mHigh: putIntoDest(p, d, e, rope(lastOrd(p.config, typ))) + else: putIntoDest(p, d, e, rope(lengthOrd(p.config, typ))) + else: internalError(p.config, e.info, "genArrayLen()") proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) = - var a, b: TLoc + if p.config.selectedGc == gcDestructors: + genCall(p, e, d) + return + var a, b, call: TLoc assert(d.k == locNone) var x = e.sons[1] if x.kind in {nkAddr, nkHiddenAddr}: x = x[0] @@ -1493,18 +1557,31 @@ proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) = initLocExpr(p, e.sons[2], b) let t = skipTypes(e.sons[1].typ, {tyVar}) let setLenPattern = if not p.module.compileToCpp: - "$1 = ($3) #setLengthSeq(&($1)->Sup, sizeof($4), $2);$n" + "($3) #setLengthSeqV2(&($1)->Sup, $4, $2)" else: - "$1 = ($3) #setLengthSeq($1, sizeof($4), $2);$n" + "($3) #setLengthSeqV2($1, $4, $2)" - lineCg(p, cpsStmts, setLenPattern, [ + initLoc(call, locCall, e, OnHeap) + call.r = ropecg(p.module, setLenPattern, [ rdLoc(a), rdLoc(b), getTypeDesc(p.module, t), - getTypeDesc(p.module, t.skipTypes(abstractInst).sons[0])]) - gcUsage(e) + genTypeInfo(p.module, t.skipTypes(abstractInst), e.info)]) + genAssignment(p, a, call, {}) + gcUsage(p.config, e) proc genSetLengthStr(p: BProc, e: PNode, d: var TLoc) = - binaryStmt(p, e, d, "$1 = #setLengthStr($1, $2);$n") - gcUsage(e) + if p.config.selectedGc == gcDestructors: + binaryStmtAddr(p, e, d, "#setLengthStrV2($1, $2);$n") + else: + var a, b, call: TLoc + if d.k != locNone: internalError(p.config, e.info, "genSetLengthStr") + initLocExpr(p, e.sons[1], a) + initLocExpr(p, e.sons[2], b) + + initLoc(call, locCall, e, OnHeap) + call.r = ropecg(p.module, "#setLengthStr($1, $2)", [ + rdLoc(a), rdLoc(b)]) + genAssignment(p, a, call, {}) + gcUsage(p.config, e) proc genSwap(p: BProc, e: PNode, d: var TLoc) = # swap(a, b) --> @@ -1519,19 +1596,19 @@ proc genSwap(p: BProc, e: PNode, d: var TLoc) = genAssignment(p, a, b, {}) genAssignment(p, b, tmp, {}) -proc rdSetElemLoc(a: TLoc, setType: PType): Rope = +proc rdSetElemLoc(conf: ConfigRef; a: TLoc, setType: PType): Rope = # read a location of an set element; it may need a subtraction operation # before the set operation result = rdCharLoc(a) assert(setType.kind == tySet) - if firstOrd(setType) != 0: - result = "($1- $2)" % [result, rope(firstOrd(setType))] + if firstOrd(conf, setType) != 0: + result = "($1- $2)" % [result, rope(firstOrd(conf, setType))] -proc fewCmps(s: PNode): bool = +proc fewCmps(conf: ConfigRef; s: PNode): bool = # this function estimates whether it is better to emit code # for constructing the set or generating a bunch of comparisons directly - if s.kind != nkCurly: internalError(s.info, "fewCmps") - if (getSize(s.typ) <= platform.intSize) and (nfAllConst in s.flags): + if s.kind != nkCurly: return false + if (getSize(conf, s.typ) <= conf.target.intSize) and (nfAllConst in s.flags): result = false # it is better to emit the set generation code elif elemType(s.typ).kind in {tyInt, tyInt16..tyInt64}: result = true # better not emit the set if int is basetype! @@ -1539,10 +1616,10 @@ proc fewCmps(s: PNode): bool = result = sonsLen(s) <= 8 # 8 seems to be a good value proc binaryExprIn(p: BProc, e: PNode, a, b, d: var TLoc, frmt: string) = - putIntoDest(p, d, e, frmt % [rdLoc(a), rdSetElemLoc(b, a.t)]) + putIntoDest(p, d, e, frmt % [rdLoc(a), rdSetElemLoc(p.config, b, a.t)]) proc genInExprAux(p: BProc, e: PNode, a, b, d: var TLoc) = - case int(getSize(skipTypes(e.sons[1].typ, abstractVar))) + case int(getSize(p.config, skipTypes(e.sons[1].typ, abstractVar))) of 1: binaryExprIn(p, e, a, b, d, "(($1 &(1U<<((NU)($2)&7U)))!=0)") of 2: binaryExprIn(p, e, a, b, d, "(($1 &(1U<<((NU)($2)&15U)))!=0)") of 4: binaryExprIn(p, e, a, b, d, "(($1 &(1U<<((NU)($2)&31U)))!=0)") @@ -1554,11 +1631,11 @@ proc binaryStmtInExcl(p: BProc, e: PNode, d: var TLoc, frmt: string) = assert(d.k == locNone) initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) - lineF(p, cpsStmts, frmt, [rdLoc(a), rdSetElemLoc(b, a.t)]) + lineF(p, cpsStmts, frmt, [rdLoc(a), rdSetElemLoc(p.config, b, a.t)]) proc genInOp(p: BProc, e: PNode, d: var TLoc) = var a, b, x, y: TLoc - if (e.sons[1].kind == nkCurly) and fewCmps(e.sons[1]): + if (e.sons[1].kind == nkCurly) and fewCmps(p.config, e.sons[1]): # a set constructor but not a constant set: # do not emit the set, but generate a bunch of comparisons; and if we do # so, we skip the unnecessary range check: This is a semantical extension @@ -1569,19 +1646,24 @@ proc genInOp(p: BProc, e: PNode, d: var TLoc) = e.sons[2] initLocExpr(p, ea, a) initLoc(b, locExpr, e, OnUnknown) - b.r = rope("(") var length = sonsLen(e.sons[1]) - for i in countup(0, length - 1): - if e.sons[1].sons[i].kind == nkRange: - initLocExpr(p, e.sons[1].sons[i].sons[0], x) - initLocExpr(p, e.sons[1].sons[i].sons[1], y) - addf(b.r, "$1 >= $2 && $1 <= $3", - [rdCharLoc(a), rdCharLoc(x), rdCharLoc(y)]) - else: - initLocExpr(p, e.sons[1].sons[i], x) - addf(b.r, "$1 == $2", [rdCharLoc(a), rdCharLoc(x)]) - if i < length - 1: add(b.r, " || ") - add(b.r, ")") + if length > 0: + b.r = rope("(") + for i in countup(0, length - 1): + let it = e.sons[1].sons[i] + if it.kind == nkRange: + initLocExpr(p, it.sons[0], x) + initLocExpr(p, it.sons[1], y) + addf(b.r, "$1 >= $2 && $1 <= $3", + [rdCharLoc(a), rdCharLoc(x), rdCharLoc(y)]) + else: + initLocExpr(p, it, x) + addf(b.r, "$1 == $2", [rdCharLoc(a), rdCharLoc(x)]) + if i < length - 1: add(b.r, " || ") + add(b.r, ")") + else: + # handle the case of an empty set + b.r = rope("0") putIntoDest(p, d, e, b.r) else: assert(e.sons[1].typ != nil) @@ -1597,11 +1679,11 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) = " $3 = (($4[$1] & ~ $5[$1]) == 0);$n" & " if (!$3) break;}$n", "for ($1 = 0; $1 < $2; $1++) { $n" & " $3 = (($4[$1] & ~ $5[$1]) == 0);$n" & " if (!$3) break;}$n" & - "if ($3) $3 = (memcmp($4, $5, $2) != 0);$n", + "if ($3) $3 = (#nimCmpMem($4, $5, $2) != 0);$n", "&", "|", "& ~", "^"] var a, b, i: TLoc var setType = skipTypes(e.sons[1].typ, abstractVar) - var size = int(getSize(setType)) + var size = int(getSize(p.config, setType)) case size of 1, 2, 4, 8: case op @@ -1625,25 +1707,24 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) = of mSymDiffSet: binaryExpr(p, e, d, "($1 ^ $2)") of mInSet: genInOp(p, e, d) - else: internalError(e.info, "genSetOp()") + else: internalError(p.config, e.info, "genSetOp()") else: case op of mIncl: binaryStmtInExcl(p, e, d, "$1[(NU)($2)>>3] |=(1U<<($2&7U));$n") of mExcl: binaryStmtInExcl(p, e, d, "$1[(NU)($2)>>3] &= ~(1U<<($2&7U));$n") of mCard: unaryExprChar(p, e, d, "#cardSet($1, " & $size & ')') of mLtSet, mLeSet: - getTemp(p, getSysType(tyInt), i) # our counter + getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), i) # our counter initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) - if d.k == locNone: getTemp(p, getSysType(tyBool), d) - lineF(p, cpsStmts, lookupOpr[op], + if d.k == locNone: getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyBool), d) + linefmt(p, cpsStmts, lookupOpr[op], [rdLoc(i), rope(size), rdLoc(d), rdLoc(a), rdLoc(b)]) of mEqSet: - useStringh(p.module) - binaryExprChar(p, e, d, "(memcmp($1, $2, " & $(size) & ")==0)") + binaryExprChar(p, e, d, "(#nimCmpMem($1, $2, " & $(size) & ")==0)") of mMulSet, mPlusSet, mMinusSet, mSymDiffSet: # we inline the simple for loop for better code generation: - getTemp(p, getSysType(tyInt), i) # our counter + getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), i) # our counter initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) if d.k == locNone: getTemp(p, a.t, d) @@ -1653,7 +1734,7 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) = rdLoc(i), rope(size), rdLoc(d), rdLoc(a), rdLoc(b), rope(lookupOpr[op])]) of mInSet: genInOp(p, e, d) - else: internalError(e.info, "genSetOp") + else: internalError(p.config, e.info, "genSetOp") proc genOrd(p: BProc, e: PNode, d: var TLoc) = unaryExprChar(p, e, d, "$1") @@ -1668,7 +1749,7 @@ proc genSomeCast(p: BProc, e: PNode, d: var TLoc) = let etyp = skipTypes(e.typ, abstractRange) if etyp.kind in ValueTypes and lfIndirect notin a.flags: putIntoDest(p, d, e, "(*($1*) ($2))" % - [getTypeDesc(p.module, e.typ), addrLoc(a)], a.storage) + [getTypeDesc(p.module, e.typ), addrLoc(p.config, a)], a.storage) elif etyp.kind == tyProc and etyp.callConv == ccClosure: putIntoDest(p, d, e, "(($1) ($2))" % [getClosureType(p.module, etyp, clHalfWithEnv), rdCharLoc(a)], a.storage) @@ -1732,7 +1813,9 @@ proc genConv(p: BProc, e: PNode, d: var TLoc) = proc convStrToCStr(p: BProc, n: PNode, d: var TLoc) = var a: TLoc initLocExpr(p, n.sons[0], a) - putIntoDest(p, d, n, "$1->data" % [rdLoc(a)], + putIntoDest(p, d, n, + ropecg(p.module, "#nimToCStringConv($1)", [rdLoc(a)]), +# "($1 ? $1->data : (NCSTRING)\"\")" % [a.rdLoc], a.storage) proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) = @@ -1741,22 +1824,20 @@ proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) = putIntoDest(p, d, n, ropecg(p.module, "#cstrToNimstr($1)", [rdLoc(a)]), a.storage) - gcUsage(n) + gcUsage(p.config, n) proc genStrEquals(p: BProc, e: PNode, d: var TLoc) = var x: TLoc var a = e.sons[1] var b = e.sons[2] - if (a.kind == nkNilLit) or (b.kind == nkNilLit): - binaryExpr(p, e, d, "($1 == $2)") - elif (a.kind in {nkStrLit..nkTripleStrLit}) and (a.strVal == ""): + if a.kind in {nkStrLit..nkTripleStrLit} and a.strVal == "": initLocExpr(p, e.sons[2], x) putIntoDest(p, d, e, - rfmt(nil, "(($1) && ($1)->$2 == 0)", rdLoc(x), lenField(p))) - elif (b.kind in {nkStrLit..nkTripleStrLit}) and (b.strVal == ""): + ropecg(p.module, "($1 == 0)", lenExpr(p, x))) + elif b.kind in {nkStrLit..nkTripleStrLit} and b.strVal == "": initLocExpr(p, e.sons[1], x) putIntoDest(p, d, e, - rfmt(nil, "(($1) && ($1)->$2 == 0)", rdLoc(x), lenField(p))) + ropecg(p.module, "($1 == 0)", lenExpr(p, x))) else: binaryExpr(p, e, d, "#eqStrings($1, $2)") @@ -1768,7 +1849,7 @@ proc binaryFloatArith(p: BProc, e: PNode, d: var TLoc, m: TMagic) = assert(e.sons[2].typ != nil) initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) - putIntoDest(p, d, e, rfmt(nil, "(($4)($2) $1 ($4)($3))", + putIntoDest(p, d, e, ropecg(p.module, "(($4)($2) $1 ($4)($3))", rope(opr[m]), rdLoc(a), rdLoc(b), getSimpleTypeDesc(p.module, e[1].typ))) if optNaNCheck in p.options: @@ -1816,9 +1897,22 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = getTypeDesc(p.module, ranged), res]) of mConStrStr: genStrConcat(p, e, d) - of mAppendStrCh: binaryStmt(p, e, d, "$1 = #addChar($1, $2);$n") + of mAppendStrCh: + if p.config.selectedGC == gcDestructors: + binaryStmtAddr(p, e, d, "#nimAddCharV1($1, $2);$n") + else: + var dest, b, call: TLoc + initLoc(call, locCall, e, OnHeap) + initLocExpr(p, e.sons[1], dest) + initLocExpr(p, e.sons[2], b) + call.r = ropecg(p.module, "#addChar($1, $2)", [rdLoc(dest), rdLoc(b)]) + genAssignment(p, dest, call, {}) of mAppendStrStr: genStrAppend(p, e, d) - of mAppendSeqElem: genSeqElemAppend(p, e, d) + of mAppendSeqElem: + if p.config.selectedGc == gcDestructors: + genCall(p, e, d) + else: + genSeqElemAppend(p, e, d) of mEqStr: genStrEquals(p, e, d) of mLeStr: binaryExpr(p, e, d, "(#cmpStrings($1, $2) <= 0)") of mLtStr: binaryExpr(p, e, d, "(#cmpStrings($1, $2) < 0)") @@ -1867,8 +1961,9 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = of mIncl, mExcl, mCard, mLtSet, mLeSet, mEqSet, mMulSet, mPlusSet, mMinusSet, mInSet: genSetOp(p, e, d, op) - of mNewString, mNewStringOfCap, mCopyStr, mCopyStrLast, mExit, - mParseBiggestFloat: + of mCopyStr, mCopyStrLast: + genCall(p, e, d) + of mNewString, mNewStringOfCap, mExit, mParseBiggestFloat: var opr = e.sons[0].sym if lfNoDecl notin opr.loc.flags: discard cgsym(p.module, $opr.loc.r) @@ -1877,12 +1972,12 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = of mEcho: genEcho(p, e[1].skipConv) of mArrToSeq: genArrToSeq(p, e, d) of mNLen..mNError, mSlurp..mQuoteAst: - localError(e.info, errXMustBeCompileTime, e.sons[0].sym.name.s) + localError(p.config, e.info, strutils.`%`(errXMustBeCompileTime, e.sons[0].sym.name.s)) of mSpawn: - let n = lowerings.wrapProcForSpawn(p.module.module, e, e.typ, nil, nil) + let n = lowerings.wrapProcForSpawn(p.module.g.graph, p.module.module, e, e.typ, nil, nil) expr(p, n, d) of mParallel: - let n = semparallel.liftParallel(p.module.module, e) + let n = semparallel.liftParallel(p.module.g.graph, p.module.module, e) expr(p, n, d) of mDeepCopy: var a, b: TLoc @@ -1894,12 +1989,12 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = else: when defined(debugMagics): echo p.prc.name.s, " ", p.prc.id, " ", p.prc.flags, " ", p.prc.ast[genericParamsPos].kind - internalError(e.info, "genMagicExpr: " & $op) + internalError(p.config, e.info, "genMagicExpr: " & $op) proc genSetConstr(p: BProc, e: PNode, d: var TLoc) = # example: { a..b, c, d, e, f..g } # we have to emit an expression of the form: - # memset(tmp, 0, sizeof(tmp)); inclRange(tmp, a, b); incl(tmp, c); + # nimZeroMem(tmp, sizeof(tmp)); inclRange(tmp, a, b); incl(tmp, c); # incl(tmp, d); incl(tmp, e); inclRange(tmp, f, g); var a, b, idx: TLoc @@ -1907,40 +2002,40 @@ proc genSetConstr(p: BProc, e: PNode, d: var TLoc) = putIntoDest(p, d, e, genSetNode(p, e)) else: if d.k == locNone: getTemp(p, e.typ, d) - if getSize(e.typ) > 8: + if getSize(p.config, e.typ) > 8: # big set: - useStringh(p.module) - lineF(p, cpsStmts, "memset($1, 0, sizeof($1));$n", [rdLoc(d)]) - for i in countup(0, sonsLen(e) - 1): - if e.sons[i].kind == nkRange: - getTemp(p, getSysType(tyInt), idx) # our counter - initLocExpr(p, e.sons[i].sons[0], a) - initLocExpr(p, e.sons[i].sons[1], b) + linefmt(p, cpsStmts, "#nimZeroMem($1, sizeof($2));$n", + [rdLoc(d), getTypeDesc(p.module, e.typ)]) + for it in e.sons: + if it.kind == nkRange: + getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), idx) # our counter + initLocExpr(p, it.sons[0], a) + initLocExpr(p, it.sons[1], b) lineF(p, cpsStmts, "for ($1 = $3; $1 <= $4; $1++) $n" & "$2[(NU)($1)>>3] |=(1U<<((NU)($1)&7U));$n", [rdLoc(idx), rdLoc(d), - rdSetElemLoc(a, e.typ), rdSetElemLoc(b, e.typ)]) + rdSetElemLoc(p.config, a, e.typ), rdSetElemLoc(p.config, b, e.typ)]) else: - initLocExpr(p, e.sons[i], a) + initLocExpr(p, it, a) lineF(p, cpsStmts, "$1[(NU)($2)>>3] |=(1U<<((NU)($2)&7U));$n", - [rdLoc(d), rdSetElemLoc(a, e.typ)]) + [rdLoc(d), rdSetElemLoc(p.config, a, e.typ)]) else: # small set - var ts = "NU" & $(getSize(e.typ) * 8) + var ts = "NU" & $(getSize(p.config, e.typ) * 8) lineF(p, cpsStmts, "$1 = 0;$n", [rdLoc(d)]) - for i in countup(0, sonsLen(e) - 1): - if e.sons[i].kind == nkRange: - getTemp(p, getSysType(tyInt), idx) # our counter - initLocExpr(p, e.sons[i].sons[0], a) - initLocExpr(p, e.sons[i].sons[1], b) + for it in e.sons: + if it.kind == nkRange: + getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), idx) # our counter + initLocExpr(p, it.sons[0], a) + initLocExpr(p, it.sons[1], b) lineF(p, cpsStmts, "for ($1 = $3; $1 <= $4; $1++) $n" & "$2 |=((" & ts & ")(1)<<(($1)%(sizeof(" & ts & ")*8)));$n", [ - rdLoc(idx), rdLoc(d), rdSetElemLoc(a, e.typ), - rdSetElemLoc(b, e.typ)]) + rdLoc(idx), rdLoc(d), rdSetElemLoc(p.config, a, e.typ), + rdSetElemLoc(p.config, b, e.typ)]) else: - initLocExpr(p, e.sons[i], a) + initLocExpr(p, it, a) lineF(p, cpsStmts, "$1 |=((" & ts & ")(1)<<(($2)%(sizeof(" & ts & ")*8)));$n", - [rdLoc(d), rdSetElemLoc(a, e.typ)]) + [rdLoc(d), rdSetElemLoc(p.config, a, e.typ)]) proc genTupleConstr(p: BProc, n: PNode, d: var TLoc) = var rec: TLoc @@ -1973,7 +2068,7 @@ proc genClosure(p: BProc, n: PNode, d: var TLoc) = initLocExpr(p, n.sons[0], a) initLocExpr(p, n.sons[1], b) if n.sons[0].skipConv.kind == nkClosure: - internalError(n.info, "closure to closure created") + internalError(p.config, n.info, "closure to closure created") # tasyncawait.nim breaks with this optimization: when false: if d.k != locNone: @@ -2014,7 +2109,7 @@ template genStmtListExprImpl(exprOrStmt) {.dirty.} = let theMacro = it[0].sym add p.s(cpsStmts), initFrameNoDebug(p, frameName, makeCString theMacro.name.s, - theMacro.info.quotedFilename, it.info.line) + quotedFilename(p.config, theMacro.info), it.info.line.int) else: genStmts(p, it) if n.len > 0: exprOrStmt @@ -2042,6 +2137,7 @@ proc upConv(p: BProc, n: PNode, d: var TLoc) = if t.kind notin {tyVar, tyLent} or not p.module.compileToCpp: r = "(*$1)" % [r] t = skipTypes(t.lastSon, abstractInst) + discard getTypeDesc(p.module, t) if not p.module.compileToCpp: while t.kind == tyObject and t.sons[0] != nil: add(r, ".Sup") @@ -2057,10 +2153,11 @@ proc upConv(p: BProc, n: PNode, d: var TLoc) = "(($1) ($2))" % [getTypeDesc(p.module, n.typ), rdLoc(a)], a.storage) else: putIntoDest(p, d, n, "(*($1*) ($2))" % - [getTypeDesc(p.module, dest), addrLoc(a)], a.storage) + [getTypeDesc(p.module, dest), addrLoc(p.config, a)], a.storage) proc downConv(p: BProc, n: PNode, d: var TLoc) = if p.module.compileToCpp: + discard getTypeDesc(p.module, skipTypes(n[0].typ, abstractPtrs)) expr(p, n.sons[0], d) # downcast does C++ for us else: var dest = skipTypes(n.typ, abstractPtrs) @@ -2069,6 +2166,7 @@ proc downConv(p: BProc, n: PNode, d: var TLoc) = while arg.kind == nkObjDownConv: arg = arg.sons[0] var src = skipTypes(arg.typ, abstractPtrs) + discard getTypeDesc(p.module, src) var a: TLoc initLocExpr(p, arg, a) var r = rdLoc(a) @@ -2132,11 +2230,11 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = #if sym.kind == skIterator: # echo renderTree(sym.getBody, {renderIds}) if sfCompileTime in sym.flags: - localError(n.info, "request to generate code for .compileTime proc: " & + localError(p.config, n.info, "request to generate code for .compileTime proc: " & sym.name.s) genProc(p.module, sym) if sym.loc.r == nil or sym.loc.lode == nil: - internalError(n.info, "expr: proc not init " & sym.name.s) + internalError(p.config, n.info, "expr: proc not init " & sym.name.s) putLocIntoDest(p, d, sym.loc) of skConst: if isSimpleConst(sym.typ): @@ -2144,6 +2242,9 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = else: genComplexConst(p, sym, d) of skEnumField: + # we never reach this case - as of the time of this comment, + # skEnumField is folded to an int in semfold.nim, but this code + # remains for robustness putIntoDest(p, d, n, rope(sym.position)) of skVar, skForVar, skResult, skLet: if {sfGlobal, sfThread} * sym.flags != {}: @@ -2151,10 +2252,10 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = if sym.loc.r == nil or sym.loc.t == nil: #echo "FAILED FOR PRCO ", p.prc.name.s #echo renderTree(p.prc.ast, {renderIds}) - internalError n.info, "expr: var not init " & sym.name.s & "_" & $sym.id + internalError p.config, n.info, "expr: var not init " & sym.name.s & "_" & $sym.id if sfThread in sym.flags: accessThreadLocalVar(p, sym) - if emulatedThreadVars(): + if emulatedThreadVars(p.config): putIntoDest(p, d, sym.loc.lode, "NimTV_->" & sym.loc.r) else: putLocIntoDest(p, d, sym.loc) @@ -2164,16 +2265,16 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = if sym.loc.r == nil or sym.loc.t == nil: #echo "FAILED FOR PRCO ", p.prc.name.s #echo renderTree(p.prc.ast, {renderIds}) - internalError(n.info, "expr: temp not init " & sym.name.s & "_" & $sym.id) + internalError(p.config, n.info, "expr: temp not init " & sym.name.s & "_" & $sym.id) putLocIntoDest(p, d, sym.loc) of skParam: if sym.loc.r == nil or sym.loc.t == nil: # echo "FAILED FOR PRCO ", p.prc.name.s # debug p.prc.typ.n # echo renderTree(p.prc.ast, {renderIds}) - internalError(n.info, "expr: param not init " & sym.name.s & "_" & $sym.id) + internalError(p.config, n.info, "expr: param not init " & sym.name.s & "_" & $sym.id) putLocIntoDest(p, d, sym.loc) - else: internalError(n.info, "expr(" & $sym.kind & "); unknown symbol") + else: internalError(p.config, n.info, "expr(" & $sym.kind & "); unknown symbol") of nkNilLit: if not isEmptyType(n.typ): putIntoDest(p, d, n, genLiteral(p, n)) @@ -2211,7 +2312,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = genSeqConstr(p, n, d) else: genArrayConstr(p, n, d) - of nkPar: + of nkPar, nkTupleConstr: if isDeepConstExpr(n) and n.len != 0: exprComplexConst(p, n, d) else: @@ -2242,15 +2343,15 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = var sym = n.sons[namePos].sym genProc(p.module, sym) if sym.loc.r == nil or sym.loc.lode == nil: - internalError(n.info, "expr: proc not init " & sym.name.s) + internalError(p.config, n.info, "expr: proc not init " & sym.name.s) putLocIntoDest(p, d, sym.loc) of nkClosure: genClosure(p, n, d) of nkEmpty: discard of nkWhileStmt: genWhileStmt(p, n) of nkVarSection, nkLetSection: genVarStmt(p, n) - of nkConstSection: genConstStmt(p, n) - of nkForStmt: internalError(n.info, "for statement not eliminated") + of nkConstSection: discard # consts generated lazily on use + of nkForStmt: internalError(p.config, n.info, "for statement not eliminated") of nkCaseStmt: genCase(p, n, d) of nkReturnStmt: genReturnStmt(p, n) of nkBreakStmt: genBreakStmt(p, n) @@ -2273,12 +2374,12 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = incl a.flags, lfSingleUse genCall(p, ex, a) if lfSingleUse notin a.flags: - line(p, cpsStmts, a.r & ";" & tnl) + line(p, cpsStmts, a.r & ";\L") else: initLocExpr(p, ex, a) of nkAsmStmt: genAsmStmt(p, n) of nkTryStmt: - if p.module.compileToCpp and optNoCppExceptions notin gGlobalOptions: + if p.module.compileToCpp and optNoCppExceptions notin p.config.globalOptions: genTryCpp(p, n, d) else: genTry(p, n, d) @@ -2289,7 +2390,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = genTypeSection(p.module, n) of nkCommentStmt, nkIteratorDef, nkIncludeStmt, nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt, - nkFromStmt, nkTemplateDef, nkMacroDef: + nkFromStmt, nkTemplateDef, nkMacroDef, nkStaticStmt: discard of nkPragma: genPragma(p, n) of nkPragmaBlock: expr(p, n.lastSon, d) @@ -2300,8 +2401,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = # are not transformed correctly. We work around this issue (#411) here # by ensuring it's no inner proc (owner is a module): if prc.skipGenericOwner.kind == skModule and sfCompileTime notin prc.flags: - if (not emitLazily(prc)) or - ({sfExportc, sfCompilerProc} * prc.flags == {sfExportc}) or + if ({sfExportc, sfCompilerProc} * prc.flags == {sfExportc}) or (sfExportc in prc.flags and lfExportLib in prc.loc.flags) or (prc.kind == skMethod): # we have not only the header: @@ -2310,8 +2410,8 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = of nkParForStmt: genParForStmt(p, n) of nkState: genState(p, n) of nkGotoState: genGotoState(p, n) - of nkBreakState: genBreakState(p, n) - else: internalError(n.info, "expr(" & $n.kind & "); unknown node kind") + of nkBreakState: genBreakState(p, n, d) + else: internalError(p.config, n.info, "expr(" & $n.kind & "); unknown node kind") proc genNamedConstExpr(p: BProc, n: PNode): Rope = if n.kind == nkExprColonExpr: result = genConstExpr(p, n.sons[1]) @@ -2344,16 +2444,16 @@ proc getDefaultValue(p: BProc; typ: PType; info: TLineInfo): Rope = result.add "}" of tyArray: result = rope"{}" of tySet: - if mapType(t) == ctArray: result = rope"{}" + if mapType(p.config, t) == ctArray: result = rope"{}" else: result = rope"0" else: - globalError(info, "cannot create null element for: " & $t.kind) + globalError(p.config, info, "cannot create null element for: " & $t.kind) proc getNullValueAux(p: BProc; t: PType; obj, cons: PNode, result: var Rope; count: var int) = case obj.kind of nkRecList: - for i in countup(0, sonsLen(obj) - 1): - getNullValueAux(p, t, obj.sons[i], cons, result, count) + for it in obj.sons: + getNullValueAux(p, t, it, cons, result, count) of nkRecCase: getNullValueAux(p, t, obj.sons[0], cons, result, count) for i in countup(1, sonsLen(obj) - 1): @@ -2373,7 +2473,7 @@ proc getNullValueAux(p: BProc; t: PType; obj, cons: PNode, result: var Rope; cou # not found, produce default value: result.add getDefaultValue(p, field.typ, cons.info) else: - localError(cons.info, "cannot create null element for: " & $obj) + localError(p.config, cons.info, "cannot create null element for: " & $obj) proc getNullValueAuxT(p: BProc; orig, t: PType; obj, cons: PNode, result: var Rope; count: var int) = var base = t.sons[0] @@ -2413,7 +2513,7 @@ proc genConstSimpleList(p: BProc, n: PNode): Rope = addf(result, "}$n", []) proc genConstSeq(p: BProc, n: PNode, t: PType): Rope = - var data = "{{$1, $1}" % [n.len.rope] + var data = "{{$1, $1 | NIM_STRLIT_FLAG}" % [n.len.rope] if n.len > 0: # array part needs extra curlies: data.add(", {") @@ -2441,13 +2541,13 @@ proc genConstExpr(p: BProc, n: PNode): Rope = result = genConstExpr(p, n.sons[1]) of nkCurly: var cs: TBitSet - toBitSet(n, cs) - result = genRawSetData(cs, int(getSize(n.typ))) - of nkBracket, nkPar, nkClosure: + toBitSet(p.config, n, cs) + result = genRawSetData(cs, int(getSize(p.config, n.typ))) + of nkBracket, nkPar, nkTupleConstr, nkClosure: var t = skipTypes(n.typ, abstractInst) if t.kind == tySequence: result = genConstSeq(p, n, n.typ) - elif t.kind == tyProc and t.callConv == ccClosure and not n.sons.isNil and + elif t.kind == tyProc and t.callConv == ccClosure and n.len > 0 and n.sons[0].kind == nkNilLit and n.sons[1].kind == nkNilLit: # this hack fixes issue that nkNilLit is expanded to {NIM_NIL,NIM_NIL} # this behaviour is needed since closure_var = nil must be @@ -2460,6 +2560,13 @@ proc genConstExpr(p: BProc, n: PNode): Rope = result = genConstSimpleList(p, n) of nkObjConstr: result = genConstObjConstr(p, n) + of nkStrLit..nkTripleStrLit: + if p.config.selectedGc == gcDestructors: + result = genStringLiteralV2Const(p.module, n) + else: + var d: TLoc + initLocExpr(p, n, d) + result = rdLoc(d) else: var d: TLoc initLocExpr(p, n, d) diff --git a/compiler/ccgliterals.nim b/compiler/ccgliterals.nim new file mode 100644 index 000000000..34677ec06 --- /dev/null +++ b/compiler/ccgliterals.nim @@ -0,0 +1,107 @@ +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This include file contains the logic to produce constant string +## and seq literals. The code here is responsible that +## ``const x = ["a", "b"]`` works without hidden runtime creation code. +## The price is that seqs and strings are not purely a library +## implementation. + +template detectVersion(field, corename) = + if m.g.field == 0: + let core = getCompilerProc(m.g.graph, corename) + if core == nil or core.kind != skConst: + m.g.field = 1 + else: + m.g.field = int ast.getInt(core.ast) + result = m.g.field + +proc detectStrVersion(m: BModule): int = + detectVersion(strVersion, "nimStrVersion") + +proc detectSeqVersion(m: BModule): int = + detectVersion(seqVersion, "nimSeqVersion") + +# ----- Version 1: GC'ed strings and seqs -------------------------------- + +proc genStringLiteralDataOnlyV1(m: BModule, s: string): Rope = + discard cgsym(m, "TGenericSeq") + result = getTempName(m) + addf(m.s[cfsData], "STRING_LITERAL($1, $2, $3);$n", + [result, makeCString(s), rope(len(s))]) + +proc genStringLiteralV1(m: BModule; n: PNode): Rope = + if s.isNil: + result = ropecg(m, "((#NimStringDesc*) NIM_NIL)", []) + else: + let id = nodeTableTestOrSet(m.dataCache, n, m.labels) + if id == m.labels: + # string literal not found in the cache: + result = ropecg(m, "((#NimStringDesc*) &$1)", + [genStringLiteralDataOnlyV1(m, n.strVal)]) + else: + result = ropecg(m, "((#NimStringDesc*) &$1$2)", + [m.tmpBase, rope(id)]) + +# ------ Version 2: destructor based strings and seqs ----------------------- + +proc genStringLiteralDataOnlyV2(m: BModule, s: string): Rope = + result = getTempName(m) + addf(m.s[cfsData], "static const struct {$n" & + " NI cap; void* allocator; NIM_CHAR data[$2];$n" & + "} $1 = { $2, NIM_NIL, $3 };$n", + [result, rope(len(s)), makeCString(s)]) + +proc genStringLiteralV2(m: BModule; n: PNode): Rope = + let id = nodeTableTestOrSet(m.dataCache, n, m.labels) + if id == m.labels: + discard cgsym(m, "NimStrPayload") + discard cgsym(m, "NimStringV2") + # string literal not found in the cache: + let pureLit = genStringLiteralDataOnlyV2(m, n.strVal) + result = getTempName(m) + addf(m.s[cfsData], "static const NimStringV2 $1 = {$2, (NimStrPayload*)&$3};$n", + [result, rope(len(n.strVal)), pureLit]) + else: + result = m.tmpBase & rope(id) + +proc genStringLiteralV2Const(m: BModule; n: PNode): Rope = + let id = nodeTableTestOrSet(m.dataCache, n, m.labels) + var pureLit: Rope + if id == m.labels: + discard cgsym(m, "NimStrPayload") + discard cgsym(m, "NimStringV2") + # string literal not found in the cache: + pureLit = genStringLiteralDataOnlyV2(m, n.strVal) + else: + pureLit = m.tmpBase & rope(id) + result = "{$1, (NimStrPayload*)&$2}" % [rope(len(n.strVal)), pureLit] + +# ------ Version selector --------------------------------------------------- + +proc genStringLiteralDataOnly(m: BModule; s: string; info: TLineInfo): Rope = + case detectStrVersion(m) + of 0, 1: result = genStringLiteralDataOnlyV1(m, s) + of 2: result = genStringLiteralDataOnlyV2(m, s) + else: + localError(m.config, info, "cannot determine how to produce code for string literal") + +proc genStringLiteralFromData(m: BModule; data: Rope; info: TLineInfo): Rope = + result = ropecg(m, "((#NimStringDesc*) &$1)", + [data]) + +proc genNilStringLiteral(m: BModule; info: TLineInfo): Rope = + result = ropecg(m, "((#NimStringDesc*) NIM_NIL)", []) + +proc genStringLiteral(m: BModule; n: PNode): Rope = + case detectStrVersion(m) + of 0, 1: result = genStringLiteralV1(m, n) + of 2: result = genStringLiteralV2(m, n) + else: + localError(m.config, n.info, "cannot determine how to produce code for string literal") diff --git a/compiler/ccgmerge.nim b/compiler/ccgmerge.nim index f667be70f..067a60c57 100644 --- a/compiler/ccgmerge.nim +++ b/compiler/ccgmerge.nim @@ -45,36 +45,34 @@ const ] NimMergeEndMark = "/*\tNIM_merge_END:*/" -proc genSectionStart*(fs: TCFileSection): Rope = - if compilationCachePresent: - result = rope(tnl) - add(result, "/*\t") +proc genSectionStart*(fs: TCFileSection; conf: ConfigRef): Rope = + if compilationCachePresent(conf): + result = nil + add(result, "\n/*\t") add(result, CFileSectionNames[fs]) - add(result, ":*/") - add(result, tnl) + add(result, ":*/\n") -proc genSectionEnd*(fs: TCFileSection): Rope = - if compilationCachePresent: - result = rope(NimMergeEndMark & tnl) +proc genSectionEnd*(fs: TCFileSection; conf: ConfigRef): Rope = + if compilationCachePresent(conf): + result = rope(NimMergeEndMark & "\n") -proc genSectionStart*(ps: TCProcSection): Rope = - if compilationCachePresent: - result = rope(tnl) - add(result, "/*\t") +proc genSectionStart*(ps: TCProcSection; conf: ConfigRef): Rope = + if compilationCachePresent(conf): + result = rope("") + add(result, "\n/*\t") add(result, CProcSectionNames[ps]) - add(result, ":*/") - add(result, tnl) + add(result, ":*/\n") -proc genSectionEnd*(ps: TCProcSection): Rope = - if compilationCachePresent: - result = rope(NimMergeEndMark & tnl) +proc genSectionEnd*(ps: TCProcSection; conf: ConfigRef): Rope = + if compilationCachePresent(conf): + result = rope(NimMergeEndMark & "\n") proc writeTypeCache(a: TypeCache, s: var string) = var i = 0 for id, value in pairs(a): if i == 10: i = 0 - s.add(tnl) + s.add('\L') else: s.add(' ') encodeStr($id, s) @@ -88,7 +86,7 @@ proc writeIntSet(a: IntSet, s: var string) = for x in items(a): if i == 10: i = 0 - s.add(tnl) + s.add('\L') else: s.add(' ') encodeVInt(x, s) @@ -96,9 +94,8 @@ proc writeIntSet(a: IntSet, s: var string) = s.add('}') proc genMergeInfo*(m: BModule): Rope = - if not compilationCachePresent: return nil - var s = "/*\tNIM_merge_INFO:" - s.add(tnl) + if not compilationCachePresent(m.config): return nil + var s = "/*\tNIM_merge_INFO:\n" s.add("typeCache:{") writeTypeCache(m.typeCache, s) s.add("declared:{") @@ -110,8 +107,7 @@ proc genMergeInfo*(m: BModule): Rope = encodeVInt(m.labels, s) s.add(" flags:") encodeVInt(cast[int](m.flags), s) - s.add(tnl) - s.add("*/") + s.add("\n*/") result = s.rope template `^`(pos: int): untyped = L.buf[pos] @@ -155,13 +151,13 @@ proc readVerbatimSection(L: var TBaseLexer): Rope = of CR: pos = nimlexbase.handleCR(L, pos) buf = L.buf - r.add(tnl) + r.add('\L') of LF: pos = nimlexbase.handleLF(L, pos) buf = L.buf - r.add(tnl) + r.add('\L') of '\0': - internalError("ccgmerge: expected: " & NimMergeEndMark) + doAssert(false, "ccgmerge: expected: " & NimMergeEndMark) break else: if atEndMark(buf, pos): @@ -179,7 +175,7 @@ proc readKey(L: var TBaseLexer, result: var string) = while buf[pos] in IdentChars: result.add(buf[pos]) inc pos - if buf[pos] != ':': internalError("ccgmerge: ':' expected") + if buf[pos] != ':': doAssert(false, "ccgmerge: ':' expected") L.bufpos = pos + 1 # skip ':' proc newFakeType(id: int): PType = @@ -187,12 +183,12 @@ proc newFakeType(id: int): PType = result.id = id proc readTypeCache(L: var TBaseLexer, result: var TypeCache) = - if ^L.bufpos != '{': internalError("ccgmerge: '{' expected") + if ^L.bufpos != '{': doAssert(false, "ccgmerge: '{' expected") inc L.bufpos while ^L.bufpos != '}': skipWhite(L) var key = decodeStr(L.buf, L.bufpos) - if ^L.bufpos != ':': internalError("ccgmerge: ':' expected") + if ^L.bufpos != ':': doAssert(false, "ccgmerge: ':' expected") inc L.bufpos var value = decodeStr(L.buf, L.bufpos) # XXX implement me @@ -201,7 +197,7 @@ proc readTypeCache(L: var TBaseLexer, result: var TypeCache) = inc L.bufpos proc readIntSet(L: var TBaseLexer, result: var IntSet) = - if ^L.bufpos != '{': internalError("ccgmerge: '{' expected") + if ^L.bufpos != '{': doAssert(false, "ccgmerge: '{' expected") inc L.bufpos while ^L.bufpos != '}': skipWhite(L) @@ -225,7 +221,7 @@ proc processMergeInfo(L: var TBaseLexer, m: BModule) = of "labels": m.labels = decodeVInt(L.buf, L.bufpos) of "flags": m.flags = cast[set[CodegenFlag]](decodeVInt(L.buf, L.bufpos) != 0) - else: internalError("ccgmerge: unknown key: " & k) + else: doAssert(false, "ccgmerge: unknown key: " & k) when not defined(nimhygiene): {.pragma: inject.} @@ -275,9 +271,9 @@ proc readMergeSections(cfilename: string, m: var TMergeSections) = if sectionB >= 0 and sectionB <= high(TCProcSection).int: m.p[TCProcSection(sectionB)] = verbatim else: - internalError("ccgmerge: unknown section: " & k) + doAssert(false, "ccgmerge: unknown section: " & k) else: - internalError("ccgmerge: '*/' expected") + doAssert(false, "ccgmerge: '*/' expected") proc mergeRequired*(m: BModule): bool = for i in cfsHeaders..cfsProcs: diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index a69495a4b..69e6558bb 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -16,7 +16,7 @@ const # above X strings a hash-switch for strings is generated proc registerGcRoot(p: BProc, v: PSym) = - if gSelectedGC in {gcMarkAndSweep, gcGenerational, gcV2, gcRefc} and + if p.config.selectedGC in {gcMarkAndSweep, gcDestructors, gcV2, gcRefc} and containsGarbageCollectedRef(v.loc.t): # we register a specialized marked proc here; this has the advantage # that it works out of the box for thread local storage then :-) @@ -28,24 +28,28 @@ proc registerGcRoot(p: BProc, v: PSym) = appcg(p.module, p.module.initProc.procSec(cpsInit), "#nimRegisterGlobalMarker($1);$n", [prc]) -proc isAssignedImmediately(n: PNode): bool {.inline.} = +proc isAssignedImmediately(conf: ConfigRef; n: PNode): bool {.inline.} = if n.kind == nkEmpty: return false - if isInvalidReturnType(n.typ): + if isInvalidReturnType(conf, n.typ): # var v = f() # is transformed into: var v; f(addr v) # where 'f' **does not** initialize the result! return false result = true +proc inExceptBlockLen(p: BProc): int = + for x in p.nestedTryStmts: + if x.inExcept: result.inc + proc genVarTuple(p: BProc, n: PNode) = var tup, field: TLoc - if n.kind != nkVarTuple: internalError(n.info, "genVarTuple") + if n.kind != nkVarTuple: internalError(p.config, n.info, "genVarTuple") var L = sonsLen(n) # if we have a something that's been captured, use the lowering instead: for i in countup(0, L-3): if n[i].kind != nkSym: - genStmts(p, lowerTupleUnpacking(n, p.prc)) + genStmts(p, lowerTupleUnpacking(p.module.g.graph, n, p.prc)) return genLineDir(p, n) @@ -61,12 +65,12 @@ proc genVarTuple(p: BProc, n: PNode) = registerGcRoot(p, v) else: assignLocalVar(p, vn) - initLocalVar(p, v, immediateAsgn=isAssignedImmediately(n[L-1])) + initLocalVar(p, v, immediateAsgn=isAssignedImmediately(p.config, n[L-1])) initLoc(field, locExpr, vn, tup.storage) if t.kind == tyTuple: field.r = "$1.Field$2" % [rdLoc(tup), rope(i)] else: - if t.n.sons[i].kind != nkSym: internalError(n.info, "genVarTuple") + if t.n.sons[i].kind != nkSym: internalError(p.config, n.info, "genVarTuple") field.r = "$1.$2" % [rdLoc(tup), mangleRecFieldName(p.module, t.n.sons[i].sym, t)] putLocIntoDest(p, v.loc, field) @@ -96,7 +100,7 @@ proc startBlock(p: BProc, start: FormatStr = "{$n", setLen(p.blocks, result + 1) p.blocks[result].id = p.labels p.blocks[result].nestedTryStmts = p.nestedTryStmts.len.int16 - p.blocks[result].nestedExceptStmts = p.inExceptBlock.int16 + p.blocks[result].nestedExceptStmts = p.inExceptBlockLen.int16 proc assignLabel(b: var TBlock): Rope {.inline.} = b.label = "LA" & b.id.rope @@ -121,7 +125,7 @@ proc endBlock(p: BProc, blockEnd: Rope) = proc endBlock(p: BProc) = let topBlock = p.blocks.len - 1 var blockEnd = if p.blocks[topBlock].label != nil: - rfmt(nil, "} $1: ;$n", p.blocks[topBlock].label) + ropecg(p.module, "} $1: ;$n", p.blocks[topBlock].label) else: ~"}$n" let frameLen = p.blocks[topBlock].frameLen @@ -145,7 +149,7 @@ template preserveBreakIdx(body: untyped): untyped = p.breakIdx = oldBreakIdx proc genState(p: BProc, n: PNode) = - internalAssert n.len == 1 + internalAssert p.config, n.len == 1 let n0 = n[0] if n0.kind == nkIntLit: let idx = n.sons[0].intVal @@ -153,6 +157,39 @@ proc genState(p: BProc, n: PNode) = elif n0.kind == nkStrLit: linefmt(p, cpsStmts, "$1: ;$n", n0.strVal.rope) +proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) = + # Called by return and break stmts. + # Deals with issues faced when jumping out of try/except/finally stmts, + + var stack = newSeq[tuple[n: PNode, inExcept: bool]](0) + + for i in countup(1, howManyTrys): + let tryStmt = p.nestedTryStmts.pop + if not p.module.compileToCpp or optNoCppExceptions in p.config.globalOptions: + # Pop safe points generated by try + if not tryStmt.inExcept: + linefmt(p, cpsStmts, "#popSafePoint();$n") + + # Pop this try-stmt of the list of nested trys + # so we don't infinite recurse on it in the next step. + stack.add(tryStmt) + + # Find finally-stmt for this try-stmt + # and generate a copy of its sons + var finallyStmt = lastSon(tryStmt.n) + if finallyStmt.kind == nkFinally: + genStmts(p, finallyStmt.sons[0]) + + # push old elements again: + for i in countdown(howManyTrys-1, 0): + p.nestedTryStmts.add(stack[i]) + + if not p.module.compileToCpp or optNoCppExceptions in p.config.globalOptions: + # Pop exceptions that was handled by the + # except-blocks we are in + for i in countdown(howManyExcepts-1, 0): + linefmt(p, cpsStmts, "#popCurrentException();$n") + proc genGotoState(p: BProc, n: PNode) = # we resist the temptation to translate it into duff's device as it later # will be translated into computed gotos anyway for GCC at least: @@ -163,8 +200,12 @@ proc genGotoState(p: BProc, n: PNode) = initLocExpr(p, n.sons[0], a) lineF(p, cpsStmts, "switch ($1) {$n", [rdLoc(a)]) p.beforeRetNeeded = true - lineF(p, cpsStmts, "case -1: goto BeforeRet_;$n", []) - var statesCounter = lastOrd(n.sons[0].typ) + lineF(p, cpsStmts, "case -1:$n", []) + blockLeaveActions(p, + howManyTrys = p.nestedTryStmts.len, + howManyExcepts = p.inExceptBlockLen) + lineF(p, cpsStmts, " goto BeforeRet_;$n", []) + var statesCounter = lastOrd(p.config, n.sons[0].typ) if n.len >= 2 and n[1].kind == nkIntLit: statesCounter = n[1].intVal let prefix = if n.len == 3 and n[2].kind == nkStrLit: n[2].strVal.rope @@ -173,23 +214,21 @@ proc genGotoState(p: BProc, n: PNode) = lineF(p, cpsStmts, "case $2: goto $1$2;$n", [prefix, rope(i)]) lineF(p, cpsStmts, "}$n", []) -proc genBreakState(p: BProc, n: PNode) = +proc genBreakState(p: BProc, n: PNode, d: var TLoc) = var a: TLoc + initLoc(d, locExpr, n, OnUnknown) + if n.sons[0].kind == nkClosure: - # XXX this produces quite inefficient code! initLocExpr(p, n.sons[0].sons[1], a) - lineF(p, cpsStmts, "if (((NI*) $1)[1] < 0) break;$n", [rdLoc(a)]) + d.r = "(((NI*) $1)[1] < 0)" % [rdLoc(a)] else: initLocExpr(p, n.sons[0], a) # the environment is guaranteed to contain the 'state' field at offset 1: - lineF(p, cpsStmts, "if ((((NI*) $1.ClE_0)[1]) < 0) break;$n", [rdLoc(a)]) - # lineF(p, cpsStmts, "if (($1) < 0) break;$n", [rdLoc(a)]) - -proc genVarPrototypeAux(m: BModule, n: PNode) + d.r = "((((NI*) $1.ClE_0)[1]) < 0)" % [rdLoc(a)] proc genGotoVar(p: BProc; value: PNode) = if value.kind notin {nkCharLit..nkUInt64Lit}: - localError(value.info, "'goto' target must be a literal value") + localError(p.config, value.info, "'goto' target must be a literal value") else: lineF(p, cpsStmts, "goto NIMSTATE_$#;$n", [value.intVal.rope]) @@ -217,15 +256,23 @@ proc genSingleVar(p: BProc, a: PNode) = # That's why we are doing the construction inside the preInitProc. # genObjectInit relies on the C runtime's guarantees that # global variables will be initialized to zero. - genObjectInit(p.module.preInitProc, cpsInit, v.typ, v.loc, true) + var loc = v.loc + + # When the native TLS is unavailable, a global thread-local variable needs + # one more layer of indirection in order to access the TLS block. + # Only do this for complex types that may need a call to `objectInit` + if sfThread in v.flags and emulatedThreadVars(p.config) and + isComplexValueType(v.typ): + initLocExprSingleUse(p.module.preInitProc, vn, loc) + genObjectInit(p.module.preInitProc, cpsInit, v.typ, loc, true) # Alternative construction using default constructor (which may zeromem): # if sfImportc notin v.flags: constructLoc(p.module.preInitProc, v.loc) if sfExportc in v.flags and p.module.g.generatedHeader != nil: - genVarPrototypeAux(p.module.g.generatedHeader, vn) + genVarPrototype(p.module.g.generatedHeader, vn) registerGcRoot(p, v) else: let value = a.sons[2] - let imm = isAssignedImmediately(value) + let imm = isAssignedImmediately(p.config, value) if imm and p.module.compileToCpp and p.splitDecls == 0 and not containsHiddenPointer(v.typ): # C++ really doesn't like things like 'Foo f; f = x' as that invokes a @@ -243,7 +290,10 @@ proc genSingleVar(p: BProc, a: PNode) = if params != nil: params.add(~", ") assert(sonsLen(typ) == sonsLen(typ.n)) add(params, genOtherArg(p, value, i, typ)) - lineF(p, cpsStmts, "$#($#);$n", [decl, params]) + if params == nil: + lineF(p, cpsStmts, "$#;$n", [decl]) + else: + lineF(p, cpsStmts, "$#($#);$n", [decl, params]) else: initLocExprSingleUse(p, value, tmp) lineF(p, cpsStmts, "$# = $#;$n", [decl, tmp.rdLoc]) @@ -264,28 +314,16 @@ proc genClosureVar(p: BProc, a: PNode) = loadInto(p, a.sons[0], a.sons[2], v) proc genVarStmt(p: BProc, n: PNode) = - for i in countup(0, sonsLen(n) - 1): - var a = n.sons[i] - if a.kind == nkCommentStmt: continue - if a.kind == nkIdentDefs: + for it in n.sons: + if it.kind == nkCommentStmt: continue + if it.kind == nkIdentDefs: # can be a lifted var nowadays ... - if a.sons[0].kind == nkSym: - genSingleVar(p, a) + if it.sons[0].kind == nkSym: + genSingleVar(p, it) else: - genClosureVar(p, a) + genClosureVar(p, it) else: - genVarTuple(p, a) - -proc genConstStmt(p: BProc, t: PNode) = - for i in countup(0, sonsLen(t) - 1): - var it = t.sons[i] - if it.kind == nkCommentStmt: continue - if it.kind != nkConstDef: internalError(t.info, "genConstStmt") - var c = it.sons[0].sym - if c.typ.containsCompileTimeOnly: continue - elif c.typ.kind in ConstantDataTypes and lfNoDecl notin c.loc.flags and - c.ast.len != 0: - if not emitLazily(c): requestConstImpl(p, c) + genVarTuple(p, it) proc genIf(p: BProc, n: PNode, d: var TLoc) = # @@ -306,18 +344,16 @@ proc genIf(p: BProc, n: PNode, d: var TLoc) = getTemp(p, n.typ, d) genLineDir(p, n) let lend = getLabel(p) - for i in countup(0, sonsLen(n) - 1): + for it in n.sons: # bug #4230: avoid false sharing between branches: if d.k == locTemp and isEmptyType(n.typ): d.k = locNone - let it = n.sons[i] if it.len == 2: - when newScopeForIf: startBlock(p) + startBlock(p) initLocExprSingleUse(p, it.sons[0], a) lelse = getLabel(p) inc(p.labels) lineF(p, cpsStmts, "if (!$1) goto $2;$n", [rdLoc(a), lelse]) - when not newScopeForIf: startBlock(p) if p.module.compileToCpp: # avoid "jump to label crosses initialization" error: add(p.s(cpsStmts), "{") @@ -333,47 +369,9 @@ proc genIf(p: BProc, n: PNode, d: var TLoc) = startBlock(p) expr(p, it.sons[0], d) endBlock(p) - else: internalError(n.info, "genIf()") + else: internalError(p.config, n.info, "genIf()") if sonsLen(n) > 1: fixLabel(p, lend) - -proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) = - # Called by return and break stmts. - # Deals with issues faced when jumping out of try/except/finally stmts, - - var stack: seq[PNode] - newSeq(stack, 0) - - var alreadyPoppedCnt = p.inExceptBlock - for i in countup(1, howManyTrys): - if not p.module.compileToCpp or optNoCppExceptions in gGlobalOptions: - # Pop safe points generated by try - if alreadyPoppedCnt > 0: - dec alreadyPoppedCnt - else: - linefmt(p, cpsStmts, "#popSafePoint();$n") - - # Pop this try-stmt of the list of nested trys - # so we don't infinite recurse on it in the next step. - var tryStmt = p.nestedTryStmts.pop - stack.add(tryStmt) - - # Find finally-stmt for this try-stmt - # and generate a copy of its sons - var finallyStmt = lastSon(tryStmt) - if finallyStmt.kind == nkFinally: - genStmts(p, finallyStmt.sons[0]) - - # push old elements again: - for i in countdown(howManyTrys-1, 0): - p.nestedTryStmts.add(stack[i]) - - if not p.module.compileToCpp or optNoCppExceptions in gGlobalOptions: - # Pop exceptions that was handled by the - # except-blocks we are in - for i in countdown(howManyExcepts-1, 0): - linefmt(p, cpsStmts, "#popCurrentException();$n") - proc genReturnStmt(p: BProc, t: PNode) = if nfPreventCg in t.flags: return p.beforeRetNeeded = true @@ -381,7 +379,7 @@ proc genReturnStmt(p: BProc, t: PNode) = if (t.sons[0].kind != nkEmpty): genStmts(p, t.sons[0]) blockLeaveActions(p, howManyTrys = p.nestedTryStmts.len, - howManyExcepts = p.inExceptBlock) + howManyExcepts = p.inExceptBlockLen) if (p.finallySafePoints.len > 0): # If we're in a finally block, and we came here by exception # consume it before we return. @@ -395,7 +393,7 @@ proc genGotoForCase(p: BProc; caseStmt: PNode) = let it = caseStmt.sons[i] for j in 0 .. it.len-2: if it.sons[j].kind == nkRange: - localError(it.info, "range notation not available for computed goto") + localError(p.config, it.info, "range notation not available for computed goto") return let val = getOrdValue(it.sons[j]) lineF(p, cpsStmts, "NIMSTATE_$#:$n", [val.rope]) @@ -410,19 +408,22 @@ proc genComputedGoto(p: BProc; n: PNode) = let it = n.sons[i] if it.kind == nkCaseStmt: if lastSon(it).kind != nkOfBranch: - localError(it.info, + localError(p.config, it.info, "case statement must be exhaustive for computed goto"); return casePos = i - let aSize = lengthOrd(it.sons[0].typ) + if enumHasHoles(it.sons[0].typ): + localError(p.config, it.info, + "case statement cannot work on enums with holes for computed goto"); return + let aSize = lengthOrd(p.config, it.sons[0].typ) if aSize > 10_000: - localError(it.info, + localError(p.config, it.info, "case statement has too many cases for computed goto"); return arraySize = aSize.int - if firstOrd(it.sons[0].typ) != 0: - localError(it.info, + if firstOrd(p.config, it.sons[0].typ) != 0: + localError(p.config, it.info, "case statement has to start at 0 for computed goto"); return if casePos < 0: - localError(n.info, "no case statement found for computed goto"); return + localError(p.config, n.info, "no case statement found for computed goto"); return var id = p.labels+1 inc p.labels, arraySize+1 let tmp = "TMP$1_" % [id.rope] @@ -456,7 +457,7 @@ proc genComputedGoto(p: BProc; n: PNode) = let it = caseStmt.sons[i] for j in 0 .. it.len-2: if it.sons[j].kind == nkRange: - localError(it.info, "range notation not available for computed goto") + localError(p.config, it.info, "range notation not available for computed goto") return let val = getOrdValue(it.sons[j]) lineF(p, cpsStmts, "TMP$#_:$n", [intLiteral(val+id+1)]) @@ -489,7 +490,7 @@ proc genWhileStmt(p: BProc, t: PNode) = lineF(p, cpsStmts, "if (!$1) goto $2;$n", [rdLoc(a), label]) var loopBody = t.sons[1] if loopBody.stmtsContainPragma(wComputedGoto) and - hasComputedGoto in CC[cCompiler].props: + hasComputedGoto in CC[p.config.cCompiler].props: # for closure support weird loop bodies are generated: if loopBody.len == 2 and loopBody.sons[0].kind == nkEmpty: loopBody = loopBody.sons[1] @@ -560,33 +561,38 @@ proc genBreakStmt(p: BProc, t: PNode) = # an unnamed 'break' can only break a loop after 'transf' pass: while idx >= 0 and not p.blocks[idx].isLoop: dec idx if idx < 0 or not p.blocks[idx].isLoop: - internalError(t.info, "no loop to break") + internalError(p.config, t.info, "no loop to break") let label = assignLabel(p.blocks[idx]) blockLeaveActions(p, p.nestedTryStmts.len - p.blocks[idx].nestedTryStmts, - p.inExceptBlock - p.blocks[idx].nestedExceptStmts) + p.inExceptBlockLen - p.blocks[idx].nestedExceptStmts) genLineDir(p, t) lineF(p, cpsStmts, "goto $1;$n", [label]) proc genRaiseStmt(p: BProc, t: PNode) = - if p.inExceptBlock > 0 and p.inExceptBlock == p.nestedTryStmts.len: + if p.module.compileToCpp: + discard cgsym(p.module, "popCurrentExceptionEx") + if p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept: # if the current try stmt have a finally block, # we must execute it before reraising - var finallyBlock = p.nestedTryStmts[p.nestedTryStmts.len - 1].lastSon + var finallyBlock = p.nestedTryStmts[^1].n[^1] if finallyBlock.kind == nkFinally: - genSimpleBlock(p, finallyBlock.sons[0]) - if t.sons[0].kind != nkEmpty: + genSimpleBlock(p, finallyBlock[0]) + if t[0].kind != nkEmpty: var a: TLoc - initLocExpr(p, t.sons[0], a) + initLocExprSingleUse(p, t[0], a) var e = rdLoc(a) - var typ = skipTypes(t.sons[0].typ, abstractPtrs) + var typ = skipTypes(t[0].typ, abstractPtrs) genLineDir(p, t) - lineCg(p, cpsStmts, "#raiseException((#Exception*)$1, $2);$n", - [e, makeCString(typ.sym.name.s)]) + if isImportedException(typ, p.config): + lineF(p, cpsStmts, "throw $1;$n", [e]) + else: + lineCg(p, cpsStmts, "#raiseException((#Exception*)$1, $2);$n", + [e, makeCString(typ.sym.name.s)]) else: genLineDir(p, t) # reraise the last exception: - if p.module.compileToCpp and optNoCppExceptions notin gGlobalOptions: + if p.module.compileToCpp and optNoCppExceptions notin p.config.globalOptions: line(p, cpsStmts, ~"throw;$n") else: linefmt(p, cpsStmts, "#reraiseException();$n") @@ -657,7 +663,7 @@ proc genCaseStringBranch(p: BProc, b: PNode, e: TLoc, labl: TLabel, assert(b.sons[i].kind != nkRange) initLocExpr(p, b.sons[i], x) assert(b.sons[i].kind in {nkStrLit..nkTripleStrLit}) - var j = int(hashString(b.sons[i].strVal) and high(branches)) + var j = int(hashString(p.config, b.sons[i].strVal) and high(branches)) appcg(p.module, branches[j], "if (#eqStrings($1, $2)) goto $3;$n", [rdLoc(e), rdLoc(x), labl]) @@ -710,7 +716,7 @@ proc ifSwitchSplitPoint(p: BProc, n: PNode): int = var stmtBlock = lastSon(branch) if stmtBlock.stmtsContainPragma(wLinearScanEnd): result = i - elif hasSwitchRange notin CC[cCompiler].props: + elif hasSwitchRange notin CC[p.config.cCompiler].props: if branch.kind == nkOfBranch and branchHasTooBigRange(branch): result = i @@ -718,7 +724,7 @@ proc genCaseRange(p: BProc, branch: PNode) = var length = branch.len for j in 0 .. length-2: if branch[j].kind == nkRange: - if hasSwitchRange in CC[cCompiler].props: + if hasSwitchRange in CC[p.config.cCompiler].props: lineF(p, cpsStmts, "case $1 ... $2:$n", [ genLiteral(p, branch[j][0]), genLiteral(p, branch[j][1])]) @@ -758,7 +764,7 @@ proc genOrdinalCase(p: BProc, n: PNode, d: var TLoc) = hasDefault = true exprBlock(p, branch.lastSon, d) lineF(p, cpsStmts, "break;$n", []) - if (hasAssume in CC[cCompiler].props) and not hasDefault: + if (hasAssume in CC[p.config.cCompiler].props) and not hasDefault: lineF(p, cpsStmts, "default: __assume(0);$n", []) lineF(p, cpsStmts, "}$n", []) if lend != nil: fixLabel(p, lend) @@ -779,6 +785,13 @@ proc genCase(p: BProc, t: PNode, d: var TLoc) = else: genOrdinalCase(p, t, d) +proc genRestoreFrameAfterException(p: BProc) = + if optStackTrace in p.module.config.options: + if not p.hasCurFramePointer: + p.hasCurFramePointer = true + p.procSec(cpsLocals).add(ropecg(p.module, "\tTFrame* _nimCurFrame;$n", [])) + p.procSec(cpsInit).add(ropecg(p.module, "\t_nimCurFrame = #getFrame();$n", [])) + linefmt(p, cpsStmts, "#setFrame(_nimCurFrame);$n") proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = # code to generate: @@ -796,27 +809,23 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = # general_handler_body # } # finallyPart(); - + template genExceptBranchBody(body: PNode) {.dirty.} = - if optStackTrace in p.options: - linefmt(p, cpsStmts, "#setFrame((TFrame*)&FR_);$n") + genRestoreFrameAfterException(p) expr(p, body, d) - linefmt(p, cpsStmts, "#popCurrentException();$n") - + if not isEmptyType(t.typ) and d.k == locNone: getTemp(p, t.typ, d) genLineDir(p, t) - - let end_label = getLabel(p) - discard cgsym(p.module, "Exception") - add(p.nestedTryStmts, t) + discard cgsym(p.module, "popCurrentExceptionEx") + add(p.nestedTryStmts, (t, false)) startBlock(p, "try {$n") expr(p, t[0], d) endBlock(p) var catchAllPresent = false - inc p.inExceptBlock + p.nestedTryStmts[^1].inExcept = true for i in 1..<t.len: if t[i].kind != nkExceptBranch: break @@ -831,11 +840,17 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = endBlock(p) else: for j in 0..t[i].len-2: - assert(t[i][j].kind == nkType) - startBlock(p, "catch ($1*) {$n", getTypeDesc(p.module, t[i][j].typ)) + if t[i][j].isInfixAs(): + let exvar = t[i][j][2] # ex1 in `except ExceptType as ex1:` + fillLoc(exvar.sym.loc, locTemp, exvar, mangleLocalName(p, exvar.sym), OnUnknown) + startBlock(p, "catch ($1& $2) {$n", getTypeDesc(p.module, t[i][j][1].typ), rdLoc(exvar.sym.loc)) + else: + startBlock(p, "catch ($1&) {$n", getTypeDesc(p.module, t[i][j].typ)) genExceptBranchBody(t[i][^1]) # exception handler body will duplicated for every type endBlock(p) + discard pop(p.nestedTryStmts) + if not catchAllPresent and t[^1].kind == nkFinally: # finally requires catch all presence startBlock(p, "catch (...) {$n") @@ -843,9 +858,6 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = line(p, cpsStmts, ~"throw;$n") endBlock(p) - dec p.inExceptBlock - discard pop(p.nestedTryStmts) - if t[^1].kind == nkFinally: genSimpleBlock(p, t[^1][0]) @@ -883,31 +895,27 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = p.module.includeHeader("<setjmp.h>") genLineDir(p, t) var safePoint = getTempName(p.module) - if getCompilerProc("Exception") != nil: - discard cgsym(p.module, "Exception") - else: - discard cgsym(p.module, "E_Base") + discard cgsym(p.module, "Exception") linefmt(p, cpsLocals, "#TSafePoint $1;$n", safePoint) linefmt(p, cpsStmts, "#pushSafePoint(&$1);$n", safePoint) - if isDefined("nimStdSetjmp"): + if isDefined(p.config, "nimStdSetjmp"): linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", safePoint) - elif isDefined("nimSigSetjmp"): + elif isDefined(p.config, "nimSigSetjmp"): linefmt(p, cpsStmts, "$1.status = sigsetjmp($1.context, 0);$n", safePoint) - elif isDefined("nimRawSetjmp"): + elif isDefined(p.config, "nimRawSetjmp"): linefmt(p, cpsStmts, "$1.status = _setjmp($1.context);$n", safePoint) else: linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", safePoint) startBlock(p, "if ($1.status == 0) {$n", [safePoint]) var length = sonsLen(t) - add(p.nestedTryStmts, t) + add(p.nestedTryStmts, (t, false)) expr(p, t.sons[0], d) linefmt(p, cpsStmts, "#popSafePoint();$n") endBlock(p) startBlock(p, "else {$n") linefmt(p, cpsStmts, "#popSafePoint();$n") - if optStackTrace in p.options: - linefmt(p, cpsStmts, "#setFrame((TFrame*)&FR_);$n") - inc p.inExceptBlock + genRestoreFrameAfterException(p) + p.nestedTryStmts[^1].inExcept = true var i = 1 while (i < length) and (t.sons[i].kind == nkExceptBranch): # bug #4230: avoid false sharing between branches: @@ -938,7 +946,6 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = linefmt(p, cpsStmts, "#popCurrentException();$n") endBlock(p) inc(i) - dec p.inExceptBlock discard pop(p.nestedTryStmts) endBlock(p) # end of else block if i < length and t.sons[i].kind == nkFinally: @@ -949,19 +956,20 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): Rope = var res = "" - for i in countup(0, sonsLen(t) - 1): - case t.sons[i].kind + for it in t.sons: + case it.kind of nkStrLit..nkTripleStrLit: - res.add(t.sons[i].strVal) + res.add(it.strVal) of nkSym: - var sym = t.sons[i].sym + var sym = it.sym if sym.kind in {skProc, skFunc, skIterator, skMethod}: var a: TLoc - initLocExpr(p, t.sons[i], a) + initLocExpr(p, it, a) res.add($rdLoc(a)) elif sym.kind == skType: res.add($getTypeDesc(p.module, sym.typ)) else: + discard getTypeDesc(p.module, skipTypes(sym.typ, abstractPtrs)) var r = sym.loc.r if r == nil: # if no name has already been given, @@ -970,28 +978,29 @@ proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): Rope = sym.loc.r = r # but be consequent! res.add($r) of nkTypeOfExpr: - res.add($getTypeDesc(p.module, t.sons[i].typ)) + res.add($getTypeDesc(p.module, it.typ)) else: + discard getTypeDesc(p.module, skipTypes(it.typ, abstractPtrs)) var a: TLoc - initLocExpr(p, t.sons[i], a) + initLocExpr(p, it, a) res.add($a.rdLoc) - #internalError(t.sons[i].info, "genAsmOrEmitStmt()") - if isAsmStmt and hasGnuAsm in CC[cCompiler].props: + if isAsmStmt and hasGnuAsm in CC[p.config.cCompiler].props: for x in splitLines(res): var j = 0 - while x[j] in {' ', '\t'}: inc(j) - if x[j] in {'"', ':'}: - # don't modify the line if already in quotes or - # some clobber register list: - add(result, x); add(result, tnl) - elif x[j] != '\0': - # ignore empty lines - add(result, "\"") - add(result, x) - add(result, "\\n\"\n") + while j < x.len and x[j] in {' ', '\t'}: inc(j) + if j < x.len: + if x[j] in {'"', ':'}: + # don't modify the line if already in quotes or + # some clobber register list: + add(result, x); add(result, "\L") + else: + # ignore empty lines + add(result, "\"") + add(result, x) + add(result, "\\n\"\n") else: - res.add(tnl) + res.add("\L") result = res.rope proc genAsmStmt(p: BProc, t: PNode) = @@ -1003,9 +1012,9 @@ proc genAsmStmt(p: BProc, t: PNode) = # work: if p.prc == nil: # top level asm statement? - addf(p.module.s[cfsProcHeaders], CC[cCompiler].asmStmtFrmt, [s]) + addf(p.module.s[cfsProcHeaders], CC[p.config.cCompiler].asmStmtFrmt, [s]) else: - lineF(p, cpsStmts, CC[cCompiler].asmStmtFrmt, [s]) + lineF(p, cpsStmts, CC[p.config.cCompiler].asmStmtFrmt, [s]) proc determineSection(n: PNode): TCFileSection = result = cfsProcHeaders @@ -1020,7 +1029,7 @@ proc genEmit(p: BProc, t: PNode) = if p.prc == nil: # top level emit pragma? let section = determineSection(t[1]) - genCLineDir(p.module.s[section], t.info) + genCLineDir(p.module.s[section], t.info, p.config) add(p.module.s[section], s) else: genLineDir(p, t) @@ -1038,7 +1047,7 @@ proc genBreakPoint(p: BProc, t: PNode) = genLineDir(p, t) # BUGFIX appcg(p.module, p.module.g.breakpoints, "#dbgRegisterBreakpoint($1, (NCSTRING)$2, (NCSTRING)$3);$n", [ - rope(toLinenumber(t.info)), makeCString(toFilename(t.info)), + rope(toLinenumber(t.info)), makeCString(toFilename(p.config, t.info)), makeCString(name)]) proc genWatchpoint(p: BProc, n: PNode) = @@ -1047,12 +1056,11 @@ proc genWatchpoint(p: BProc, n: PNode) = initLocExpr(p, n.sons[1], a) let typ = skipTypes(n.sons[1].typ, abstractVarRange) lineCg(p, cpsStmts, "#dbgRegisterWatchpoint($1, (NCSTRING)$2, $3);$n", - [a.addrLoc, makeCString(renderTree(n.sons[1])), + [addrLoc(p.config, a), makeCString(renderTree(n.sons[1])), genTypeInfo(p.module, typ, n.info)]) proc genPragma(p: BProc, n: PNode) = - for i in countup(0, sonsLen(n) - 1): - var it = n.sons[i] + for it in n.sons: case whichPragma(it) of wEmit: genEmit(p, it) of wBreakpoint: genBreakPoint(p, it) @@ -1079,7 +1087,7 @@ proc genDiscriminantCheck(p: BProc, a, tmp: TLoc, objtype: PType, var t = skipTypes(objtype, abstractVar) assert t.kind == tyObject discard genTypeInfo(p.module, t, a.lode.info) - var L = lengthOrd(field.typ) + var L = lengthOrd(p.config, field.typ) if not containsOrIncl(p.module.declaredThings, field.id): appcg(p.module, cfsVars, "extern $1", discriminatorTableDecl(p.module, t, field)) @@ -1130,8 +1138,8 @@ proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) = patchAsgnStmtListExpr(patchedTree, e, ri) genStmts(p, patchedTree) return - var a: TLoc + discard getTypeDesc(p.module, le.typ.skipTypes(skipPtrs)) if le.kind in {nkDerefExpr, nkHiddenDeref}: genDeref(p, le, a, enforceDeref=true) else: @@ -1146,5 +1154,9 @@ proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) = proc genStmts(p: BProc, t: PNode) = var a: TLoc + + let isPush = hintExtendedContext in p.config.notes + if isPush: pushInfoContext(p.config, t.info) expr(p, t, a) - internalAssert a.k in {locNone, locTemp, locLocalVar} + if isPush: popInfoContext(p.config) + internalAssert p.config, a.k in {locNone, locTemp, locLocalVar} diff --git a/compiler/ccgthreadvars.nim b/compiler/ccgthreadvars.nim index 505b69eab..3e8a87041 100644 --- a/compiler/ccgthreadvars.nim +++ b/compiler/ccgthreadvars.nim @@ -12,52 +12,39 @@ # included from cgen.nim -proc emulatedThreadVars(): bool = - result = {optThreads, optTlsEmulation} <= gGlobalOptions +proc emulatedThreadVars(conf: ConfigRef): bool = + result = {optThreads, optTlsEmulation} <= conf.globalOptions proc accessThreadLocalVar(p: BProc, s: PSym) = - if emulatedThreadVars() and not p.threadVarAccessed: + if emulatedThreadVars(p.config) and not p.threadVarAccessed: p.threadVarAccessed = true incl p.module.flags, usesThreadVars addf(p.procSec(cpsLocals), "\tNimThreadVars* NimTV_;$n", []) add(p.procSec(cpsInit), ropecg(p.module, "\tNimTV_ = (NimThreadVars*) #GetThreadLocalVars();$n")) -var - nimtv: Rope # Nim thread vars; the struct body - nimtvDeps: seq[PType] = @[] # type deps: every module needs whole struct - nimtvDeclared = initIntSet() # so that every var/field exists only once - # in the struct - -# 'nimtv' is incredibly hard to modularize! Best effort is to store all thread -# vars in a ROD section and with their type deps and load them -# unconditionally... - -# nimtvDeps is VERY hard to cache because it's not a list of IDs nor can it be -# made to be one. - proc declareThreadVar(m: BModule, s: PSym, isExtern: bool) = - if emulatedThreadVars(): + if emulatedThreadVars(m.config): # we gather all thread locals var into a struct; we need to allocate # storage for that somehow, can't use the thread local storage # allocator for it :-( - if not containsOrIncl(nimtvDeclared, s.id): - nimtvDeps.add(s.loc.t) - addf(nimtv, "$1 $2;$n", [getTypeDesc(m, s.loc.t), s.loc.r]) + if not containsOrIncl(m.g.nimtvDeclared, s.id): + m.g.nimtvDeps.add(s.loc.t) + addf(m.g.nimtv, "$1 $2;$n", [getTypeDesc(m, s.loc.t), s.loc.r]) else: if isExtern: add(m.s[cfsVars], "extern ") - if optThreads in gGlobalOptions: add(m.s[cfsVars], "NIM_THREADVAR ") + if optThreads in m.config.globalOptions: add(m.s[cfsVars], "NIM_THREADVAR ") add(m.s[cfsVars], getTypeDesc(m, s.loc.t)) addf(m.s[cfsVars], " $1;$n", [s.loc.r]) proc generateThreadLocalStorage(m: BModule) = - if nimtv != nil and (usesThreadVars in m.flags or sfMainModule in m.module.flags): - for t in items(nimtvDeps): discard getTypeDesc(m, t) - addf(m.s[cfsSeqTypes], "typedef struct {$1} NimThreadVars;$n", [nimtv]) + if m.g.nimtv != nil and (usesThreadVars in m.flags or sfMainModule in m.module.flags): + for t in items(m.g.nimtvDeps): discard getTypeDesc(m, t) + addf(m.s[cfsSeqTypes], "typedef struct {$1} NimThreadVars;$n", [m.g.nimtv]) proc generateThreadVarsSize(m: BModule) = - if nimtv != nil: - let externc = if gCmd == cmdCompileToCpp or + if m.g.nimtv != nil: + let externc = if m.config.cmd == cmdCompileToCpp or sfCompileToCpp in m.module.flags: "extern \"C\" " else: "" addf(m.s[cfsProcs], diff --git a/compiler/ccgtrav.nim b/compiler/ccgtrav.nim index ad5d1c95f..c69bb2c80 100644 --- a/compiler/ccgtrav.nim +++ b/compiler/ccgtrav.nim @@ -7,8 +7,7 @@ # distribution, for details about the copyright. # -## Generates traversal procs for the C backend. Traversal procs are only an -## optimization; the GC works without them too. +## Generates traversal procs for the C backend. # included from cgen.nim @@ -17,11 +16,11 @@ type p: BProc visitorFrmt: string -proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, typ: PType) +proc genTraverseProc(c: TTraversalClosure, accessor: Rope, typ: PType) proc genCaseRange(p: BProc, branch: PNode) proc getTemp(p: BProc, t: PType, result: var TLoc; needsInit=false) -proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, n: PNode; +proc genTraverseProc(c: TTraversalClosure, accessor: Rope, n: PNode; typ: PType) = if n == nil: return case n.kind @@ -29,12 +28,12 @@ proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, n: PNode; for i in countup(0, sonsLen(n) - 1): genTraverseProc(c, accessor, n.sons[i], typ) of nkRecCase: - if (n.sons[0].kind != nkSym): internalError(n.info, "genTraverseProc") + if (n.sons[0].kind != nkSym): internalError(c.p.config, n.info, "genTraverseProc") var p = c.p let disc = n.sons[0].sym if disc.loc.r == nil: fillObjectFields(c.p.module, typ) if disc.loc.t == nil: - internalError(n.info, "genTraverseProc()") + internalError(c.p.config, n.info, "genTraverseProc()") lineF(p, cpsStmts, "switch ($1.$2) {$n", [accessor, disc.loc.r]) for i in countup(1, sonsLen(n) - 1): let branch = n.sons[i] @@ -51,9 +50,9 @@ proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, n: PNode; if field.typ.kind == tyVoid: return if field.loc.r == nil: fillObjectFields(c.p.module, typ) if field.loc.t == nil: - internalError(n.info, "genTraverseProc()") + internalError(c.p.config, n.info, "genTraverseProc()") genTraverseProc(c, "$1.$2" % [accessor, field.loc.r], field.loc.t) - else: internalError(n.info, "genTraverseProc()") + else: internalError(c.p.config, n.info, "genTraverseProc()") proc parentObj(accessor: Rope; m: BModule): Rope {.inline.} = if not m.compileToCpp: @@ -61,7 +60,8 @@ proc parentObj(accessor: Rope; m: BModule): Rope {.inline.} = else: result = accessor -proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, typ: PType) = +proc genTraverseProcSeq(c: TTraversalClosure, accessor: Rope, typ: PType) +proc genTraverseProc(c: TTraversalClosure, accessor: Rope, typ: PType) = if typ == nil: return var p = c.p @@ -70,14 +70,14 @@ proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, typ: PType) = tySink: genTraverseProc(c, accessor, lastSon(typ)) of tyArray: - let arraySize = lengthOrd(typ.sons[0]) + let arraySize = lengthOrd(c.p.config, typ.sons[0]) var i: TLoc - getTemp(p, getSysType(tyInt), i) + getTemp(p, getSysType(c.p.module.g.graph, unknownLineInfo(), tyInt), i) let oldCode = p.s(cpsStmts) linefmt(p, cpsStmts, "for ($1 = 0; $1 < $2; $1++) {$n", i.r, arraySize.rope) let oldLen = p.s(cpsStmts).len - genTraverseProc(c, rfmt(nil, "$1[$2]", accessor, i.r), typ.sons[1]) + genTraverseProc(c, ropecg(c.p.module, "$1[$2]", accessor, i.r), typ.sons[1]) if p.s(cpsStmts).len == oldLen: # do not emit dummy long loops for faster debug builds: p.s(cpsStmts) = oldCode @@ -92,42 +92,49 @@ proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, typ: PType) = of tyTuple: let typ = getUniqueType(typ) for i in countup(0, sonsLen(typ) - 1): - genTraverseProc(c, rfmt(nil, "$1.Field$2", accessor, i.rope), typ.sons[i]) - of tyRef, tyString, tySequence: + genTraverseProc(c, ropecg(c.p.module, "$1.Field$2", accessor, i.rope), typ.sons[i]) + of tyRef: lineCg(p, cpsStmts, c.visitorFrmt, accessor) + of tySequence: + if tfHasAsgn notin typ.flags: + lineCg(p, cpsStmts, c.visitorFrmt, accessor) + elif containsGarbageCollectedRef(typ.lastSon): + # destructor based seqs are themselves not traced but their data is, if + # they contain a GC'ed type: + genTraverseProcSeq(c, accessor, typ) + of tyString: + if tfHasAsgn notin typ.flags: + lineCg(p, cpsStmts, c.visitorFrmt, accessor) of tyProc: if typ.callConv == ccClosure: - lineCg(p, cpsStmts, c.visitorFrmt, rfmt(nil, "$1.ClE_0", accessor)) + lineCg(p, cpsStmts, c.visitorFrmt, ropecg(c.p.module, "$1.ClE_0", accessor)) else: discard -proc genTraverseProcSeq(c: var TTraversalClosure, accessor: Rope, typ: PType) = +proc genTraverseProcSeq(c: TTraversalClosure, accessor: Rope, typ: PType) = var p = c.p assert typ.kind == tySequence var i: TLoc - getTemp(p, getSysType(tyInt), i) + getTemp(p, getSysType(c.p.module.g.graph, unknownLineInfo(), tyInt), i) let oldCode = p.s(cpsStmts) - lineF(p, cpsStmts, "for ($1 = 0; $1 < $2->$3; $1++) {$n", - [i.r, accessor, rope(if c.p.module.compileToCpp: "len" else: "Sup.len")]) + var a: TLoc + a.r = accessor + + lineF(p, cpsStmts, "for ($1 = 0; $1 < $2; $1++) {$n", + [i.r, lenExpr(c.p, a)]) let oldLen = p.s(cpsStmts).len - genTraverseProc(c, "$1->data[$2]" % [accessor, i.r], typ.sons[0]) + genTraverseProc(c, "$1$3[$2]" % [accessor, i.r, dataField(c.p)], typ.sons[0]) if p.s(cpsStmts).len == oldLen: # do not emit dummy long loops for faster debug builds: p.s(cpsStmts) = oldCode else: lineF(p, cpsStmts, "}$n", []) -proc genTraverseProc(m: BModule, origTyp: PType; sig: SigHash; - reason: TTypeInfoReason): Rope = +proc genTraverseProc(m: BModule, origTyp: PType; sig: SigHash): Rope = var c: TTraversalClosure var p = newProc(nil, m) result = "Marker_" & getTypeName(m, origTyp, sig) var typ = origTyp.skipTypes(abstractInst) - if typ.kind == tyOpt: typ = optLowering(typ) - - case reason - of tiNew: c.visitorFrmt = "#nimGCvisit((void*)$1, op);$n" - else: assert false let header = "static N_NIMCALL(void, $1)(void* p, NI op)" % [result] @@ -136,6 +143,8 @@ proc genTraverseProc(m: BModule, origTyp: PType; sig: SigHash; lineF(p, cpsInit, "a = ($1)p;$n", [t]) c.p = p + c.visitorFrmt = "#nimGCvisit((void*)$1, op);$n" + assert typ.kind != tyTypeDesc if typ.kind == tySequence: genTraverseProcSeq(c, "a".rope, typ) @@ -160,7 +169,7 @@ proc genTraverseProcForGlobal(m: BModule, s: PSym; info: TLineInfo): Rope = var sLoc = s.loc.r result = getTempName(m) - if sfThread in s.flags and emulatedThreadVars(): + if sfThread in s.flags and emulatedThreadVars(m.config): accessThreadLocalVar(p, s) sLoc = "NimTV_->" & sLoc diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 6f9da56e3..dd79f4846 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -14,6 +14,8 @@ import sighashes from lowerings import createObj +proc genProcHeader(m: BModule, prc: PSym): Rope + proc isKeyword(w: PIdent): bool = # Nim and C++ share some keywords # it's more efficient to test the whole Nim keywords range @@ -42,40 +44,13 @@ when false: assert p.kind == skPackage result = gDebugInfo.register(p.name.s, m.name.s) -proc idOrSig(m: BModule; s: PSym): Rope = - if s.kind in routineKinds and s.typ != nil: - # signatures for exported routines are reliable enough to - # produce a unique name and this means produced C++ is more stable wrt - # Nim changes: - let sig = hashProc(s) - result = rope($sig) - #let m = if s.typ.callConv != ccInline: findPendingModule(m, s) else: m - let counter = m.sigConflicts.getOrDefault(sig) - #if sigs == "_jckmNePK3i2MFnWwZlp6Lg" and s.name.s == "contains": - # echo "counter ", counter, " ", s.id - if counter != 0: - result.add "_" & rope(counter+1) - # this minor hack is necessary to make tests/collections/thashes compile. - # The inlined hash function's original module is ambiguous so we end up - # generating duplicate names otherwise: - if s.typ.callConv == ccInline: - result.add rope(m.module.name.s) - m.sigConflicts.inc(sig) - else: - let sig = hashNonProc(s) - result = rope($sig) - let counter = m.sigConflicts.getOrDefault(sig) - if counter != 0: - result.add "_" & rope(counter+1) - m.sigConflicts.inc(sig) - proc mangleName(m: BModule; s: PSym): Rope = result = s.loc.r if result == nil: result = s.name.s.mangle.rope - add(result, m.idOrSig(s)) + add(result, idOrSig(s, m.module.name.s.mangle, m.sigConflicts)) s.loc.r = result - writeMangledName(m.ndi, s) + writeMangledName(m.ndi, s, m.config) proc mangleParamName(m: BModule; s: PSym): Rope = ## we cannot use 'sigConflicts' here since we have a BModule, not a BProc. @@ -88,7 +63,7 @@ proc mangleParamName(m: BModule; s: PSym): Rope = res.add "_0" result = res.rope s.loc.r = result - writeMangledName(m.ndi, s) + writeMangledName(m.ndi, s, m.config) proc mangleLocalName(p: BProc; s: PSym): Rope = assert s.kind in skLocalVars+{skTemp} @@ -106,7 +81,7 @@ proc mangleLocalName(p: BProc; s: PSym): Rope = result.add "_" & rope(counter+1) p.sigConflicts.inc(key) s.loc.r = result - if s.kind != skTemp: writeMangledName(p.module.ndi, s) + if s.kind != skTemp: writeMangledName(p.module.ndi, s, p.config) proc scopeMangledParam(p: BProc; param: PSym) = ## parameter generation only takes BModule, not a BProc, so we have to @@ -147,42 +122,42 @@ proc getTypeName(m: BModule; typ: PType; sig: SigHash): Rope = # check consistency: assert($typ.loc.r == $(typ.typeName & $sig)) result = typ.loc.r - if result == nil: internalError("getTypeName: " & $typ.kind) + if result == nil: internalError(m.config, "getTypeName: " & $typ.kind) -proc mapSetType(typ: PType): TCTypeKind = - case int(getSize(typ)) +proc mapSetType(conf: ConfigRef; typ: PType): TCTypeKind = + case int(getSize(conf, typ)) of 1: result = ctInt8 of 2: result = ctInt16 of 4: result = ctInt32 of 8: result = ctInt64 else: result = ctArray -proc mapType(typ: PType): TCTypeKind = +proc mapType(conf: ConfigRef; typ: PType): TCTypeKind = ## Maps a Nim type to a C type case typ.kind of tyNone, tyStmt: result = ctVoid of tyBool: result = ctBool of tyChar: result = ctChar - of tySet: result = mapSetType(typ) + of tySet: result = mapSetType(conf, typ) of tyOpenArray, tyArray, tyVarargs: result = ctArray of tyObject, tyTuple: result = ctStruct of tyUserTypeClasses: - internalAssert typ.isResolvedUserTypeClass - return mapType(typ.lastSon) + doAssert typ.isResolvedUserTypeClass + return mapType(conf, typ.lastSon) of tyGenericBody, tyGenericInst, tyGenericParam, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, tySink, tyInferred: - result = mapType(lastSon(typ)) + result = mapType(conf, lastSon(typ)) of tyEnum: - if firstOrd(typ) < 0: + if firstOrd(conf, typ) < 0: result = ctInt32 else: - case int(getSize(typ)) + case int(getSize(conf, typ)) of 1: result = ctUInt8 of 2: result = ctUInt16 of 4: result = ctInt32 of 8: result = ctInt64 - else: internalError("mapType") - of tyRange: result = mapType(typ.sons[0]) + else: result = ctInt32 + of tyRange: result = mapType(conf, typ.sons[0]) of tyPtr, tyVar, tyLent, tyRef, tyOptAsRef: var base = skipTypes(typ.lastSon, typedescInst) case base.kind @@ -194,27 +169,20 @@ proc mapType(typ: PType): TCTypeKind = else: result = ctPtr of tyPointer: result = ctPtr of tySequence: result = ctNimSeq - of tyOpt: - case optKind(typ) - of oBool: result = ctStruct - of oNil, oPtr: result = ctPtr - of oEnum: - # The 'nil' value is always negative, so we always use a signed integer - result = if getSize(typ.sons[0]) == 8: ctInt64 else: ctInt32 of tyProc: result = if typ.callConv != ccClosure: ctProc else: ctStruct of tyString: result = ctNimStr of tyCString: result = ctCString of tyInt..tyUInt64: result = TCTypeKind(ord(typ.kind) - ord(tyInt) + ord(ctInt)) of tyStatic: - if typ.n != nil: result = mapType(lastSon typ) - else: internalError("mapType") - else: internalError("mapType") + if typ.n != nil: result = mapType(conf, lastSon typ) + else: doAssert(false, "mapType") + else: doAssert(false, "mapType") -proc mapReturnType(typ: PType): TCTypeKind = +proc mapReturnType(conf: ConfigRef; typ: PType): TCTypeKind = #if skipTypes(typ, typedescInst).kind == tyArray: result = ctPtr #else: - result = mapType(typ) + result = mapType(conf, typ) proc isImportedType(t: PType): bool = result = t.sym != nil and sfImportc in t.sym.flags @@ -232,14 +200,14 @@ proc isObjLackingTypeField(typ: PType): bool {.inline.} = result = (typ.kind == tyObject) and ((tfFinal in typ.flags) and (typ.sons[0] == nil) or isPureObject(typ)) -proc isInvalidReturnType(rettype: PType): bool = +proc isInvalidReturnType(conf: ConfigRef; rettype: PType): bool = # Arrays and sets cannot be returned by a C procedure, because C is # such a poor programming language. # We exclude records with refs too. This enhances efficiency and # is necessary for proper code generation of assignments. if rettype == nil: result = true else: - case mapType(rettype) + case mapType(conf, rettype) of ctArray: result = not (skipTypes(rettype, typedescInst).kind in {tyVar, tyLent, tyRef, tyPtr}) @@ -264,14 +232,10 @@ proc cacheGetType(tab: TypeCache; sig: SigHash): Rope = result = tab.getOrDefault(sig) proc addAbiCheck(m: BModule, t: PType, name: Rope) = - if isDefined("checkabi"): - addf(m.s[cfsTypeInfo], "NIM_CHECK_SIZE($1, $2);$n", [name, rope(getSize(t))]) + if isDefined(m.config, "checkabi"): + addf(m.s[cfsTypeInfo], "NIM_CHECK_SIZE($1, $2);$n", [name, rope(getSize(m.config, t))]) -proc getTempName(m: BModule): Rope = - result = m.tmpBase & rope(m.labels) - inc m.labels - -proc ccgIntroducedPtr(s: PSym): bool = +proc ccgIntroducedPtr(conf: ConfigRef; s: PSym): bool = var pt = skipTypes(s.typ, typedescInst) assert skResult != s.kind if tfByRef in pt.flags: return true @@ -281,7 +245,7 @@ proc ccgIntroducedPtr(s: PSym): bool = if s.typ.sym != nil and sfForward in s.typ.sym.flags: # forwarded objects are *always* passed by pointers for consistency! result = true - elif (optByRef in s.options) or (getSize(pt) > platform.floatSize * 3): + elif (optByRef in s.options) or (getSize(conf, pt) > conf.target.floatSize * 3): result = true # requested anyway elif (tfFinal in pt.flags) and (pt.sons[0] == nil): result = false # no need, because no subtyping possible @@ -289,14 +253,14 @@ proc ccgIntroducedPtr(s: PSym): bool = result = true # ordinary objects are always passed by reference, # otherwise casting doesn't work of tyTuple: - result = (getSize(pt) > platform.floatSize*3) or (optByRef in s.options) + result = (getSize(conf, pt) > conf.target.floatSize*3) or (optByRef in s.options) else: result = false -proc fillResult(param: PNode) = +proc fillResult(conf: ConfigRef; param: PNode) = fillLoc(param.sym.loc, locParam, param, ~"Result", OnStack) let t = param.sym.typ - if mapReturnType(t) != ctArray and isInvalidReturnType(t): + if mapReturnType(conf, t) != ctArray and isInvalidReturnType(conf, t): incl(param.sym.loc.flags, lfIndirect) param.sym.loc.storage = OnUnknown @@ -316,8 +280,13 @@ proc getSimpleTypeDesc(m: BModule, typ: PType): Rope = of tyPointer: result = typeNameOrLiteral(m, typ, "void*") of tyString: - discard cgsym(m, "NimStringDesc") - result = typeNameOrLiteral(m, typ, "NimStringDesc*") + case detectStrVersion(m) + of 2: + discard cgsym(m, "NimStringV2") + result = typeNameOrLiteral(m, typ, "NimStringV2") + else: + discard cgsym(m, "NimStringDesc") + result = typeNameOrLiteral(m, typ, "NimStringDesc*") of tyCString: result = typeNameOrLiteral(m, typ, "NCSTRING") of tyBool: result = typeNameOrLiteral(m, typ, "NIM_BOOL") of tyChar: result = typeNameOrLiteral(m, typ, "NIM_CHAR") @@ -327,7 +296,7 @@ proc getSimpleTypeDesc(m: BModule, typ: PType): Rope = of tyDistinct, tyRange, tyOrdinal: result = getSimpleTypeDesc(m, typ.sons[0]) of tyStatic: if typ.n != nil: result = getSimpleTypeDesc(m, lastSon typ) - else: internalError("tyStatic for getSimpleTypeDesc") + else: internalError(m.config, "tyStatic for getSimpleTypeDesc") of tyGenericInst, tyAlias, tySink: result = getSimpleTypeDesc(m, lastSon typ) else: result = nil @@ -355,6 +324,10 @@ proc getForwardStructFormat(m: BModule): string = if m.compileToCpp: result = "$1 $2;$n" else: result = "typedef $1 $2 $2;$n" +proc seqStar(m: BModule): string = + if m.config.selectedGC == gcDestructors: result = "" + else: result = "*" + proc getTypeForward(m: BModule, typ: PType; sig: SigHash): Rope = result = cacheGetType(m.forwTypeCache, sig) if result != nil: return @@ -368,8 +341,10 @@ proc getTypeForward(m: BModule, typ: PType; sig: SigHash): Rope = if not isImportedType(concrete): addf(m.s[cfsForwardTypes], getForwardStructFormat(m), [structOrUnion(typ), result]) + else: + pushType(m, concrete) doAssert m.forwTypeCache[sig] == result - else: internalError("getTypeForward(" & $typ.kind & ')') + else: internalError(m.config, "getTypeForward(" & $typ.kind & ')') proc getTypeDescWeak(m: BModule; t: PType; check: var IntSet): Rope = ## like getTypeDescAux but creates only a *weak* dependency. In other words @@ -384,14 +359,11 @@ proc getTypeDescWeak(m: BModule; t: PType; check: var IntSet): Rope = result = getTypeForward(m, t, hashType(t)) pushType(m, t) of tySequence: - result = getTypeForward(m, t, hashType(t)) & "*" - pushType(m, t) - of tyOpt: - if optKind(etB) == oPtr: - result = getTypeForward(m, t, hashType(t)) & "*" - pushType(m, t) - else: + if m.config.selectedGC == gcDestructors: result = getTypeDescAux(m, t, check) + else: + result = getTypeForward(m, t, hashType(t)) & seqStar(m) + pushType(m, t) else: result = getTypeDescAux(m, t, check) @@ -406,18 +378,18 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var Rope, check: var IntSet, declareEnvironment=true; weakDep=false) = params = nil - if t.sons[0] == nil or isInvalidReturnType(t.sons[0]): + if t.sons[0] == nil or isInvalidReturnType(m.config, t.sons[0]): rettype = ~"void" else: rettype = getTypeDescAux(m, t.sons[0], check) for i in countup(1, sonsLen(t.n) - 1): - if t.n.sons[i].kind != nkSym: internalError(t.n.info, "genProcParams") + if t.n.sons[i].kind != nkSym: internalError(m.config, t.n.info, "genProcParams") var param = t.n.sons[i].sym if isCompileTimeOnly(param.typ): continue if params != nil: add(params, ~", ") fillLoc(param.loc, locParam, t.n.sons[i], mangleParamName(m, param), param.paramStorageLoc) - if ccgIntroducedPtr(param): + if ccgIntroducedPtr(m.config, param): add(params, getTypeDescWeak(m, param.typ, check)) add(params, ~"*") incl(param.loc.flags, lfIndirect) @@ -439,10 +411,10 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var Rope, addf(params, ", NI $1Len_$2", [param.loc.r, j.rope]) inc(j) arr = arr.sons[0] - if t.sons[0] != nil and isInvalidReturnType(t.sons[0]): + if t.sons[0] != nil and isInvalidReturnType(m.config, t.sons[0]): var arr = t.sons[0] if params != nil: add(params, ", ") - if mapReturnType(t.sons[0]) != ctArray: + if mapReturnType(m.config, t.sons[0]) != ctArray: add(params, getTypeDescWeak(m, arr, check)) add(params, "*") else: @@ -464,7 +436,7 @@ proc mangleRecFieldName(m: BModule; field: PSym, rectype: PType): Rope = result = field.loc.r else: result = rope(mangleField(m, field.name)) - if result == nil: internalError(field.info, "mangleRecFieldName") + if result == nil: internalError(m.config, field.info, "mangleRecFieldName") proc genRecordFieldsAux(m: BModule, n: PNode, accessExpr: Rope, rectype: PType, @@ -475,9 +447,11 @@ proc genRecordFieldsAux(m: BModule, n: PNode, for i in countup(0, sonsLen(n) - 1): add(result, genRecordFieldsAux(m, n.sons[i], accessExpr, rectype, check)) of nkRecCase: - if n.sons[0].kind != nkSym: internalError(n.info, "genRecordFieldsAux") + if n.sons[0].kind != nkSym: internalError(m.config, n.info, "genRecordFieldsAux") add(result, genRecordFieldsAux(m, n.sons[0], accessExpr, rectype, check)) - let uname = rope(mangle(n.sons[0].sym.name.s) & 'U') + # prefix mangled name with "_U" to avoid clashes with other field names, + # since identifiers are not allowed to start with '_' + let uname = rope("_U" & mangle(n.sons[0].sym.name.s)) let ae = if accessExpr != nil: "$1.$2" % [accessExpr, uname] else: uname var unionBody: Rope = nil @@ -493,17 +467,17 @@ proc genRecordFieldsAux(m: BModule, n: PNode, if tfPacked notin rectype.flags: add(unionBody, "struct {") else: - if hasAttribute in CC[cCompiler].props: + if hasAttribute in CC[m.config.cCompiler].props: add(unionBody, "struct __attribute__((__packed__)){" ) else: addf(unionBody, "#pragma pack(push, 1)$nstruct{", []) add(unionBody, a) addf(unionBody, "} $1;$n", [sname]) - if tfPacked in rectype.flags and hasAttribute notin CC[cCompiler].props: + if tfPacked in rectype.flags and hasAttribute notin CC[m.config.cCompiler].props: addf(unionBody, "#pragma pack(pop)$n", []) else: add(unionBody, genRecordFieldsAux(m, k, ae, rectype, check)) - else: internalError("genRecordFieldsAux(record case branch)") + else: internalError(m.config, "genRecordFieldsAux(record case branch)") if unionBody != nil: addf(result, "union{$n$1} $2;$n", [unionBody, uname]) of nkSym: @@ -522,7 +496,7 @@ proc genRecordFieldsAux(m: BModule, n: PNode, if fieldType.kind == tyArray and tfUncheckedArray in fieldType.flags: addf(result, "$1 $2[SEQ_DECL_SIZE];$n", [getTypeDescAux(m, fieldType.elemType, check), sname]) - elif fieldType.kind in {tySequence, tyOpt}: + elif fieldType.kind == tySequence and m.config.selectedGC != gcDestructors: # we need to use a weak dependency here for trecursive_table. addf(result, "$1 $2;$n", [getTypeDescWeak(m, field.loc.t, check), sname]) elif field.bitsize != 0: @@ -531,7 +505,7 @@ proc genRecordFieldsAux(m: BModule, n: PNode, # don't use fieldType here because we need the # tyGenericInst for C++ template support addf(result, "$1 $2;$n", [getTypeDescAux(m, field.loc.t, check), sname]) - else: internalError(n.info, "genRecordFieldsAux()") + else: internalError(m.config, n.info, "genRecordFieldsAux()") proc getRecordFields(m: BModule, typ: PType, check: var IntSet): Rope = result = genRecordFieldsAux(m, typ.n, nil, typ, check) @@ -548,10 +522,10 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope, var hasField = false if tfPacked in typ.flags: - if hasAttribute in CC[cCompiler].props: + if hasAttribute in CC[m.config.cCompiler].props: result = structOrUnion(typ) & " __attribute__((__packed__))" else: - result = "#pragma pack(push, 1)" & tnl & structOrUnion(typ) + result = "#pragma pack(push, 1)\L" & structOrUnion(typ) else: result = structOrUnion(typ) @@ -570,7 +544,15 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope, appcg(m, result, " : public $1 {$n", [getTypeDescAux(m, typ.sons[0].skipTypes(skipPtrs), check)]) if typ.isException: - appcg(m, result, "virtual void raise() {throw this;}$n") # required for polymorphic exceptions + appcg(m, result, "virtual void raise() {throw *this;}$n") # required for polymorphic exceptions + if typ.sym.magic == mException: + # Add cleanup destructor to Exception base class + appcg(m, result, "~$1() {if(this->raise_id) popCurrentExceptionEx(this->raise_id);}$n", [name]) + # hack: forward declare popCurrentExceptionEx() on top of type description, + # proper request to generate popCurrentExceptionEx not possible for 2 reasons: + # generated function will be below declared Exception type and circular dependency + # between Exception and popCurrentExceptionEx function + result = genProcHeader(m, magicsys.getCompilerProc(m.g.graph, "popCurrentExceptionEx")) & ";" & result hasField = true else: appcg(m, result, " {$n $1 Sup;$n", @@ -584,9 +566,9 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope, addf(result, "char dummy;$n", []) else: add(result, desc) - add(result, "};" & tnl) - if tfPacked in typ.flags and hasAttribute notin CC[cCompiler].props: - result.add "#pragma pack(pop)" & tnl + add(result, "};\L") + if tfPacked in typ.flags and hasAttribute notin CC[m.config.cCompiler].props: + result.add "#pragma pack(pop)\L" proc getTupleDesc(m: BModule, typ: PType, name: Rope, check: var IntSet): Rope = @@ -595,9 +577,9 @@ proc getTupleDesc(m: BModule, typ: PType, name: Rope, for i in countup(0, sonsLen(typ) - 1): addf(desc, "$1 Field$2;$n", [getTypeDescAux(m, typ.sons[i], check), rope(i)]) - if desc == nil: add(result, "char dummy;" & tnl) + if desc == nil: add(result, "char dummy;\L") else: add(result, desc) - add(result, "};" & tnl) + add(result, "};\L") proc scanCppGenericSlot(pat: string, cursor, outIdx, outStars: var int): bool = # A helper proc for handling cppimport patterns, involving numeric @@ -617,8 +599,10 @@ proc scanCppGenericSlot(pat: string, cursor, outIdx, outStars: var int): bool = return false proc resolveStarsInCppType(typ: PType, idx, stars: int): PType = - # XXX: we should catch this earlier and report it as a semantic error - if idx >= typ.len: internalError "invalid apostrophe type parameter index" + # Make sure the index refers to one of the generic params of the type. + # XXX: we should catch this earlier and report it as a semantic error. + if idx >= typ.len: + doAssert false, "invalid apostrophe type parameter index" result = typ.sons[idx] for i in 1..stars: @@ -626,12 +610,21 @@ proc resolveStarsInCppType(typ: PType, idx, stars: int): PType = result = if result.kind == tyGenericInst: result.sons[1] else: result.elemType +proc getSeqPayloadType(m: BModule; t: PType): Rope = + result = getTypeForward(m, t, hashType(t)) & "_Content" + when false: + var check = initIntSet() + # XXX remove this duplication: + appcg(m, m.s[cfsSeqTypes], + "struct $2_Content { NI cap; void* allocator; $1 data[SEQ_DECL_SIZE]; };$n", + [getTypeDescAux(m, t.sons[0], check), result]) + proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = # returns only the type's name var t = origTyp.skipTypes(irrelevantForBackend) if containsOrIncl(check, t.id): if not (isImportedCppType(origTyp) or isImportedCppType(t)): - internalError("cannot generate C type for: " & typeToString(origTyp)) + internalError(m.config, "cannot generate C type for: " & typeToString(origTyp)) # XXX: this BUG is hard to fix -> we need to introduce helper structs, # but determining when this needs to be done is hard. We should split # C type generation into an analysis and a code generation phase somehow. @@ -663,28 +656,12 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = let name = getTypeForward(m, et, hashType et) result = name & star m.typeCache[sig] = result - pushType(m, et) of tySequence: # no restriction! We have a forward declaration for structs let name = getTypeForward(m, et, hashType et) - result = name & "*" & star + result = name & seqStar(m) & star m.typeCache[sig] = result pushType(m, et) - of tyOpt: - if etB.sons[0].kind in {tyObject, tyTuple}: - let name = getTypeForward(m, et, hashType et) - result = name & "*" & star - m.typeCache[sig] = result - pushType(m, et) - elif optKind(etB) == oBool: - let name = getTypeForward(m, et, hashType et) - result = name & "*" - m.typeCache[sig] = result - pushType(m, et) - else: - # else we have a strong dependency :-( - result = getTypeDescAux(m, et, check) & star - m.typeCache[sig] = result else: # else we have a strong dependency :-( result = getTypeDescAux(m, et, check) & star @@ -700,17 +677,17 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = (sfImportc in t.sym.flags and t.sym.magic == mNone)): m.typeCache[sig] = result var size: int - if firstOrd(t) < 0: + if firstOrd(m.config, t) < 0: addf(m.s[cfsTypes], "typedef NI32 $1;$n", [result]) size = 4 else: - size = int(getSize(t)) + size = int(getSize(m.config, t)) case size of 1: addf(m.s[cfsTypes], "typedef NU8 $1;$n", [result]) of 2: addf(m.s[cfsTypes], "typedef NU16 $1;$n", [result]) of 4: addf(m.s[cfsTypes], "typedef NI32 $1;$n", [result]) of 8: addf(m.s[cfsTypes], "typedef NI64 $1;$n", [result]) - else: internalError(t.sym.info, "getTypeDescAux: enum") + else: internalError(m.config, t.sym.info, "getTypeDescAux: enum") when false: let owner = hashOwner(t.sym) if not gDebugInfo.hasEnum(t.sym.name.s, t.sym.info.line, owner): @@ -746,54 +723,31 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = [structOrUnion(t), result]) m.forwTypeCache[sig] = result assert(cacheGetType(m.typeCache, sig) == nil) - m.typeCache[sig] = result & "*" + m.typeCache[sig] = result & seqStar(m) if not isImportedType(t): if skipTypes(t.sons[0], typedescInst).kind != tyEmpty: const cppSeq = "struct $2 : #TGenericSeq {$n" cSeq = "struct $2 {$n" & " #TGenericSeq Sup;$n" - appcg(m, m.s[cfsSeqTypes], - (if m.compileToCpp: cppSeq else: cSeq) & - " $1 data[SEQ_DECL_SIZE];$n" & + if m.config.selectedGC == gcDestructors: + appcg(m, m.s[cfsTypes], + "typedef struct{ NI cap;void* allocator;$1 data[SEQ_DECL_SIZE];}$2_Content;$n" & + "struct $2 {$n" & + " NI len; $2_Content* p;$n" & "};$n", [getTypeDescAux(m, t.sons[0], check), result]) + else: + appcg(m, m.s[cfsSeqTypes], + (if m.compileToCpp: cppSeq else: cSeq) & + " $1 data[SEQ_DECL_SIZE];$n" & + "};$n", [getTypeDescAux(m, t.sons[0], check), result]) + elif m.config.selectedGC == gcDestructors: + internalError(m.config, "cannot map the empty seq type to a C type") else: result = rope("TGenericSeq") - add(result, "*") - of tyOpt: - result = cacheGetType(m.typeCache, sig) - if result == nil: - case optKind(t) - of oBool: - result = cacheGetType(m.forwTypeCache, sig) - if result == nil: - result = getTypeName(m, origTyp, sig) - addf(m.s[cfsForwardTypes], getForwardStructFormat(m), - [structOrUnion(t), result]) - m.forwTypeCache[sig] = result - appcg(m, m.s[cfsSeqTypes], "struct $2 {$n" & - " NIM_BOOL Field0;$n" & - " $1 Field1;$n" & - "};$n", [getTypeDescAux(m, t.sons[0], check), result]) - of oPtr: - let et = t.sons[0] - if et.kind in {tyTuple, tyObject}: - let name = getTypeForward(m, et, hashType et) - result = name & "*" - pushType(m, et) - else: - result = getTypeDescAux(m, t.sons[0], check) & "*" - of oNil: - result = getTypeDescAux(m, t.sons[0], check) - of oEnum: - result = getTypeName(m, origTyp, sig) - if getSize(t.sons[0]) == 8: - addf(m.s[cfsTypes], "typedef NI64 $1;$n", [result]) - else: - addf(m.s[cfsTypes], "typedef NI32 $1;$n", [result]) - m.typeCache[sig] = result + add(result, seqStar(m)) of tyArray: - var n: BiggestInt = lengthOrd(t) + var n: BiggestInt = lengthOrd(m.config, t) if n <= 0: n = 1 # make an array of at least one element result = getTypeName(m, origTyp, sig) m.typeCache[sig] = result @@ -804,11 +758,19 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = else: addAbiCheck(m, t, result) of tyObject, tyTuple: if isImportedCppType(t) and origTyp.kind == tyGenericInst: - # for instantiated templates we do not go through the type cache as the - # the type cache is not aware of 'tyGenericInst'. let cppName = getTypeName(m, t, sig) var i = 0 var chunkStart = 0 + + template addResultType(ty: untyped) = + if ty == nil or ty.kind == tyVoid: + result.add(~"void") + elif ty.kind == tyStatic: + internalAssert m.config, ty.n != nil + result.add ty.n.renderTree + else: + result.add getTypeDescAux(m, ty, check) + while i < cppName.data.len: if cppName.data[i] == '\'': var chunkEnd = i-1 @@ -818,10 +780,7 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = chunkStart = i let typeInSlot = resolveStarsInCppType(origTyp, idx + 1, stars) - if typeInSlot == nil or typeInSlot.kind == tyVoid: - result.add(~"void") - else: - result.add getTypeDescAux(m, typeInSlot, check) + addResultType(typeInSlot) else: inc i @@ -831,11 +790,18 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = result = cppName & "<" for i in 1 .. origTyp.len-2: if i > 1: result.add(" COMMA ") - result.add(getTypeDescAux(m, origTyp.sons[i], check)) + addResultType(origTyp.sons[i]) result.add("> ") # always call for sideeffects: assert t.kind != tyTuple discard getRecordDesc(m, t, result, check) + # The resulting type will include commas and these won't play well + # with the C macros for defining procs such as N_NIMCALL. We must + # create a typedef for the type and use it in the proc signature: + let typedefName = ~"TY" & $sig + addf(m.s[cfsTypes], "typedef $1 $2;$n", [result, typedefName]) + m.typeCache[sig] = typedefName + result = typedefName else: when false: if t.sym != nil and t.sym.name.s == "KeyValuePair": @@ -869,16 +835,16 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = result = $t.kind & '_' & getTypeName(m, t.lastSon, hashType t.lastSon) m.typeCache[sig] = result if not isImportedType(t): - let s = int(getSize(t)) + let s = int(getSize(m.config, t)) case s of 1, 2, 4, 8: addf(m.s[cfsTypes], "typedef NU$2 $1;$n", [result, rope(s*8)]) else: addf(m.s[cfsTypes], "typedef NU8 $1[$2];$n", - [result, rope(getSize(t))]) + [result, rope(getSize(m.config, t))]) of tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, tySink, tyUserTypeClass, tyUserTypeClassInst, tyInferred: result = getTypeDescAux(m, lastSon(t), check) else: - internalError("getTypeDescAux(" & $t.kind & ')') + internalError(m.config, "getTypeDescAux(" & $t.kind & ')') result = nil # fixes bug #145: excl(check, t.id) @@ -918,7 +884,7 @@ template cgDeclFrmt*(s: PSym): string = s.constraint.strVal proc genProcHeader(m: BModule, prc: PSym): Rope = var rettype, params: Rope - genCLineDir(result, prc.info) + genCLineDir(result, prc.info, m.config) # using static is needed for inline procs if lfExportLib in prc.loc.flags: if isHeaderFile in m.flags: @@ -971,11 +937,11 @@ proc genTypeInfoAuxBase(m: BModule; typ, origType: PType; if flags != 0: addf(m.s[cfsTypeInit3], "$1.flags = $2;$n", [name, rope(flags)]) discard cgsym(m, "TNimType") - if isDefined("nimTypeNames"): + if isDefined(m.config, "nimTypeNames"): var typename = typeToString(if origType.typeInst != nil: origType.typeInst else: origType, preferName) if typename == "ref object" and origType.skipTypes(skipPtrs).sym != nil: - typename = "anon ref object from " & $origType.skipTypes(skipPtrs).sym.info + typename = "anon ref object from " & m.config$origType.skipTypes(skipPtrs).sym.info addf(m.s[cfsTypeInit3], "$1.name = $2;$n", [name, makeCstring typename]) discard cgsym(m, "nimTypeRoot") @@ -1003,13 +969,13 @@ proc discriminatorTableName(m: BModule, objtype: PType, d: PSym): Rope = while lookupInRecord(objtype.n, d.name) == nil: objtype = objtype.sons[0] if objtype.sym == nil: - internalError(d.info, "anonymous obj with discriminator") + internalError(m.config, d.info, "anonymous obj with discriminator") result = "NimDT_$1_$2" % [rope($hashType(objtype)), rope(d.name.s.mangle)] proc discriminatorTableDecl(m: BModule, objtype: PType, d: PSym): Rope = discard cgsym(m, "TNimNode") var tmp = discriminatorTableName(m, objtype, d) - result = "TNimNode* $1[$2];$n" % [tmp, rope(lengthOrd(d.typ)+1)] + result = "TNimNode* $1[$2];$n" % [tmp, rope(lengthOrd(m.config, d.typ)+1)] proc genObjectFields(m: BModule, typ, origType: PType, n: PNode, expr: Rope; info: TLineInfo) = @@ -1033,11 +999,11 @@ proc genObjectFields(m: BModule, typ, origType: PType, n: PNode, expr: Rope; assert(n.sons[0].kind == nkSym) var field = n.sons[0].sym var tmp = discriminatorTableName(m, typ, field) - var L = lengthOrd(field.typ) + var L = lengthOrd(m.config, field.typ) assert L > 0 if field.loc.r == nil: fillObjectFields(m, typ) if field.loc.t == nil: - internalError(n.info, "genObjectFields") + internalError(m.config, n.info, "genObjectFields") addf(m.s[cfsTypeInit3], "$1.kind = 3;$n" & "$1.offset = offsetof($2, $3);$n" & "$1.typ = $4;$n" & "$1.name = $5;$n" & "$1.sons = &$6[0];$n" & @@ -1053,7 +1019,7 @@ proc genObjectFields(m: BModule, typ, origType: PType, n: PNode, expr: Rope; case b.kind of nkOfBranch: if sonsLen(b) < 2: - internalError(b.info, "genObjectFields; nkOfBranch broken") + internalError(m.config, b.info, "genObjectFields; nkOfBranch broken") for j in countup(0, sonsLen(b) - 2): if b.sons[j].kind == nkRange: var x = int(getOrdValue(b.sons[j].sons[0])) @@ -1067,23 +1033,23 @@ proc genObjectFields(m: BModule, typ, origType: PType, n: PNode, expr: Rope; of nkElse: addf(m.s[cfsTypeInit3], "$1[$2] = &$3;$n", [tmp, rope(L), tmp2]) - else: internalError(n.info, "genObjectFields(nkRecCase)") + else: internalError(m.config, n.info, "genObjectFields(nkRecCase)") of nkSym: var field = n.sym if field.bitsize == 0: if field.loc.r == nil: fillObjectFields(m, typ) if field.loc.t == nil: - internalError(n.info, "genObjectFields") + internalError(m.config, n.info, "genObjectFields") addf(m.s[cfsTypeInit3], "$1.kind = 1;$n" & "$1.offset = offsetof($2, $3);$n" & "$1.typ = $4;$n" & "$1.name = $5;$n", [expr, getTypeDesc(m, origType), field.loc.r, genTypeInfo(m, field.typ, info), makeCString(field.name.s)]) - else: internalError(n.info, "genObjectFields") + else: internalError(m.config, n.info, "genObjectFields") proc genObjectInfo(m: BModule, typ, origType: PType, name: Rope; info: TLineInfo) = if typ.kind == tyObject: if incompleteType(typ): - localError(info, "request for RTTI generation for incomplete object: " & + localError(m.config, info, "request for RTTI generation for incomplete object: " & typeToString(typ)) genTypeInfoAux(m, typ, origType, name, info) else: @@ -1143,7 +1109,7 @@ proc genEnumInfo(m: BModule, typ: PType, name: Rope; info: TLineInfo) = add(enumNames, makeCString(field.name.s)) else: add(enumNames, makeCString(field.ast.strVal)) - if i < length - 1: add(enumNames, ", " & tnl) + if i < length - 1: add(enumNames, ", \L") if field.position != i or tfEnumHasHoles in typ.flags: addf(specialCases, "$1.offset = $2;$n", [elemNode, rope(field.position)]) hasHoles = true @@ -1169,24 +1135,20 @@ proc genSetInfo(m: BModule, typ: PType, name: Rope; info: TLineInfo) = genTypeInfoAux(m, typ, typ, name, info) var tmp = getNimNode(m) addf(m.s[cfsTypeInit3], "$1.len = $2; $1.kind = 0;$n" & "$3.node = &$1;$n", - [tmp, rope(firstOrd(typ)), name]) + [tmp, rope(firstOrd(m.config, typ)), name]) proc genArrayInfo(m: BModule, typ: PType, name: Rope; info: TLineInfo) = genTypeInfoAuxBase(m, typ, typ, name, genTypeInfo(m, typ.sons[1], info), info) -proc fakeClosureType(owner: PSym): PType = +proc fakeClosureType(m: BModule; owner: PSym): PType = # we generate the same RTTI as for a tuple[pointer, ref tuple[]] result = newType(tyTuple, owner) result.rawAddSon(newType(tyPointer, owner)) var r = newType(tyRef, owner) - let obj = createObj(owner, owner.info, final=false) + let obj = createObj(m.g.graph, owner, owner.info, final=false) r.rawAddSon(obj) result.rawAddSon(r) -type - TTypeInfoReason = enum ## for what do we need the type info? - tiNew, ## for 'new' - include ccgtrav proc genDeepCopyProc(m: BModule; s: PSym; result: Rope) = @@ -1197,8 +1159,6 @@ proc genDeepCopyProc(m: BModule; s: PSym; result: Rope) = proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope = let origType = t var t = skipTypes(origType, irrelevantForBackend + tyUserTypeClasses) - if t.kind == tyOpt: - return genTypeInfo(m, optLowering(t), info) let sig = hashType(origType) result = m.typeInfoMarker.getOrDefault(sig) @@ -1234,20 +1194,26 @@ proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope = genTypeInfoAuxBase(m, t, t, result, rope"0", info) of tyStatic: if t.n != nil: result = genTypeInfo(m, lastSon t, info) - else: internalError("genTypeInfo(" & $t.kind & ')') + else: internalError(m.config, "genTypeInfo(" & $t.kind & ')') of tyUserTypeClasses: - internalAssert t.isResolvedUserTypeClass + internalAssert m.config, t.isResolvedUserTypeClass return genTypeInfo(m, t.lastSon, info) of tyProc: if t.callConv != ccClosure: genTypeInfoAuxBase(m, t, t, result, rope"0", info) else: - let x = fakeClosureType(t.owner) + let x = fakeClosureType(m, t.owner) genTupleInfo(m, x, x, result, info) - of tySequence, tyRef, tyOptAsRef: + of tySequence: + if tfHasAsgn notin t.flags: + genTypeInfoAux(m, t, t, result, info) + if m.config.selectedGC >= gcMarkAndSweep: + let markerProc = genTraverseProc(m, origType, sig) + addf(m.s[cfsTypeInit3], "$1.marker = $2;$n", [result, markerProc]) + of tyRef, tyOptAsRef: genTypeInfoAux(m, t, t, result, info) - if gSelectedGC >= gcMarkAndSweep: - let markerProc = genTraverseProc(m, origType, sig, tiNew) + if m.config.selectedGC >= gcMarkAndSweep: + let markerProc = genTraverseProc(m, origType, sig) addf(m.s[cfsTypeInit3], "$1.marker = $2;$n", [result, markerProc]) of tyPtr, tyRange: genTypeInfoAux(m, t, t, result, info) of tyArray: genArrayInfo(m, t, result, info) @@ -1260,7 +1226,7 @@ proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope = # BUGFIX: use consistently RTTI without proper field names; otherwise # results are not deterministic! genTupleInfo(m, t, origType, result, info) - else: internalError("genTypeInfo(" & $t.kind & ')') + else: internalError(m.config, "genTypeInfo(" & $t.kind & ')') if t.deepCopy != nil: genDeepCopyProc(m, t.deepCopy, result) elif origType.deepCopy != nil: diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim index fe28d2209..75cd3d35d 100644 --- a/compiler/ccgutils.nim +++ b/compiler/ccgutils.nim @@ -27,9 +27,9 @@ proc getPragmaStmt*(n: PNode, w: TSpecialWord): PNode = proc stmtsContainPragma*(n: PNode, w: TSpecialWord): bool = result = getPragmaStmt(n, w) != nil -proc hashString*(s: string): BiggestInt = +proc hashString*(conf: ConfigRef; s: string): BiggestInt = # has to be the same algorithm as system.hashString! - if CPU[targetCPU].bit == 64: + if CPU[conf.target.targetCPU].bit == 64: # we have to use the same bitwidth # as the target CPU var b = 0'i64 @@ -52,117 +52,12 @@ proc hashString*(s: string): BiggestInt = a = a +% `shl`(a, 15'i32) result = a -var - gTypeTable: array[TTypeKind, TIdTable] - gCanonicalTypes: array[TTypeKind, PType] - -proc initTypeTables() = - for i in countup(low(TTypeKind), high(TTypeKind)): initIdTable(gTypeTable[i]) - -proc resetCaches* = - ## XXX: fix that more properly - initTypeTables() - for i in low(gCanonicalTypes)..high(gCanonicalTypes): - gCanonicalTypes[i] = nil - -when false: - proc echoStats*() = - for i in countup(low(TTypeKind), high(TTypeKind)): - echo i, " ", gTypeTable[i].counter - -proc slowSearch(key: PType; k: TTypeKind): PType = - # tuples are quite horrible as C does not support them directly and - # tuple[string, string] is a (strange) subtype of - # tuple[nameA, nameB: string]. This bites us here, so we - # use 'sameBackendType' instead of 'sameType'. - if idTableHasObjectAsKey(gTypeTable[k], key): return key - for h in countup(0, high(gTypeTable[k].data)): - var t = PType(gTypeTable[k].data[h].key) - if t != nil and sameBackendType(t, key): - return t - idTablePut(gTypeTable[k], key, key) - result = key - -proc getUniqueType*(key: PType): PType = - # this is a hotspot in the compiler! - result = key - when false: - if key == nil: return - var k = key.kind - case k - of tyBool, tyChar, tyInt..tyUInt64: - # no canonicalization for integral types, so that e.g. ``pid_t`` is - # produced instead of ``NI``. - result = key - of tyEmpty, tyNil, tyExpr, tyStmt, tyPointer, tyString, - tyCString, tyNone, tyVoid: - result = gCanonicalTypes[k] - if result == nil: - gCanonicalTypes[k] = key - result = key - of tyTypeDesc, tyTypeClasses, tyGenericParam, tyFromExpr: - if key.isResolvedUserTypeClass: - return getUniqueType(lastSon(key)) - if key.sym != nil: - internalError(key.sym.info, "metatype not eliminated") - else: - internalError("metatype not eliminated") - of tyDistinct: - if key.deepCopy != nil: result = key - else: result = getUniqueType(lastSon(key)) - of tyGenericInst, tyOrdinal, tyStatic, tyAlias, tySink, tyInferred: - result = getUniqueType(lastSon(key)) - #let obj = lastSon(key) - #if obj.sym != nil and obj.sym.name.s == "TOption": - # echo "for ", typeToString(key), " I returned " - # debug result - of tyPtr, tyRef, tyVar, tyLent: - let elemType = lastSon(key) - if elemType.kind in {tyBool, tyChar, tyInt..tyUInt64}: - # no canonicalization for integral types, so that e.g. ``ptr pid_t`` is - # produced instead of ``ptr NI``. - result = key - else: - result = slowSearch(key, k) - of tyGenericInvocation, tyGenericBody, - tyOpenArray, tyArray, tySet, tyRange, tyTuple, - tySequence, tyForward, tyVarargs, tyProxy, tyOpt: - # we have to do a slow linear search because types may need - # to be compared by their structure: - result = slowSearch(key, k) - of tyObject: - if tfFromGeneric notin key.flags: - # fast case; lookup per id suffices: - result = PType(idTableGet(gTypeTable[k], key)) - if result == nil: - idTablePut(gTypeTable[k], key, key) - result = key - else: - # ugly slow case: need to compare by structure - if idTableHasObjectAsKey(gTypeTable[k], key): return key - for h in countup(0, high(gTypeTable[k].data)): - var t = PType(gTypeTable[k].data[h].key) - if t != nil and sameBackendType(t, key): - return t - idTablePut(gTypeTable[k], key, key) - result = key - of tyEnum: - result = PType(idTableGet(gTypeTable[k], key)) - if result == nil: - idTablePut(gTypeTable[k], key, key) - result = key - of tyProc: - if key.callConv != ccClosure: - result = key - else: - # ugh, we need the canon here: - result = slowSearch(key, k) - of tyUnused, tyOptAsRef, tyUnused1, tyUnused2: internalError("getUniqueType") +template getUniqueType*(key: PType): PType = key proc makeSingleLineCString*(s: string): string = result = "\"" for c in items(s): - result.add(c.toCChar) + c.toCChar(result) result.add('\"') proc mangle*(name: string): string = @@ -210,9 +105,3 @@ proc mangle*(name: string): string = requiresUnderscore = true if requiresUnderscore: result.add "_" - -proc emitLazily*(s: PSym): bool {.inline.} = - result = optDeadCodeElim in gGlobalOptions or - sfDeadCodeElim in getModule(s).flags - -initTypeTables() diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 8b3da223f..dea8b1e8a 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -12,13 +12,15 @@ import ast, astalgo, hashes, trees, platform, magicsys, extccomp, options, intsets, nversion, nimsets, msgs, std / sha1, bitsets, idents, types, - ccgutils, os, ropes, math, passes, rodread, wordrecg, treetab, cgmeth, + ccgutils, os, ropes, math, passes, wordrecg, treetab, cgmeth, condsyms, rodutils, renderer, idgen, cgendata, ccgmerge, semfold, aliases, - lowerings, semparallel, tables, sets, ndi + lowerings, semparallel, tables, sets, ndi, lineinfos import strutils except `%` # collides with ropes.`%` from modulegraphs import ModuleGraph +from lineinfos import + warnGcMem, errXMustBeCompileTime, hintDependency, errGenerated, errCannotOpenFile import dynlib when not declared(dynlib.libCandidates): @@ -78,11 +80,6 @@ proc isSimpleConst(typ: PType): bool = {tyTuple, tyObject, tyArray, tySet, tySequence} and not (t.kind == tyProc and t.callConv == ccClosure) -proc useStringh(m: BModule) = - if includesStringh notin m.flags: - incl m.flags, includesStringh - m.includeHeader("<string.h>") - proc useHeader(m: BModule, sym: PSym) = if lfHeader in sym.loc.flags: assert(sym.annex != nil) @@ -92,6 +89,7 @@ proc useHeader(m: BModule, sym: PSym) = proc cgsym(m: BModule, name: string): Rope proc ropecg(m: BModule, frmt: FormatStr, args: varargs[Rope]): Rope = + assert m != nil var i = 0 var length = len(frmt) result = nil @@ -115,15 +113,15 @@ proc ropecg(m: BModule, frmt: FormatStr, args: varargs[Rope]): Rope = if i >= length or not (frmt[i] in {'0'..'9'}): break num = j if j > high(args) + 1: - internalError("ropes: invalid format string $" & $j) + internalError(m.config, "ropes: invalid format string $" & $j) add(result, args[j-1]) of 'n': - if optLineDir notin gOptions: add(result, rnl) + if optLineDir notin m.config.options: add(result, "\L") inc(i) of 'N': - add(result, rnl) + add(result, "\L") inc(i) - else: internalError("ropes: invalid format string $" & frmt[i]) + else: internalError(m.config, "ropes: invalid format string $" & frmt[i]) elif frmt[i] == '#' and frmt[i+1] in IdentStartChars: inc(i) var j = i @@ -145,15 +143,10 @@ proc ropecg(m: BModule, frmt: FormatStr, args: varargs[Rope]): Rope = if i - 1 >= start: add(result, substr(frmt, start, i - 1)) -template rfmt(m: BModule, fmt: string, args: varargs[Rope]): untyped = - ropecg(m, fmt, args) - -var indent = "\t".rope - proc indentLine(p: BProc, r: Rope): Rope = result = r for i in countup(0, p.blocks.len-1): - prepend(result, indent) + prepend(result, "\t".rope) proc appcg(m: BModule, c: var Rope, frmt: FormatStr, args: varargs[Rope]) = @@ -189,14 +182,14 @@ proc safeLineNm(info: TLineInfo): int = result = toLinenumber(info) if result < 0: result = 0 # negative numbers are not allowed in #line -proc genCLineDir(r: var Rope, filename: string, line: int) = +proc genCLineDir(r: var Rope, filename: string, line: int; conf: ConfigRef) = assert line >= 0 - if optLineDir in gOptions: + if optLineDir in conf.options: addf(r, "$N#line $2 $1$N", [rope(makeSingleLineCString(filename)), rope(line)]) -proc genCLineDir(r: var Rope, info: TLineInfo) = - genCLineDir(r, info.toFullPath, info.safeLineNm) +proc genCLineDir(r: var Rope, info: TLineInfo; conf: ConfigRef) = + genCLineDir(r, toFullPath(conf, info), info.safeLineNm, conf) proc freshLineInfo(p: BProc; info: TLineInfo): bool = if p.lastLineInfo.line != info.line or @@ -206,50 +199,65 @@ proc freshLineInfo(p: BProc; info: TLineInfo): bool = result = true proc genLineDir(p: BProc, t: PNode) = - var tt = t - #while tt.kind in {nkStmtListExpr}+nkCallKinds: - # tt = tt.lastSon - if tt.kind in nkCallKinds and tt.len > 1: - tt = tt.sons[1] - let line = tt.info.safeLineNm - - if optEmbedOrigSrc in gGlobalOptions: - add(p.s(cpsStmts), ~"//" & tt.info.sourceLine & rnl) - genCLineDir(p.s(cpsStmts), tt.info.toFullPath, line) + let line = t.info.safeLineNm + + if optEmbedOrigSrc in p.config.globalOptions: + add(p.s(cpsStmts), ~"//" & sourceLine(p.config, t.info) & "\L") + genCLineDir(p.s(cpsStmts), toFullPath(p.config, t.info), line, p.config) if ({optStackTrace, optEndb} * p.options == {optStackTrace, optEndb}) and (p.prc == nil or sfPure notin p.prc.flags): - if freshLineInfo(p, tt.info): + if freshLineInfo(p, t.info): linefmt(p, cpsStmts, "#endb($1, $2);$N", - line.rope, makeCString(toFilename(tt.info))) + line.rope, makeCString(toFilename(p.config, t.info))) elif ({optLineTrace, optStackTrace} * p.options == {optLineTrace, optStackTrace}) and - (p.prc == nil or sfPure notin p.prc.flags) and tt.info.fileIndex >= 0: - if freshLineInfo(p, tt.info): + (p.prc == nil or sfPure notin p.prc.flags) and t.info.fileIndex != InvalidFileIDX: + if freshLineInfo(p, t.info): linefmt(p, cpsStmts, "nimln_($1, $2);$n", - line.rope, tt.info.quotedFilename) + line.rope, quotedFilename(p.config, t.info)) proc postStmtActions(p: BProc) {.inline.} = add(p.s(cpsStmts), p.module.injectStmt) proc accessThreadLocalVar(p: BProc, s: PSym) -proc emulatedThreadVars(): bool {.inline.} +proc emulatedThreadVars(conf: ConfigRef): bool {.inline.} proc genProc(m: BModule, prc: PSym) template compileToCpp(m: BModule): untyped = - gCmd == cmdCompileToCpp or sfCompileToCpp in m.module.flags - -include "ccgtypes.nim" + m.config.cmd == cmdCompileToCpp or sfCompileToCpp in m.module.flags -# ------------------------------ Manager of temporaries ------------------ +proc getTempName(m: BModule): Rope = + result = m.tmpBase & rope(m.labels) + inc m.labels proc rdLoc(a: TLoc): Rope = # 'read' location (deref if indirect) result = a.r if lfIndirect in a.flags: result = "(*$1)" % [result] -proc addrLoc(a: TLoc): Rope = +proc lenField(p: BProc): Rope = + result = rope(if p.module.compileToCpp: "len" else: "Sup.len") + +proc lenExpr(p: BProc; a: TLoc): Rope = + if p.config.selectedGc == gcDestructors: + result = rdLoc(a) & ".len" + else: + result = "($1 ? $1->$2 : 0)" % [rdLoc(a), lenField(p)] + +proc dataField(p: BProc): Rope = + if p.config.selectedGc == gcDestructors: + result = rope".p->data" + else: + result = rope"->data" + +include ccgliterals +include ccgtypes + +# ------------------------------ Manager of temporaries ------------------ + +proc addrLoc(conf: ConfigRef; a: TLoc): Rope = result = a.r - if lfIndirect notin a.flags and mapType(a.t) != ctArray: + if lfIndirect notin a.flags and mapType(conf, a.t) != ctArray: result = "(&" & result & ")" proc rdCharLoc(a: TLoc): Rope = @@ -260,7 +268,7 @@ proc rdCharLoc(a: TLoc): Rope = proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: TLoc, takeAddr: bool) = - if p.module.compileToCpp and t.isException: + if p.module.compileToCpp and t.isException and not isDefined(p.config, "noCppExceptions"): # init vtable in Exception object for polymorphic exceptions includeHeader(p.module, "<new>") linefmt(p, section, "new ($1) $2;$n", rdLoc(a), getTypeDesc(p.module, t)) @@ -279,7 +287,7 @@ proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: TLoc, linefmt(p, section, "$1.m_type = $2;$n", r, genTypeInfo(p.module, t, a.lode.info)) of frEmbedded: # worst case for performance: - var r = if takeAddr: addrLoc(a) else: rdLoc(a) + var r = if takeAddr: addrLoc(p.config, a) else: rdLoc(a) linefmt(p, section, "#objectInit($1, $2);$n", r, genTypeInfo(p.module, t, a.lode.info)) type @@ -290,7 +298,7 @@ type proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) proc isComplexValueType(t: PType): bool {.inline.} = - let t = t.skipTypes(abstractInst) + let t = t.skipTypes(abstractInst + tyUserTypeClasses) result = t.kind in {tyArray, tySet, tyTuple, tyObject} or (t.kind == tyProc and t.callConv == ccClosure) @@ -308,34 +316,36 @@ proc resetLoc(p: BProc, loc: var TLoc) = linefmt(p, cpsStmts, "$1 = 0;$n", rdLoc(loc)) else: if optNilCheck in p.options: - linefmt(p, cpsStmts, "#chckNil((void*)$1);$n", addrLoc(loc)) + linefmt(p, cpsStmts, "#chckNil((void*)$1);$n", addrLoc(p.config, loc)) if loc.storage != OnStack: linefmt(p, cpsStmts, "#genericReset((void*)$1, $2);$n", - addrLoc(loc), genTypeInfo(p.module, loc.t, loc.lode.info)) + addrLoc(p.config, loc), genTypeInfo(p.module, loc.t, loc.lode.info)) # XXX: generated reset procs should not touch the m_type # field, so disabling this should be safe: genObjectInit(p, cpsStmts, loc.t, loc, true) else: - useStringh(p.module) - linefmt(p, cpsStmts, "memset((void*)$1, 0, sizeof($2));$n", - addrLoc(loc), rdLoc(loc)) + # array passed as argument decayed into pointer, bug #7332 + # so we use getTypeDesc here rather than rdLoc(loc) + linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n", + addrLoc(p.config, loc), getTypeDesc(p.module, loc.t)) # XXX: We can be extra clever here and call memset only # on the bytes following the m_type field? genObjectInit(p, cpsStmts, loc.t, loc, true) proc constructLoc(p: BProc, loc: TLoc, isTemp = false) = let typ = loc.t - if not isComplexValueType(typ): + if p.config.selectedGc == gcDestructors and skipTypes(typ, abstractInst).kind in {tyString, tySequence}: + linefmt(p, cpsStmts, "$1.len = 0; $1.p = NIM_NIL;$n", rdLoc(loc)) + elif not isComplexValueType(typ): linefmt(p, cpsStmts, "$1 = ($2)0;$n", rdLoc(loc), getTypeDesc(p.module, typ)) else: if not isTemp or containsGarbageCollectedRef(loc.t): - # don't use memset for temporary values for performance if we can + # don't use nimZeroMem for temporary values for performance if we can # avoid it: if not isImportedCppType(typ): - useStringh(p.module) - linefmt(p, cpsStmts, "memset((void*)$1, 0, sizeof($2));$n", - addrLoc(loc), rdLoc(loc)) + linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n", + addrLoc(p.config, loc), getTypeDesc(p.module, typ)) genObjectInit(p, cpsStmts, loc.t, loc, true) proc initLocalVar(p: BProc, v: PSym, immediateAsgn: bool) = @@ -366,7 +376,7 @@ proc getIntTemp(p: BProc, result: var TLoc) = linefmt(p, cpsLocals, "NI $1;$n", result.r) result.k = locTemp result.storage = OnStack - result.lode = lodeTyp getSysType(tyInt) + result.lode = lodeTyp getSysType(p.module.g.graph, unknownLineInfo(), tyInt) result.flags = {} proc initGCFrame(p: BProc): Rope = @@ -382,7 +392,7 @@ proc localDebugInfo(p: BProc, s: PSym) = # XXX work around a bug: No type information for open arrays possible: if skipTypes(s.typ, abstractVar).kind in {tyOpenArray, tyVarargs}: return var a = "&" & s.loc.r - if s.kind == skParam and ccgIntroducedPtr(s): a = s.loc.r + if s.kind == skParam and ccgIntroducedPtr(p.config, s): a = s.loc.r lineF(p, cpsInit, "FR_.s[$1].address = (void*)$3; FR_.s[$1].typ = $4; FR_.s[$1].name = $2;$n", [p.maxFrameLen.rope, makeCString(normalize(s.name.s)), a, @@ -410,7 +420,7 @@ proc assignLocalVar(p: BProc, n: PNode) = #assert(s.loc.k == locNone) # not yet assigned # this need not be fulfilled for inline procs; they are regenerated # for each module that uses them! - let nl = if optLineDir in gOptions: "" else: tnl + let nl = if optLineDir in p.config.options: "" else: "\L" let decl = localVarDecl(p, n) & ";" & nl line(p, cpsLocals, decl) localDebugInfo(p, n.sym) @@ -495,33 +505,30 @@ proc initLocExprSingleUse(p: BProc, e: PNode, result: var TLoc) = result.flags.incl lfSingleUse expr(p, e, result) -proc lenField(p: BProc): Rope = - result = rope(if p.module.compileToCpp: "len" else: "Sup.len") - include ccgcalls, "ccgstmts.nim" proc initFrame(p: BProc, procname, filename: Rope): Rope = discard cgsym(p.module, "nimFrame") if p.maxFrameLen > 0: discard cgsym(p.module, "VarSlot") - result = rfmt(nil, "\tnimfrs_($1, $2, $3, $4);$n", + result = ropecg(p.module, "\tnimfrs_($1, $2, $3, $4);$n", procname, filename, p.maxFrameLen.rope, p.blocks[0].frameLen.rope) else: - result = rfmt(nil, "\tnimfr_($1, $2);$n", procname, filename) + result = ropecg(p.module, "\tnimfr_($1, $2);$n", procname, filename) proc initFrameNoDebug(p: BProc; frame, procname, filename: Rope; line: int): Rope = discard cgsym(p.module, "nimFrame") addf(p.blocks[0].sections[cpsLocals], "TFrame $1;$n", [frame]) - result = rfmt(nil, "\t$1.procname = $2; $1.filename = $3; " & + result = ropecg(p.module, "\t$1.procname = $2; $1.filename = $3; " & " $1.line = $4; $1.len = -1; nimFrame(&$1);$n", frame, procname, filename, rope(line)) proc deinitFrameNoDebug(p: BProc; frame: Rope): Rope = - result = rfmt(p.module, "\t#popFrameOfAddr(&$1);$n", frame) + result = ropecg(p.module, "\t#popFrameOfAddr(&$1);$n", frame) proc deinitFrame(p: BProc): Rope = - result = rfmt(p.module, "\t#popFrame();$n") + result = ropecg(p.module, "\t#popFrame();$n") include ccgexprs @@ -544,16 +551,18 @@ proc loadDynamicLib(m: BModule, lib: PLib) = if lib.path.kind in {nkStrLit..nkTripleStrLit}: var s: TStringSeq = @[] libCandidates(lib.path.strVal, s) - rawMessage(hintDependency, lib.path.strVal) + rawMessage(m.config, hintDependency, lib.path.strVal) var loadlib: Rope = nil for i in countup(0, high(s)): inc(m.labels) if i > 0: add(loadlib, "||") - appcg(m, loadlib, "($1 = #nimLoadLibrary((#NimStringDesc*) &$2))$n", - [tmp, getStrLit(m, s[i])]) + let n = newStrNode(nkStrLit, s[i]) + n.info = lib.path.info + appcg(m, loadlib, "($1 = #nimLoadLibrary($2))$n", + [tmp, genStringLiteral(m, n)]) appcg(m, m.s[cfsDynLibInit], - "if (!($1)) #nimLoadLibraryError((#NimStringDesc*) &$2);$n", - [loadlib, getStrLit(m, lib.path.strVal)]) + "if (!($1)) #nimLoadLibraryError($2);$n", + [loadlib, genStringLiteral(m, lib.path)]) else: var p = newProc(nil, m) p.options = p.options - {optStackTrace, optEndb} @@ -566,14 +575,16 @@ proc loadDynamicLib(m: BModule, lib: PLib) = "if (!($1 = #nimLoadLibrary($2))) #nimLoadLibraryError($2);$n", [tmp, rdLoc(dest)]) - if lib.name == nil: internalError("loadDynamicLib") + if lib.name == nil: internalError(m.config, "loadDynamicLib") proc mangleDynLibProc(sym: PSym): Rope = + # we have to build this as a single rope in order not to trip the + # optimization in genInfixCall if sfCompilerProc in sym.flags: # NOTE: sym.loc.r is the external name! result = rope(sym.name.s) else: - result = "Dl_$1_" % [rope(sym.id)] + result = rope(strutils.`%`("Dl_$1_", $sym.id)) proc symInDynamicLib(m: BModule, sym: PSym) = var lib = sym.annex @@ -597,14 +608,14 @@ proc symInDynamicLib(m: BModule, sym: PSym) = [tmp, getTypeDesc(m, sym.typ), params, makeCString($extname)] var last = lastSon(n) if last.kind == nkHiddenStdConv: last = last.sons[1] - internalAssert(last.kind == nkStrLit) + internalAssert(m.config, last.kind == nkStrLit) let idx = last.strVal if idx.len == 0: add(m.initProc.s(cpsStmts), load) elif idx.len == 1 and idx[0] in {'0'..'9'}: add(m.extensionLoaders[idx[0]], load) else: - internalError(sym.info, "wrong index: " & idx) + internalError(m.config, sym.info, "wrong index: " & idx) else: appcg(m, m.s[cfsDynLibInit], "\t$1 = ($2) #nimGetProcAddr($3, $4);$n", @@ -630,49 +641,56 @@ proc symInDynamicLibPartial(m: BModule, sym: PSym) = sym.typ.sym = nil # generate a new name proc cgsym(m: BModule, name: string): Rope = - let sym = magicsys.getCompilerProc(name) + let sym = magicsys.getCompilerProc(m.g.graph, name) if sym != nil: case sym.kind of skProc, skFunc, skMethod, skConverter, skIterator: genProc(m, sym) of skVar, skResult, skLet: genVarPrototype(m, newSymNode sym) of skType: discard getTypeDesc(m, sym.typ) - else: internalError("cgsym: " & name & ": " & $sym.kind) + else: internalError(m.config, "cgsym: " & name & ": " & $sym.kind) else: # we used to exclude the system module from this check, but for DLL # generation support this sloppyness leads to hard to detect bugs, so # we're picky here for the system module too: - rawMessage(errSystemNeeds, name) + rawMessage(m.config, errGenerated, "system module needs: " & name) result = sym.loc.r proc generateHeaders(m: BModule) = - add(m.s[cfsHeaders], tnl & "#include \"nimbase.h\"" & tnl) + add(m.s[cfsHeaders], "\L#include \"nimbase.h\"\L") for it in m.headerFiles: if it[0] == '#': - add(m.s[cfsHeaders], rope(it.replace('`', '"') & tnl)) + add(m.s[cfsHeaders], rope(it.replace('`', '"') & "\L")) elif it[0] notin {'\"', '<'}: addf(m.s[cfsHeaders], "#include \"$1\"$N", [rope(it)]) else: addf(m.s[cfsHeaders], "#include $1$N", [rope(it)]) - add(m.s[cfsHeaders], "#undef LANGUAGE_C" & tnl) - add(m.s[cfsHeaders], "#undef MIPSEB" & tnl) - add(m.s[cfsHeaders], "#undef MIPSEL" & tnl) - add(m.s[cfsHeaders], "#undef PPC" & tnl) - add(m.s[cfsHeaders], "#undef R3000" & tnl) - add(m.s[cfsHeaders], "#undef R4000" & tnl) - add(m.s[cfsHeaders], "#undef i386" & tnl) - add(m.s[cfsHeaders], "#undef linux" & tnl) - add(m.s[cfsHeaders], "#undef mips" & tnl) - add(m.s[cfsHeaders], "#undef near" & tnl) - add(m.s[cfsHeaders], "#undef powerpc" & tnl) - add(m.s[cfsHeaders], "#undef unix" & tnl) + add(m.s[cfsHeaders], "#undef LANGUAGE_C\L") + add(m.s[cfsHeaders], "#undef MIPSEB\L") + add(m.s[cfsHeaders], "#undef MIPSEL\L") + add(m.s[cfsHeaders], "#undef PPC\L") + add(m.s[cfsHeaders], "#undef R3000\L") + add(m.s[cfsHeaders], "#undef R4000\L") + add(m.s[cfsHeaders], "#undef i386\L") + add(m.s[cfsHeaders], "#undef linux\L") + add(m.s[cfsHeaders], "#undef mips\L") + add(m.s[cfsHeaders], "#undef near\L") + add(m.s[cfsHeaders], "#undef far\L") + add(m.s[cfsHeaders], "#undef powerpc\L") + add(m.s[cfsHeaders], "#undef unix\L") + +proc openNamespaceNim(): Rope = + result.add("namespace Nim {\L") + +proc closeNamespaceNim(): Rope = + result.add("}\L") proc closureSetup(p: BProc, prc: PSym) = if tfCapturesEnv notin prc.typ.flags: return # prc.ast[paramsPos].last contains the type we're after: var ls = lastSon(prc.ast[paramsPos]) if ls.kind != nkSym: - internalError(prc.info, "closure generation failed") + internalError(p.config, prc.info, "closure generation failed") var env = ls.sym #echo "created environment: ", env.id, " for ", prc.name.s assignLocalVar(p, ls) @@ -687,9 +705,10 @@ proc containsResult(n: PNode): bool = for i in 0..<n.safeLen: if containsResult(n[i]): return true +const harmless = {nkConstSection, nkTypeSection, nkEmpty, nkCommentStmt} + + declarativeDefs + proc easyResultAsgn(n: PNode): PNode = - const harmless = {nkConstSection, nkTypeSection, nkEmpty, nkCommentStmt} + - declarativeDefs case n.kind of nkStmtList, nkStmtListExpr: var i = 0 @@ -705,6 +724,105 @@ proc easyResultAsgn(n: PNode): PNode = if result != nil: incl n.flags, nfPreventCg else: discard +type + InitResultEnum = enum Unknown, InitSkippable, InitRequired + +proc allPathsAsgnResult(n: PNode): InitResultEnum = + # Exceptions coming from calls don't have not be considered here: + # + # proc bar(): string = raise newException(...) + # + # proc foo(): string = + # # optimized out: 'reset(result)' + # result = bar() + # + # try: + # a = foo() + # except: + # echo "a was not written to" + # + template allPathsInBranch(it) = + let a = allPathsAsgnResult(it) + case a + of InitRequired: return InitRequired + of InitSkippable: discard + of Unknown: + # sticky, but can be overwritten by InitRequired: + result = Unknown + + result = Unknown + case n.kind + of nkStmtList, nkStmtListExpr: + for it in n: + result = allPathsAsgnResult(it) + if result != Unknown: return result + of nkAsgn, nkFastAsgn: + if n[0].kind == nkSym and n[0].sym.kind == skResult: + if not containsResult(n[1]): result = InitSkippable + else: result = InitRequired + elif containsResult(n): + result = InitRequired + of nkReturnStmt: + if n.len > 0: + result = allPathsAsgnResult(n[0]) + of nkIfStmt, nkIfExpr: + var exhaustive = false + result = InitSkippable + for it in n: + # Every condition must not use 'result': + if it.len == 2 and containsResult(it[0]): + return InitRequired + if it.len == 1: exhaustive = true + allPathsInBranch(it.lastSon) + # if the 'if' statement is not exhaustive and yet it touched 'result' + # in some way, say Unknown. + if not exhaustive: result = Unknown + of nkCaseStmt: + if containsResult(n[0]): return InitRequired + result = InitSkippable + var exhaustive = skipTypes(n[0].typ, + abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString} + for i in 1..<n.len: + let it = n[i] + allPathsInBranch(it.lastSon) + if it.kind == nkElse: exhaustive = true + if not exhaustive: result = Unknown + of nkWhileStmt: + # some dubious code can assign the result in the 'while' + # condition and that would be fine. Everything else isn't: + result = allPathsAsgnResult(n[0]) + if result == Unknown: + result = allPathsAsgnResult(n[1]) + # we cannot assume that the 'while' loop is really executed at least once: + if result == InitSkippable: result = Unknown + of harmless: + result = Unknown + of nkGotoState, nkBreakState: + # give up for now. + result = InitRequired + of nkSym: + # some path reads from 'result' before it was written to! + if n.sym.kind == skResult: result = InitRequired + of nkTryStmt: + # We need to watch out for the following problem: + # try: + # result = stuffThatRaises() + # except: + # discard "result was not set" + # + # So ... even if the assignment to 'result' is the very first + # assignment this is not good enough! The only pattern we allow for + # is 'finally: result = x' + result = InitSkippable + for it in n: + if it.kind == nkFinally: + result = allPathsAsgnResult(it.lastSon) + else: + allPathsInBranch(it.lastSon) + else: + for i in 0..<safeLen(n): + allPathsInBranch(n[i]) + proc genProcAux(m: BModule, prc: PSym) = var p = newProc(prc, m) var header = genProcHeader(m, prc) @@ -712,10 +830,10 @@ proc genProcAux(m: BModule, prc: PSym) = assert(prc.ast != nil) if sfPure notin prc.flags and prc.typ.sons[0] != nil: if resultPos >= prc.ast.len: - internalError(prc.info, "proc has no result symbol") + internalError(m.config, prc.info, "proc has no result symbol") let resNode = prc.ast.sons[resultPos] let res = resNode.sym # get result symbol - if not isInvalidReturnType(prc.typ.sons[0]): + if not isInvalidReturnType(m.config, prc.typ.sons[0]): if sfNoInit in prc.flags: incl(res.flags, sfNoInit) if sfNoInit in prc.flags and p.module.compileToCpp and (let val = easyResultAsgn(prc.getBody); val != nil): var decl = localVarDecl(p, resNode) @@ -727,11 +845,20 @@ proc genProcAux(m: BModule, prc: PSym) = assignLocalVar(p, resNode) assert(res.loc.r != nil) initLocalVar(p, res, immediateAsgn=false) - returnStmt = rfmt(nil, "\treturn $1;$n", rdLoc(res.loc)) + returnStmt = ropecg(p.module, "\treturn $1;$n", rdLoc(res.loc)) else: - fillResult(resNode) + fillResult(p.config, resNode) assignParam(p, res) - resetLoc(p, res.loc) + # We simplify 'unsureAsgn(result, nil); unsureAsgn(result, x)' + # to 'unsureAsgn(result, x)' + # Sketch why this is correct: If 'result' points to a stack location + # the 'unsureAsgn' is a nop. If it points to a global variable the + # global is either 'nil' or points to valid memory and so the RC operation + # succeeds without touching not-initialized memory. + if sfNoInit in prc.flags: discard + elif allPathsAsgnResult(prc.getBody) == InitSkippable: discard + else: + resetLoc(p, res.loc) if skipTypes(res.typ, abstractInst).kind == tyArray: #incl(res.loc.flags, lfIndirect) res.loc.storage = OnUnknown @@ -744,20 +871,20 @@ proc genProcAux(m: BModule, prc: PSym) = genStmts(p, prc.getBody) # modifies p.locals, p.init, etc. var generatedProc: Rope if sfNoReturn in prc.flags: - if hasDeclspec in extccomp.CC[extccomp.cCompiler].props: + if hasDeclspec in extccomp.CC[p.config.cCompiler].props: header = "__declspec(noreturn) " & header if sfPure in prc.flags: - if hasDeclspec in extccomp.CC[extccomp.cCompiler].props: + if hasDeclspec in extccomp.CC[p.config.cCompiler].props: header = "__declspec(naked) " & header - generatedProc = rfmt(nil, "$N$1 {$n$2$3$4}$N$N", + generatedProc = ropecg(p.module, "$N$1 {$n$2$3$4}$N$N", header, p.s(cpsLocals), p.s(cpsInit), p.s(cpsStmts)) else: - generatedProc = rfmt(nil, "$N$1 {$N", header) + generatedProc = ropecg(p.module, "$N$1 {$N", header) add(generatedProc, initGCFrame(p)) if optStackTrace in prc.options: add(generatedProc, p.s(cpsLocals)) var procname = makeCString(prc.name.s) - add(generatedProc, initFrame(p, procname, prc.info.quotedFilename)) + add(generatedProc, initFrame(p, procname, quotedFilename(p.config, prc.info))) else: add(generatedProc, p.s(cpsLocals)) if optProfiler in prc.options: @@ -776,10 +903,10 @@ proc genProcAux(m: BModule, prc: PSym) = proc requiresExternC(m: BModule; sym: PSym): bool {.inline.} = result = (sfCompileToCpp in m.module.flags and sfCompileToCpp notin sym.getModule().flags and - gCmd != cmdCompileToCpp) or ( + m.config.cmd != cmdCompileToCpp) or ( sym.flags * {sfImportc, sfInfixCall, sfCompilerProc} == {sfImportc} and sym.magic == mNone and - gCmd == cmdCompileToCpp) + m.config.cmd == cmdCompileToCpp) proc genProcPrototype(m: BModule, sym: PSym) = useHeader(m, sym) @@ -787,19 +914,19 @@ proc genProcPrototype(m: BModule, sym: PSym) = if lfDynamicLib in sym.loc.flags: if getModule(sym).id != m.module.id and not containsOrIncl(m.declaredThings, sym.id): - add(m.s[cfsVars], rfmt(nil, "extern $1 $2;$n", + add(m.s[cfsVars], ropecg(m, "extern $1 $2;$n", getTypeDesc(m, sym.loc.t), mangleDynLibProc(sym))) elif not containsOrIncl(m.declaredProtos, sym.id): var header = genProcHeader(m, sym) - if sfNoReturn in sym.flags and hasDeclspec in extccomp.CC[cCompiler].props: + if sfNoReturn in sym.flags and hasDeclspec in extccomp.CC[m.config.cCompiler].props: header = "__declspec(noreturn) " & header if sym.typ.callConv != ccInline and requiresExternC(m, sym): header = "extern \"C\" " & header - if sfPure in sym.flags and hasAttribute in CC[cCompiler].props: + if sfPure in sym.flags and hasAttribute in CC[m.config.cCompiler].props: header.add(" __attribute__((naked))") - if sfNoReturn in sym.flags and hasAttribute in CC[cCompiler].props: + if sfNoReturn in sym.flags and hasAttribute in CC[m.config.cCompiler].props: header.add(" __attribute__((noreturn))") - add(m.s[cfsProcHeaders], rfmt(nil, "$1;$n", header)) + add(m.s[cfsProcHeaders], ropecg(m, "$1;$n", header)) proc genProcNoForward(m: BModule, prc: PSym) = if lfImportCompilerProc in prc.loc.flags: @@ -885,7 +1012,7 @@ proc genProc(m: BModule, prc: PSym) = if not containsOrIncl(m.g.generatedHeader.declaredThings, prc.id): genProcAux(m.g.generatedHeader, prc) -proc genVarPrototypeAux(m: BModule, n: PNode) = +proc genVarPrototype(m: BModule, n: PNode) = #assert(sfGlobal in sym.flags) let sym = n.sym useHeader(m, sym) @@ -905,16 +1032,14 @@ proc genVarPrototypeAux(m: BModule, n: PNode) = if sfVolatile in sym.flags: add(m.s[cfsVars], " volatile") addf(m.s[cfsVars], " $1;$n", [sym.loc.r]) -proc genVarPrototype(m: BModule, n: PNode) = - genVarPrototypeAux(m, n) - -proc addIntTypes(result: var Rope) {.inline.} = - addf(result, "#define NIM_NEW_MANGLING_RULES" & tnl & - "#define NIM_INTBITS $1" & tnl, [ - platform.CPU[targetCPU].intSize.rope]) +proc addIntTypes(result: var Rope; conf: ConfigRef) {.inline.} = + addf(result, "#define NIM_NEW_MANGLING_RULES\L" & + "#define NIM_INTBITS $1\L", [ + platform.CPU[conf.target.targetCPU].intSize.rope]) + if optUseNimNamespace in conf.globalOptions: result.add("#define USE_NIM_NAMESPACE\L") -proc getCopyright(cfile: Cfile): Rope = - if optCompileOnly in gGlobalOptions: +proc getCopyright(conf: ConfigRef; cfile: Cfile): Rope = + if optCompileOnly in conf.globalOptions: result = ("/* Generated by Nim Compiler v$1 */$N" & "/* (c) " & copyrightYear & " Andreas Rumpf */$N" & "/* The generated code is subject to the original license. */$N") % @@ -926,20 +1051,20 @@ proc getCopyright(cfile: Cfile): Rope = "/* Compiled for: $2, $3, $4 */$N" & "/* Command for C compiler:$n $5 */$N") % [rope(VersionAsString), - rope(platform.OS[targetOS].name), - rope(platform.CPU[targetCPU].name), - rope(extccomp.CC[extccomp.cCompiler].name), - rope(getCompileCFileCmd(cfile))] + rope(platform.OS[conf.target.targetOS].name), + rope(platform.CPU[conf.target.targetCPU].name), + rope(extccomp.CC[conf.cCompiler].name), + rope(getCompileCFileCmd(conf, cfile))] -proc getFileHeader(cfile: Cfile): Rope = - result = getCopyright(cfile) - addIntTypes(result) +proc getFileHeader(conf: ConfigRef; cfile: Cfile): Rope = + result = getCopyright(conf, cfile) + addIntTypes(result, conf) proc genFilenames(m: BModule): Rope = discard cgsym(m, "dbgRegisterFilename") result = nil - for i in 0..<fileInfos.len: - result.addf("dbgRegisterFilename($1);$N", [fileInfos[i].projPath.makeCString]) + for i in 0..<m.config.m.fileInfos.len: + result.addf("dbgRegisterFilename($1);$N", [m.config.m.fileInfos[i].projPath.makeCString]) proc genMainProc(m: BModule) = const @@ -1025,50 +1150,56 @@ proc genMainProc(m: BModule) = "}$N$N" GenodeNimMain = - "Libc::Env *genodeEnv;$N" & + "extern Genode::Env *nim_runtime_env;$N" & + "extern void nim_component_construct(Genode::Env*);$N$N" & NimMainBody ComponentConstruct = "void Libc::Component::construct(Libc::Env &env) {$N" & - "\tgenodeEnv = &env;$N" & + "\t// Set Env used during runtime initialization$N" & + "\tnim_runtime_env = &env;$N" & "\tLibc::with_libc([&] () {$N\t" & + "\t// Initialize runtime and globals$N" & MainProcs & + "\t// Call application construct$N" & + "\t\tnim_component_construct(&env);$N" & "\t});$N" & "}$N$N" var nimMain, otherMain: FormatStr - if platform.targetOS == osWindows and - gGlobalOptions * {optGenGuiApp, optGenDynLib} != {}: - if optGenGuiApp in gGlobalOptions: + if m.config.target.targetOS == osWindows and + m.config.globalOptions * {optGenGuiApp, optGenDynLib} != {}: + if optGenGuiApp in m.config.globalOptions: nimMain = WinNimMain otherMain = WinCMain else: nimMain = WinNimDllMain otherMain = WinCDllMain m.includeHeader("<windows.h>") - elif platform.targetOS == osGenode: + elif m.config.target.targetOS == osGenode: nimMain = GenodeNimMain otherMain = ComponentConstruct - elif optGenDynLib in gGlobalOptions: + m.includeHeader("<libc/component.h>") + elif optGenDynLib in m.config.globalOptions: nimMain = PosixNimDllMain otherMain = PosixCDllMain - elif platform.targetOS == osStandalone: + elif m.config.target.targetOS == osStandalone: nimMain = PosixNimMain otherMain = StandaloneCMain else: nimMain = PosixNimMain otherMain = PosixCMain if m.g.breakpoints != nil: discard cgsym(m, "dbgRegisterBreakpoint") - if optEndb in gOptions: + if optEndb in m.config.options: m.g.breakpoints.add(m.genFilenames) let initStackBottomCall = - if platform.targetOS == osStandalone or gSelectedGC == gcNone: "".rope + if m.config.target.targetOS == osStandalone or m.config.selectedGC == gcNone: "".rope else: ropecg(m, "\t#initStackBottomWith((void *)&inner);$N") inc(m.labels) appcg(m, m.s[cfsProcs], PreMainBody, [ m.g.mainDatInit, m.g.breakpoints, m.g.otherModsInit, - if emulatedThreadVars() and platform.targetOS != osStandalone: + if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone: ropecg(m, "\t#initThreadVarsEmulation();$N") else: "".rope, @@ -1076,8 +1207,12 @@ proc genMainProc(m: BModule) = appcg(m, m.s[cfsProcs], nimMain, [m.g.mainModInit, initStackBottomCall, rope(m.labels)]) - if optNoMain notin gGlobalOptions: + if optNoMain notin m.config.globalOptions: + if optUseNimNamespace in m.config.globalOptions: + m.s[cfsProcs].add closeNamespaceNim() & "using namespace Nim;\L" + appcg(m, m.s[cfsProcs], otherMain, []) + if optUseNimNamespace in m.config.globalOptions: m.s[cfsProcs].add openNamespaceNim() proc getSomeInitName(m: PSym, suffix: string): Rope = assert m.kind == skModule @@ -1085,7 +1220,7 @@ proc getSomeInitName(m: PSym, suffix: string): Rope = if {sfSystemModule, sfMainModule} * m.flags == {}: result = m.owner.name.s.mangle.rope result.add "_" - result.add m.name.s + result.add m.name.s.mangle result.add suffix proc getInitName(m: PSym): Rope = @@ -1101,8 +1236,8 @@ proc registerModuleToMain(g: BModuleList; m: PSym) = var init = m.getInitName datInit = m.getDatInitName - addf(g.mainModProcs, "NIM_EXTERNC N_NOINLINE(void, $1)(void);$N", [init]) - addf(g.mainModProcs, "NIM_EXTERNC N_NOINLINE(void, $1)(void);$N", [datInit]) + addf(g.mainModProcs, "N_LIB_PRIVATE N_NIMCALL(void, $1)(void);$N", [init]) + addf(g.mainModProcs, "N_LIB_PRIVATE N_NIMCALL(void, $1)(void);$N", [datInit]) if sfSystemModule notin m.flags: addf(g.mainDatInit, "\t$1();$N", [datInit]) let initCall = "\t$1();$N" % [init] @@ -1113,7 +1248,7 @@ proc registerModuleToMain(g: BModuleList; m: PSym) = proc genInitCode(m: BModule) = var initname = getInitName(m.module) - var prc = "NIM_EXTERNC N_NOINLINE(void, $1)(void) {$N" % [initname] + var prc = "N_LIB_PRIVATE N_NIMCALL(void, $1)(void) {$N" % [initname] if m.typeNodes > 0: appcg(m, m.s[cfsTypeInit1], "static #TNimNode $1[$2];$n", [m.typeNodesName, rope(m.typeNodes)]) @@ -1121,13 +1256,30 @@ proc genInitCode(m: BModule) = appcg(m, m.s[cfsTypeInit1], "static #TNimType $1[$2];$n", [m.nimTypesName, rope(m.nimTypes)]) + # Give this small function its own scope + addf(prc, "{$N", []) + block: + # Keep a bogus frame in case the code needs one + add(prc, ~"\tTFrame FR_; FR_.len = 0;$N") + + add(prc, genSectionStart(cpsLocals, m.config)) + add(prc, m.preInitProc.s(cpsLocals)) + add(prc, genSectionEnd(cpsLocals, m.config)) + + add(prc, genSectionStart(cpsInit, m.config)) + add(prc, m.preInitProc.s(cpsInit)) + add(prc, genSectionEnd(cpsInit, m.config)) + + add(prc, genSectionStart(cpsStmts, m.config)) + add(prc, m.preInitProc.s(cpsStmts)) + add(prc, genSectionEnd(cpsStmts, m.config)) + addf(prc, "}$N", []) + add(prc, initGCFrame(m.initProc)) - add(prc, genSectionStart(cpsLocals)) - add(prc, m.preInitProc.s(cpsLocals)) + add(prc, genSectionStart(cpsLocals, m.config)) add(prc, m.initProc.s(cpsLocals)) - add(prc, m.postInitProc.s(cpsLocals)) - add(prc, genSectionEnd(cpsLocals)) + add(prc, genSectionEnd(cpsLocals, m.config)) if optStackTrace in m.initProc.options and frameDeclared notin m.flags: # BUT: the generated init code might depend on a current frame, so @@ -1135,33 +1287,30 @@ proc genInitCode(m: BModule) = incl m.flags, frameDeclared if preventStackTrace notin m.flags: var procname = makeCString(m.module.name.s) - add(prc, initFrame(m.initProc, procname, m.module.info.quotedFilename)) + add(prc, initFrame(m.initProc, procname, quotedFilename(m.config, m.module.info))) else: add(prc, ~"\tTFrame FR_; FR_.len = 0;$N") - add(prc, genSectionStart(cpsInit)) - add(prc, m.preInitProc.s(cpsInit)) + add(prc, genSectionStart(cpsInit, m.config)) add(prc, m.initProc.s(cpsInit)) - add(prc, m.postInitProc.s(cpsInit)) - add(prc, genSectionEnd(cpsInit)) + add(prc, genSectionEnd(cpsInit, m.config)) - add(prc, genSectionStart(cpsStmts)) - add(prc, m.preInitProc.s(cpsStmts)) + add(prc, genSectionStart(cpsStmts, m.config)) add(prc, m.initProc.s(cpsStmts)) - add(prc, m.postInitProc.s(cpsStmts)) - add(prc, genSectionEnd(cpsStmts)) + add(prc, genSectionEnd(cpsStmts, m.config)) + if optStackTrace in m.initProc.options and preventStackTrace notin m.flags: add(prc, deinitFrame(m.initProc)) add(prc, deinitGCFrame(m.initProc)) addf(prc, "}$N$N", []) - prc.addf("NIM_EXTERNC N_NOINLINE(void, $1)(void) {$N", + prc.addf("N_LIB_PRIVATE N_NIMCALL(void, $1)(void) {$N", [getDatInitName(m.module)]) for i in cfsTypeInit1..cfsDynLibInit: - add(prc, genSectionStart(i)) + add(prc, genSectionStart(i, m.config)) add(prc, m.s[i]) - add(prc, genSectionEnd(i)) + add(prc, genSectionEnd(i, m.config)) addf(prc, "}$N$N", []) # we cannot simply add the init proc to ``m.s[cfsProcs]`` anymore because @@ -1176,32 +1325,32 @@ proc genInitCode(m: BModule) = add(m.s[cfsInitProc], ex) proc genModule(m: BModule, cfile: Cfile): Rope = - result = getFileHeader(cfile) + result = getFileHeader(m.config, cfile) result.add(genMergeInfo(m)) generateThreadLocalStorage(m) generateHeaders(m) for i in countup(cfsHeaders, cfsProcs): - add(result, genSectionStart(i)) + add(result, genSectionStart(i, m.config)) add(result, m.s[i]) - add(result, genSectionEnd(i)) + add(result, genSectionEnd(i, m.config)) + if optUseNimNamespace in m.config.globalOptions and i == cfsHeaders: + result.add openNamespaceNim() add(result, m.s[cfsInitProc]) + if optUseNimNamespace in m.config.globalOptions: result.add closeNamespaceNim() proc newPreInitProc(m: BModule): BProc = result = newProc(nil, m) # little hack so that unique temporaries are generated: result.labels = 100_000 -proc newPostInitProc(m: BModule): BProc = - result = newProc(nil, m) - # little hack so that unique temporaries are generated: - result.labels = 200_000 - proc initProcOptions(m: BModule): TOptions = - if sfSystemModule in m.module.flags: gOptions-{optStackTrace} else: gOptions + let opts = m.config.options + if sfSystemModule in m.module.flags: opts-{optStackTrace} else: opts proc rawNewModule(g: BModuleList; module: PSym, filename: string): BModule = new(result) + result.g = g result.tmpBase = rope("TM" & $hashOwner(module) & "_") result.headerFiles = @[] result.declaredThings = initIntSet() @@ -1216,22 +1365,19 @@ proc rawNewModule(g: BModuleList; module: PSym, filename: string): BModule = result.initProc = newProc(nil, result) result.initProc.options = initProcOptions(result) result.preInitProc = newPreInitProc(result) - result.postInitProc = newPostInitProc(result) initNodeTable(result.dataCache) result.typeStack = @[] result.forwardedProcs = @[] result.typeNodesName = getTempName(result) result.nimTypesName = getTempName(result) - result.g = g # no line tracing for the init sections of the system module so that we # don't generate a TFrame which can confuse the stack botton initialization: if sfSystemModule in module.flags: incl result.flags, preventStackTrace excl(result.preInitProc.options, optStackTrace) - excl(result.postInitProc.options, optStackTrace) - let ndiName = if optCDebug in gGlobalOptions: changeFileExt(completeCFilePath(filename), "ndi") + let ndiName = if optCDebug in g.config.globalOptions: changeFileExt(completeCFilePath(g.config, filename), "ndi") else: "" - open(result.ndi, ndiName) + open(result.ndi, ndiName, g.config) proc nullify[T](arr: var T) = for i in low(arr)..high(arr): @@ -1246,7 +1392,6 @@ proc resetModule*(m: BModule) = m.initProc = newProc(nil, m) m.initProc.options = initProcOptions(m) m.preInitProc = newPreInitProc(m) - m.postInitProc = newPostInitProc(m) initNodeTable(m.dataCache) m.typeStack = @[] m.forwardedProcs = @[] @@ -1281,31 +1426,30 @@ proc resetModule*(m: BModule) = proc resetCgenModules*(g: BModuleList) = for m in cgenModules(g): resetModule(m) -proc rawNewModule(g: BModuleList; module: PSym): BModule = - result = rawNewModule(g, module, module.position.int32.toFullPath) +proc rawNewModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule = + result = rawNewModule(g, module, toFullPath(conf, module.position.FileIndex)) -proc newModule(g: BModuleList; module: PSym): BModule = +proc newModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule = # we should create only one cgen module for each module sym - result = rawNewModule(g, module) - growCache g.modules, module.position + result = rawNewModule(g, module, conf) + if module.position >= g.modules.len: + setLen(g.modules, module.position + 1) + #growCache g.modules, module.position g.modules[module.position] = result - if (optDeadCodeElim in gGlobalOptions): - if (sfDeadCodeElim in module.flags): - internalError("added pending module twice: " & module.filename) - -template injectG(config) {.dirty.} = +template injectG() {.dirty.} = if graph.backend == nil: - graph.backend = newModuleList(config) + graph.backend = newModuleList(graph) let g = BModuleList(graph.backend) -proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = - injectG(graph.config) - result = newModule(g, module) - if optGenIndex in gGlobalOptions and g.generatedHeader == nil: - let f = if graph.config.headerFile.len > 0: graph.config.headerFile else: gProjectFull +proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = + injectG() + result = newModule(g, module, graph.config) + if optGenIndex in graph.config.globalOptions and g.generatedHeader == nil: + let f = if graph.config.headerFile.len > 0: graph.config.headerFile + else: graph.config.projectFull g.generatedHeader = rawNewModule(g, module, - changeFileExt(completeCFilePath(f), hExt)) + changeFileExt(completeCFilePath(graph.config, f), hExt)) incl g.generatedHeader.flags, isHeaderFile proc writeHeader(m: BModule) = @@ -1316,41 +1460,47 @@ proc writeHeader(m: BModule) = var guard = "__$1__" % [m.filename.splitFile.name.rope] result.addf("#ifndef $1$n#define $1$n", [guard]) - addIntTypes(result) + addIntTypes(result, m.config) generateHeaders(m) generateThreadLocalStorage(m) for i in countup(cfsHeaders, cfsProcs): - add(result, genSectionStart(i)) + add(result, genSectionStart(i, m.config)) add(result, m.s[i]) - add(result, genSectionEnd(i)) + add(result, genSectionEnd(i, m.config)) + if optUseNimNamespace in m.config.globalOptions and i == cfsHeaders: result.add openNamespaceNim() add(result, m.s[cfsInitProc]) - if optGenDynLib in gGlobalOptions: + if optGenDynLib in m.config.globalOptions: result.add("N_LIB_IMPORT ") result.addf("N_CDECL(void, NimMain)(void);$n", []) + if optUseNimNamespace in m.config.globalOptions: result.add closeNamespaceNim() result.addf("#endif /* $1 */$n", [guard]) - writeRope(result, m.filename) + if not writeRope(result, m.filename): + rawMessage(m.config, errCannotOpenFile, m.filename) proc getCFile(m: BModule): string = let ext = if m.compileToCpp: ".cpp" - elif gCmd == cmdCompileToOC or sfCompileToObjC in m.module.flags: ".m" + elif m.config.cmd == cmdCompileToOC or sfCompileToObjC in m.module.flags: ".m" else: ".c" - result = changeFileExt(completeCFilePath(m.cfilename.withPackageName), ext) + result = changeFileExt(completeCFilePath(m.config, withPackageName(m.config, m.cfilename)), ext) -proc myOpenCached(graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext = - injectG(graph.config) - var m = newModule(g, module) - readMergeInfo(getCFile(m), m) - result = m +when false: + proc myOpenCached(graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext = + injectG() + var m = newModule(g, module, graph.config) + readMergeInfo(getCFile(m), m) + result = m proc myProcess(b: PPassContext, n: PNode): PNode = result = n - if b == nil or passes.skipCodegen(n): return + if b == nil: return var m = BModule(b) + if passes.skipCodegen(m.config, n): return m.initProc.options = initProcOptions(m) - softRnl = if optLineDir in gOptions: noRnl else: rnl + #softRnl = if optLineDir in m.config.options: noRnl else: rnl + # XXX replicate this logic! genStmts(m.initProc, n) proc finishModule(m: BModule) = @@ -1360,29 +1510,31 @@ proc finishModule(m: BModule) = # a ``for`` loop here var prc = m.forwardedProcs[i] if sfForward in prc.flags: - internalError(prc.info, "still forwarded: " & prc.name.s) + internalError(m.config, prc.info, "still forwarded: " & prc.name.s) genProcNoForward(m, prc) inc(i) assert(m.g.forwardedProcsCounter >= i) dec(m.g.forwardedProcsCounter, i) setLen(m.forwardedProcs, 0) -proc shouldRecompile(code: Rope, cfile: Cfile): bool = +proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool = result = true - if optForceFullMake notin gGlobalOptions: + if optForceFullMake notin m.config.globalOptions: if not equalsFile(code, cfile.cname): - if isDefined("nimdiff"): + if isDefined(m.config, "nimdiff"): if fileExists(cfile.cname): copyFile(cfile.cname, cfile.cname & ".backup") echo "diff ", cfile.cname, ".backup ", cfile.cname else: echo "new file ", cfile.cname - writeRope(code, cfile.cname) + if not writeRope(code, cfile.cname): + rawMessage(m.config, errCannotOpenFile, cfile.cname) return if existsFile(cfile.obj) and os.fileNewer(cfile.obj, cfile.cname): result = false else: - writeRope(code, cfile.cname) + if not writeRope(code, cfile.cname): + rawMessage(m.config, errCannotOpenFile, cfile.cname) # We need 2 different logics here: pending modules (including # 'nim__dat') may require file merging for the combination of dead code @@ -1394,7 +1546,7 @@ proc writeModule(m: BModule, pending: bool) = # generate code for the init statements of the module: let cfile = getCFile(m) - if m.rd == nil or optForceFullMake in gGlobalOptions: + if true or optForceFullMake in m.config.globalOptions: genInitCode(m) finishTypeDescriptions(m) if sfMainModule in m.module.flags: @@ -1402,35 +1554,36 @@ proc writeModule(m: BModule, pending: bool) = add(m.s[cfsProcHeaders], m.g.mainModProcs) generateThreadVarsSize(m) - var cf = Cfile(cname: cfile, obj: completeCFilePath(toObjFile(cfile)), flags: {}) + var cf = Cfile(cname: cfile, obj: completeCFilePath(m.config, toObjFile(m.config, cfile)), flags: {}) var code = genModule(m, cf) when hasTinyCBackend: - if gCmd == cmdRun: + if conf.cmd == cmdRun: tccgen.compileCCode($code) return - if not shouldRecompile(code, cf): cf.flags = {CfileFlag.Cached} - addFileToCompile(cf) + if not shouldRecompile(m, code, cf): cf.flags = {CfileFlag.Cached} + addFileToCompile(m.config, cf) elif pending and mergeRequired(m) and sfMainModule notin m.module.flags: - let cf = Cfile(cname: cfile, obj: completeCFilePath(toObjFile(cfile)), flags: {}) + let cf = Cfile(cname: cfile, obj: completeCFilePath(m.config, toObjFile(m.config, cfile)), flags: {}) mergeFiles(cfile, m) genInitCode(m) finishTypeDescriptions(m) var code = genModule(m, cf) - writeRope(code, cfile) - addFileToCompile(cf) + if not writeRope(code, cfile): + rawMessage(m.config, errCannotOpenFile, cfile) + addFileToCompile(m.config, cf) else: # Consider: first compilation compiles ``system.nim`` and produces # ``system.c`` but then compilation fails due to an error. This means # that ``system.o`` is missing, so we need to call the C compiler for it: - var cf = Cfile(cname: cfile, obj: completeCFilePath(toObjFile(cfile)), flags: {}) + var cf = Cfile(cname: cfile, obj: completeCFilePath(m.config, toObjFile(m.config, cfile)), flags: {}) if not existsFile(cf.obj): cf.flags = {CfileFlag.Cached} - addFileToCompile(cf) + addFileToCompile(m.config, cf) close(m.ndi) proc updateCachedModule(m: BModule) = let cfile = getCFile(m) - var cf = Cfile(cname: cfile, obj: completeCFilePath(toObjFile(cfile)), flags: {}) + var cf = Cfile(cname: cfile, obj: completeCFilePath(m.config, toObjFile(m.config, cfile)), flags: {}) if mergeRequired(m) and sfMainModule notin m.module.flags: mergeFiles(cfile, m) @@ -1438,19 +1591,20 @@ proc updateCachedModule(m: BModule) = finishTypeDescriptions(m) var code = genModule(m, cf) - writeRope(code, cfile) + if not writeRope(code, cfile): + rawMessage(m.config, errCannotOpenFile, cfile) else: cf.flags = {CfileFlag.Cached} - addFileToCompile(cf) + addFileToCompile(m.config, cf) proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = result = n - if b == nil or passes.skipCodegen(n): return + if b == nil: return var m = BModule(b) + if passes.skipCodegen(m.config, n): return # if the module is cached, we don't regenerate the main proc # nor the dispatchers? But if the dispatchers changed? # XXX emit the dispatchers into its own .c file? - if b.rd != nil: return if n != nil: m.initProc.options = initProcOptions(m) genStmts(m.initProc, n) @@ -1473,14 +1627,10 @@ proc cgenWriteModules*(backend: RootRef, config: ConfigRef) = if g.generatedHeader != nil: finishModule(g.generatedHeader) while g.forwardedProcsCounter > 0: for m in cgenModules(g): - if m.rd == nil: - finishModule(m) + finishModule(m) for m in cgenModules(g): - if m.rd != nil: - m.updateCachedModule - else: - m.writeModule(pending=true) - writeMapping(g.mapping) + m.writeModule(pending=true) + writeMapping(config, g.mapping) if g.generatedHeader != nil: writeHeader(g.generatedHeader) -const cgenPass* = makePass(myOpen, myOpenCached, myProcess, myClose) +const cgenPass* = makePass(myOpen, myProcess, myClose) diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim index 0f8fa760e..56dbd65a2 100644 --- a/compiler/cgendata.nim +++ b/compiler/cgendata.nim @@ -11,9 +11,9 @@ import ast, astalgo, ropes, passes, options, intsets, platform, sighashes, - tables, ndi + tables, ndi, lineinfos -from msgs import TLineInfo +from modulegraphs import ModuleGraph type TLabel* = Rope # for the C generator a label is just a rope @@ -68,13 +68,14 @@ type prc*: PSym # the Nim proc that this C proc belongs to beforeRetNeeded*: bool # true iff 'BeforeRet' label for proc is needed threadVarAccessed*: bool # true if the proc already accessed some threadvar + hasCurFramePointer*: bool # true if _nimCurFrame var needed to recover after + # exception is generated lastLineInfo*: TLineInfo # to avoid generating excessive 'nimln' statements currLineInfo*: TLineInfo # AST codegen will make this superfluous - nestedTryStmts*: seq[PNode] # in how many nested try statements we are - # (the vars must be volatile then) - inExceptBlock*: int # are we currently inside an except block? - # leaving such scopes by raise or by return must - # execute any applicable finally blocks + nestedTryStmts*: seq[tuple[n: PNode, inExcept: bool]] + # in how many nested try statements we are + # (the vars must be volatile then) + # bool is true when are in the except part of a try block finallySafePoints*: seq[Rope] # For correctly cleaning up exceptions when # using return in finally statements labels*: Natural # for generating unique labels in the C proc @@ -117,6 +118,19 @@ type breakpoints*: Rope # later the breakpoints are inserted into the main proc typeInfoMarker*: TypeCache config*: ConfigRef + graph*: ModuleGraph + strVersion*, seqVersion*: int # version of the string/seq implementation to use + + nimtv*: Rope # Nim thread vars; the struct body + nimtvDeps*: seq[PType] # type deps: every module needs whole struct + nimtvDeclared*: IntSet # so that every var/field exists only once + # in the struct + # 'nimtv' is incredibly hard to modularize! Best + # effort is to store all thread vars in a ROD + # section and with their type deps and load them + # unconditionally... + # nimtvDeps is VERY hard to cache because it's + # not a list of IDs nor can it be made to be one. TCGen = object of TPassContext # represents a C source file s*: TCFileSections # sections of the C file @@ -133,7 +147,6 @@ type headerFiles*: seq[string] # needed headers to include typeInfoMarker*: TypeCache # needed for generating type information initProc*: BProc # code for init procedure - postInitProc*: BProc # code to be executed after the init proc preInitProc*: BProc # code executed before the init proc typeStack*: TTypeSeq # used for type generation dataCache*: TNodeTable @@ -148,6 +161,9 @@ type g*: BModuleList ndi*: NdiFile +template config*(m: BModule): ConfigRef = m.g.config +template config*(p: BProc): ConfigRef = p.module.g.config + proc includeHeader*(this: BModule; header: string) = if not this.headerFiles.contains header: this.headerFiles.add header @@ -165,14 +181,15 @@ proc newProc*(prc: PSym, module: BModule): BProc = result.prc = prc result.module = module if prc != nil: result.options = prc.options - else: result.options = gOptions + else: result.options = module.config.options newSeq(result.blocks, 1) result.nestedTryStmts = @[] result.finallySafePoints = @[] result.sigConflicts = initCountTable[string]() -proc newModuleList*(config: ConfigRef): BModuleList = - BModuleList(modules: @[], typeInfoMarker: initTable[SigHash, Rope](), config: config) +proc newModuleList*(g: ModuleGraph): BModuleList = + BModuleList(modules: @[], typeInfoMarker: initTable[SigHash, Rope](), config: g.config, + graph: g, nimtvDeps: @[], nimtvDeclared: initIntSet()) iterator cgenModules*(g: BModuleList): BModule = for i in 0..high(g.modules): diff --git a/compiler/cgmeth.nim b/compiler/cgmeth.nim index 0513e88f4..d0ec6c636 100644 --- a/compiler/cgmeth.nim +++ b/compiler/cgmeth.nim @@ -11,9 +11,9 @@ import intsets, options, ast, astalgo, msgs, idents, renderer, types, magicsys, - sempass2, strutils, modulegraphs + sempass2, strutils, modulegraphs, lineinfos -proc genConv(n: PNode, d: PType, downcast: bool): PNode = +proc genConv(n: PNode, d: PType, downcast: bool; conf: ConfigRef): PNode = var dest = skipTypes(d, abstractPtrs) var source = skipTypes(n.typ, abstractPtrs) if (source.kind == tyObject) and (dest.kind == tyObject): @@ -24,12 +24,12 @@ proc genConv(n: PNode, d: PType, downcast: bool): PNode = elif diff < 0: result = newNodeIT(nkObjUpConv, n.info, d) addSon(result, n) - if downcast: internalError(n.info, "cgmeth.genConv: no upcast allowed") + if downcast: internalError(conf, n.info, "cgmeth.genConv: no upcast allowed") elif diff > 0: result = newNodeIT(nkObjDownConv, n.info, d) addSon(result, n) if not downcast: - internalError(n.info, "cgmeth.genConv: no downcast allowed") + internalError(conf, n.info, "cgmeth.genConv: no downcast allowed") else: result = n else: @@ -42,7 +42,7 @@ proc getDispatcher*(s: PSym): PSym = let disp = dispn.sym if sfDispatcher in disp.flags: result = disp -proc methodCall*(n: PNode): PNode = +proc methodCall*(n: PNode; conf: ConfigRef): PNode = result = n # replace ordinary method by dispatcher method: let disp = getDispatcher(result.sons[0].sym) @@ -50,9 +50,9 @@ proc methodCall*(n: PNode): PNode = result.sons[0].sym = disp # change the arguments to up/downcasts to fit the dispatcher's parameters: for i in countup(1, sonsLen(result)-1): - result.sons[i] = genConv(result.sons[i], disp.typ.sons[i], true) + result.sons[i] = genConv(result.sons[i], disp.typ.sons[i], true, conf) else: - localError(n.info, "'" & $result.sons[0] & "' lacks a dispatcher") + localError(conf, n.info, "'" & $result.sons[0] & "' lacks a dispatcher") type MethodResult = enum No, Invalid, Yes @@ -115,7 +115,7 @@ proc createDispatcher(s: PSym): PSym = # we can't inline the dispatcher itself (for now): if disp.typ.callConv == ccInline: disp.typ.callConv = ccDefault disp.ast = copyTree(s.ast) - disp.ast.sons[bodyPos] = ast.emptyNode + disp.ast.sons[bodyPos] = newNodeI(nkEmpty, s.info) disp.loc.r = nil if s.typ.sons[0] != nil: if disp.ast.sonsLen > resultPos: @@ -124,20 +124,20 @@ proc createDispatcher(s: PSym): PSym = # We've encountered a method prototype without a filled-in # resultPos slot. We put a placeholder in there that will # be updated in fixupDispatcher(). - disp.ast.addSon(ast.emptyNode) + disp.ast.addSon(newNodeI(nkEmpty, s.info)) attachDispatcher(s, newSymNode(disp)) # attach to itself to prevent bugs: attachDispatcher(disp, newSymNode(disp)) return disp -proc fixupDispatcher(meth, disp: PSym) = +proc fixupDispatcher(meth, disp: PSym; conf: ConfigRef) = # We may have constructed the dispatcher from a method prototype # and need to augment the incomplete dispatcher with information # from later definitions, particularly the resultPos slot. Also, # the lock level of the dispatcher needs to be updated/checked # against that of the method. if disp.ast.sonsLen > resultPos and meth.ast.sonsLen > resultPos and - disp.ast.sons[resultPos] == ast.emptyNode: + disp.ast.sons[resultPos].kind == nkEmpty: disp.ast.sons[resultPos] = copyTree(meth.ast.sons[resultPos]) # The following code works only with lock levels, so we disable @@ -149,7 +149,7 @@ proc fixupDispatcher(meth, disp: PSym) = disp.typ.lockLevel = meth.typ.lockLevel elif meth.typ.lockLevel != UnspecifiedLockLevel and meth.typ.lockLevel != disp.typ.lockLevel: - message(meth.info, warnLockLevel, + message(conf, meth.info, warnLockLevel, "method has lock level $1, but another method has $2" % [$meth.typ.lockLevel, $disp.typ.lockLevel]) # XXX The following code silences a duplicate warning in @@ -166,13 +166,13 @@ proc methodDef*(g: ModuleGraph; s: PSym, fromCache: bool) = of Yes: add(g.methods[i].methods, s) attachDispatcher(s, lastSon(disp.ast)) - fixupDispatcher(s, disp) + fixupDispatcher(s, disp, g.config) #echo "fixup ", disp.name.s, " ", disp.id - when useEffectSystem: checkMethodEffects(disp, s) + when useEffectSystem: checkMethodEffects(g, disp, s) if {sfBase, sfFromGeneric} * s.flags == {sfBase} and g.methods[i].methods[0] != s: # already exists due to forwarding definition? - localError(s.info, "method is not a base") + localError(g.config, s.info, "method is not a base") return of No: discard of Invalid: @@ -183,10 +183,10 @@ proc methodDef*(g: ModuleGraph; s: PSym, fromCache: bool) = #if fromCache: # internalError(s.info, "no method dispatcher found") if witness != nil: - localError(s.info, "invalid declaration order; cannot attach '" & s.name.s & - "' to method defined here: " & $witness.info) + localError(g.config, s.info, "invalid declaration order; cannot attach '" & s.name.s & + "' to method defined here: " & g.config$witness.info) elif sfBase notin s.flags: - message(s.info, warnUseBase) + message(g.config, s.info, warnUseBase) proc relevantCol(methods: TSymSeq, col: int): bool = # returns true iff the position is relevant @@ -225,32 +225,33 @@ proc sortBucket(a: var TSymSeq, relevantCols: IntSet) = a[j] = v if h == 1: break -proc genDispatcher(methods: TSymSeq, relevantCols: IntSet): PSym = +proc genDispatcher(g: ModuleGraph; methods: TSymSeq, relevantCols: IntSet): PSym = var base = lastSon(methods[0].ast).sym result = base var paramLen = sonsLen(base.typ) var nilchecks = newNodeI(nkStmtList, base.info) var disp = newNodeI(nkIfStmt, base.info) - var ands = getSysSym("and") - var iss = getSysSym("of") + var ands = getSysMagic(g, unknownLineInfo(), "and", mAnd) + var iss = getSysMagic(g, unknownLineInfo(), "of", mOf) + let boolType = getSysType(g, unknownLineInfo(), tyBool) for col in countup(1, paramLen - 1): if contains(relevantCols, col): let param = base.typ.n.sons[col].sym if param.typ.skipTypes(abstractInst).kind in {tyRef, tyPtr}: addSon(nilchecks, newTree(nkCall, - newSymNode(getCompilerProc"chckNilDisp"), newSymNode(param))) + newSymNode(getCompilerProc(g, "chckNilDisp")), newSymNode(param))) for meth in countup(0, high(methods)): var curr = methods[meth] # generate condition: var cond: PNode = nil for col in countup(1, paramLen - 1): if contains(relevantCols, col): - var isn = newNodeIT(nkCall, base.info, getSysType(tyBool)) + var isn = newNodeIT(nkCall, base.info, boolType) addSon(isn, newSymNode(iss)) let param = base.typ.n.sons[col].sym addSon(isn, newSymNode(param)) addSon(isn, newNodeIT(nkType, base.info, curr.typ.sons[col])) if cond != nil: - var a = newNodeIT(nkCall, base.info, getSysType(tyBool)) + var a = newNodeIT(nkCall, base.info, boolType) addSon(a, newSymNode(ands)) addSon(a, cond) addSon(a, isn) @@ -262,7 +263,7 @@ proc genDispatcher(methods: TSymSeq, relevantCols: IntSet): PSym = addSon(call, newSymNode(curr)) for col in countup(1, paramLen - 1): addSon(call, genConv(newSymNode(base.typ.n.sons[col].sym), - curr.typ.sons[col], false)) + curr.typ.sons[col], false, g.config)) var ret: PNode if retTyp != nil: var a = newNodeI(nkFastAsgn, base.info) @@ -290,4 +291,4 @@ proc generateMethodDispatchers*(g: ModuleGraph): PNode = if relevantCol(g.methods[bucket].methods, col): incl(relevantCols, col) sortBucket(g.methods[bucket].methods, relevantCols) addSon(result, - newSymNode(genDispatcher(g.methods[bucket].methods, relevantCols))) + newSymNode(genDispatcher(g, g.methods[bucket].methods, relevantCols))) diff --git a/compiler/closureiters.nim b/compiler/closureiters.nim new file mode 100644 index 000000000..b0857e6c7 --- /dev/null +++ b/compiler/closureiters.nim @@ -0,0 +1,1325 @@ +# +# +# The Nim Compiler +# (c) Copyright 2018 Nim Contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# This file implements closure iterator transformations. +# The main idea is to split the closure iterator body to top level statements. +# The body is split by yield statement. +# +# Example: +# while a > 0: +# echo "hi" +# yield a +# dec a +# +# Should be transformed to: +# STATE0: +# if a > 0: +# echo "hi" +# :state = 1 # Next state +# return a # yield +# else: +# :state = 2 # Next state +# break :stateLoop # Proceed to the next state +# STATE1: +# dec a +# :state = 0 # Next state +# break :stateLoop # Proceed to the next state +# STATE2: +# :state = -1 # End of execution + +# The transformation should play well with lambdalifting, however depending +# on situation, it can be called either before or after lambdalifting +# transformation. As such we behave slightly differently, when accessing +# iterator state, or using temp variables. If lambdalifting did not happen, +# we just create local variables, so that they will be lifted further on. +# Otherwise, we utilize existing env, created by lambdalifting. + +# Lambdalifting treats :state variable specially, it should always end up +# as the first field in env. Currently C codegen depends on this behavior. + +# One special subtransformation is nkStmtListExpr lowering. +# Example: +# template foo(): int = +# yield 1 +# 2 +# +# iterator it(): int {.closure.} = +# if foo() == 2: +# yield 3 +# +# If a nkStmtListExpr has yield inside, it has first to be lowered to: +# yield 1 +# :tmpSlLower = 2 +# if :tmpSlLower == 2: +# yield 3 + +# nkTryStmt Transformations: +# If the iter has an nkTryStmt with a yield inside +# - the closure iter is promoted to have exceptions (ctx.hasExceptions = true) +# - exception table is created. This is a const array, where +# `abs(exceptionTable[i])` is a state idx to which we should jump from state +# `i` should exception be raised in state `i`. For all states in `try` block +# the target state is `except` block. For all states in `except` block +# the target state is `finally` block. For all other states there is no +# target state (0, as the first block can never be neither except nor finally). +# `exceptionTable[i]` is < 0 if `abs(exceptionTable[i])` is except block, +# and > 0, for finally block. +# - local variable :curExc is created +# - the iter body is wrapped into a +# try: +# closureIterSetupExc(:curExc) +# ...body... +# catch: +# :state = exceptionTable[:state] +# if :state == 0: raise # No state that could handle exception +# :unrollFinally = :state > 0 # Target state is finally +# if :state < 0: +# :state = -:state +# :curExc = getCurrentException() +# +# nkReturnStmt within a try/except/finally now has to behave differently as we +# want the nearest finally block to be executed before the return, thus it is +# transformed to: +# :tmpResult = returnValue (if return doesn't have a value, this is skipped) +# :unrollFinally = true +# goto nearestFinally (or -1 if not exists) +# +# Every finally block calls closureIterEndFinally() upon its successful +# completion. +# +# Example: +# +# try: +# yield 0 +# raise ... +# except: +# yield 1 +# return 3 +# finally: +# yield 2 +# +# Is transformed to (yields are left in place for example simplicity, +# in reality the code is subdivided even more, as described above): +# +# STATE0: # Try +# yield 0 +# raise ... +# :state = 2 # What would happen should we not raise +# break :stateLoop +# STATE1: # Except +# yield 1 +# :tmpResult = 3 # Return +# :unrollFinally = true # Return +# :state = 2 # Goto Finally +# break :stateLoop +# :state = 2 # What would happen should we not return +# break :stateLoop +# STATE2: # Finally +# yield 2 +# if :unrollFinally: # This node is created by `newEndFinallyNode` +# if :curExc.isNil: +# return :tmpResult +# else: +# raise +# state = -1 # Goto next state. In this case we just exit +# break :stateLoop + +import + intsets, strutils, options, ast, astalgo, trees, treetab, msgs, idents, + renderer, types, magicsys, lowerings, lambdalifting, modulegraphs, lineinfos + +type + Ctx = object + g: ModuleGraph + fn: PSym + stateVarSym: PSym # :state variable. nil if env already introduced by lambdalifting + tmpResultSym: PSym # Used when we return, but finally has to interfere + unrollFinallySym: PSym # Indicates that we're unrolling finally states (either exception happened or premature return) + curExcSym: PSym # Current exception + + states: seq[PNode] # The resulting states. Every state is an nkState node. + blockLevel: int # Temp used to transform break and continue stmts + stateLoopLabel: PSym # Label to break on, when jumping between states. + exitStateIdx: int # index of the last state + tempVarId: int # unique name counter + tempVars: PNode # Temp var decls, nkVarSection + exceptionTable: seq[int] # For state `i` jump to state `exceptionTable[i]` if exception is raised + hasExceptions: bool # Does closure have yield in try? + curExcHandlingState: int # Negative for except, positive for finally + nearestFinally: int # Index of the nearest finally block. For try/except it + # is their finally. For finally it is parent finally. Otherwise -1 + +const + nkSkip = { nkEmpty..nkNilLit, nkTemplateDef, nkTypeSection, nkStaticStmt, + nkCommentStmt } + procDefs + +proc newStateAccess(ctx: var Ctx): PNode = + if ctx.stateVarSym.isNil: + result = rawIndirectAccess(newSymNode(getEnvParam(ctx.fn)), + getStateField(ctx.g, ctx.fn), ctx.fn.info) + else: + result = newSymNode(ctx.stateVarSym) + +proc newStateAssgn(ctx: var Ctx, toValue: PNode): PNode = + # Creates state assignment: + # :state = toValue + newTree(nkAsgn, ctx.newStateAccess(), toValue) + +proc newStateAssgn(ctx: var Ctx, stateNo: int = -2): PNode = + # Creates state assignment: + # :state = stateNo + ctx.newStateAssgn(newIntTypeNode(nkIntLit, stateNo, ctx.g.getSysType(TLineInfo(), tyInt))) + +proc newEnvVar(ctx: var Ctx, name: string, typ: PType): PSym = + result = newSym(skVar, getIdent(ctx.g.cache, name), ctx.fn, ctx.fn.info) + result.typ = typ + assert(not typ.isNil) + + if not ctx.stateVarSym.isNil: + # We haven't gone through labmda lifting yet, so just create a local var, + # it will be lifted later + if ctx.tempVars.isNil: + ctx.tempVars = newNodeI(nkVarSection, ctx.fn.info) + addVar(ctx.tempVars, newSymNode(result)) + else: + let envParam = getEnvParam(ctx.fn) + # let obj = envParam.typ.lastSon + result = addUniqueField(envParam.typ.lastSon, result, ctx.g.cache) + +proc newEnvVarAccess(ctx: Ctx, s: PSym): PNode = + if ctx.stateVarSym.isNil: + result = rawIndirectAccess(newSymNode(getEnvParam(ctx.fn)), s, ctx.fn.info) + else: + result = newSymNode(s) + +proc newTmpResultAccess(ctx: var Ctx): PNode = + if ctx.tmpResultSym.isNil: + ctx.tmpResultSym = ctx.newEnvVar(":tmpResult", ctx.fn.typ[0]) + ctx.newEnvVarAccess(ctx.tmpResultSym) + +proc newUnrollFinallyAccess(ctx: var Ctx, info: TLineInfo): PNode = + if ctx.unrollFinallySym.isNil: + ctx.unrollFinallySym = ctx.newEnvVar(":unrollFinally", ctx.g.getSysType(info, tyBool)) + ctx.newEnvVarAccess(ctx.unrollFinallySym) + +proc newCurExcAccess(ctx: var Ctx): PNode = + if ctx.curExcSym.isNil: + ctx.curExcSym = ctx.newEnvVar(":curExc", ctx.g.callCodegenProc("getCurrentException", ctx.g.emptyNode).typ) + ctx.newEnvVarAccess(ctx.curExcSym) + +proc newState(ctx: var Ctx, n, gotoOut: PNode): int = + # Creates a new state, adds it to the context fills out `gotoOut` so that it + # will goto this state. + # Returns index of the newly created state + + result = ctx.states.len + let resLit = ctx.g.newIntLit(n.info, result) + let s = newNodeI(nkState, n.info) + s.add(resLit) + s.add(n) + ctx.states.add(s) + ctx.exceptionTable.add(ctx.curExcHandlingState) + + if not gotoOut.isNil: + assert(gotoOut.len == 0) + gotoOut.add(ctx.g.newIntLit(gotoOut.info, result)) + +proc toStmtList(n: PNode): PNode = + result = n + if result.kind notin {nkStmtList, nkStmtListExpr}: + result = newNodeI(nkStmtList, n.info) + result.add(n) + +proc addGotoOut(n: PNode, gotoOut: PNode): PNode = + # Make sure `n` is a stmtlist, and ends with `gotoOut` + result = toStmtList(n) + if result.len == 0 or result.sons[^1].kind != nkGotoState: + result.add(gotoOut) + +proc newTempVar(ctx: var Ctx, typ: PType): PSym = + result = ctx.newEnvVar(":tmpSlLower" & $ctx.tempVarId, typ) + inc ctx.tempVarId + +proc hasYields(n: PNode): bool = + # TODO: This is very inefficient. It traverses the node, looking for nkYieldStmt. + case n.kind + of nkYieldStmt: + result = true + of nkSkip: + discard + else: + for c in n: + if c.hasYields: + result = true + break + +proc transformBreaksAndContinuesInWhile(ctx: var Ctx, n: PNode, before, after: PNode): PNode = + result = n + case n.kind + of nkSkip: + discard + of nkWhileStmt: discard # Do not recurse into nested whiles + of nkContinueStmt: + result = before + of nkBlockStmt: + inc ctx.blockLevel + result[1] = ctx.transformBreaksAndContinuesInWhile(result[1], before, after) + dec ctx.blockLevel + of nkBreakStmt: + if ctx.blockLevel == 0: + result = after + else: + for i in 0 ..< n.len: + n[i] = ctx.transformBreaksAndContinuesInWhile(n[i], before, after) + +proc transformBreaksInBlock(ctx: var Ctx, n: PNode, label, after: PNode): PNode = + result = n + case n.kind + of nkSkip: + discard + of nkBlockStmt, nkWhileStmt: + inc ctx.blockLevel + result[1] = ctx.transformBreaksInBlock(result[1], label, after) + dec ctx.blockLevel + of nkBreakStmt: + if n[0].kind == nkEmpty: + if ctx.blockLevel == 0: + result = after + else: + if label.kind == nkSym and n[0].sym == label.sym: + result = after + else: + for i in 0 ..< n.len: + n[i] = ctx.transformBreaksInBlock(n[i], label, after) + +proc newNullifyCurExc(ctx: var Ctx, info: TLineInfo): PNode = + # :curEcx = nil + let curExc = ctx.newCurExcAccess() + curExc.info = info + let nilnode = newNode(nkNilLit) + nilnode.typ = curExc.typ + result = newTree(nkAsgn, curExc, nilnode) + +proc newOr(g: ModuleGraph, a, b: PNode): PNode {.inline.} = + result = newTree(nkCall, newSymNode(g.getSysMagic(a.info, "or", mOr)), a, b) + result.typ = g.getSysType(a.info, tyBool) + result.info = a.info + +proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} = + var ifStmt = newNodeI(nkIfStmt, n.info) + let g = ctx.g + for c in n: + if c.kind == nkExceptBranch: + var ifBranch: PNode + + if c.len > 1: + var cond: PNode + for i in 0 .. c.len - 2: + assert(c[i].kind == nkType) + let nextCond = newTree(nkCall, + newSymNode(g.getSysMagic(c.info, "of", mOf)), + g.callCodegenProc("getCurrentException", ctx.g.emptyNode), + c[i]) + nextCond.typ = ctx.g.getSysType(c.info, tyBool) + nextCond.info = c.info + + if cond.isNil: + cond = nextCond + else: + cond = g.newOr(cond, nextCond) + + ifBranch = newNodeI(nkElifBranch, c.info) + ifBranch.add(cond) + else: + if ifStmt.len == 0: + ifStmt = newNodeI(nkStmtList, c.info) + ifBranch = newNodeI(nkStmtList, c.info) + else: + ifBranch = newNodeI(nkElse, c.info) + + ifBranch.add(c[^1]) + ifStmt.add(ifBranch) + + if ifStmt.len != 0: + result = newTree(nkStmtList, ctx.newNullifyCurExc(n.info), ifStmt) + else: + result = ctx.g.emptyNode + +proc addElseToExcept(ctx: var Ctx, n: PNode) = + if n.kind == nkStmtList and n[1].kind == nkIfStmt and n[1][^1].kind != nkElse: + # Not all cases are covered + let branchBody = newNodeI(nkStmtList, n.info) + + block: # :unrollFinally = true + branchBody.add(newTree(nkAsgn, + ctx.newUnrollFinallyAccess(n.info), + newIntTypeNode(nkIntLit, 1, ctx.g.getSysType(n.info, tyBool)))) + + block: # :curExc = getCurrentException() + branchBody.add(newTree(nkAsgn, + ctx.newCurExcAccess(), + ctx.g.callCodegenProc("getCurrentException", ctx.g.emptyNode))) + + block: # goto nearestFinally + branchBody.add(newTree(nkGotoState, ctx.g.newIntLit(n.info, ctx.nearestFinally))) + + let elseBranch = newTree(nkElse, branchBody) + n[1].add(elseBranch) + +proc getFinallyNode(ctx: var Ctx, n: PNode): PNode = + result = n[^1] + if result.kind == nkFinally: + result = result[0] + else: + result = ctx.g.emptyNode + +proc hasYieldsInExpressions(n: PNode): bool = + case n.kind + of nkSkip: + discard + of nkStmtListExpr: + if isEmptyType(n.typ): + for c in n: + if c.hasYieldsInExpressions: + return true + else: + result = n.hasYields + else: + for c in n: + if c.hasYieldsInExpressions: + return true + +proc exprToStmtList(n: PNode): tuple[s, res: PNode] = + assert(n.kind == nkStmtListExpr) + result.s = newNodeI(nkStmtList, n.info) + result.s.sons = @[] + + var n = n + while n.kind == nkStmtListExpr: + result.s.sons.add(n.sons) + result.s.sons.setLen(result.s.sons.len - 1) # delete last son + n = n[^1] + + result.res = n + + +proc newEnvVarAsgn(ctx: Ctx, s: PSym, v: PNode): PNode = + result = newTree(nkFastAsgn, ctx.newEnvVarAccess(s), v) + result.info = v.info + +proc addExprAssgn(ctx: Ctx, output, input: PNode, sym: PSym) = + if input.kind == nkStmtListExpr: + let (st, res) = exprToStmtList(input) + output.add(st) + output.add(ctx.newEnvVarAsgn(sym, res)) + else: + output.add(ctx.newEnvVarAsgn(sym, input)) + +proc convertExprBodyToAsgn(ctx: Ctx, exprBody: PNode, res: PSym): PNode = + result = newNodeI(nkStmtList, exprBody.info) + ctx.addExprAssgn(result, exprBody, res) + +proc newNotCall(g: ModuleGraph; e: PNode): PNode = + result = newTree(nkCall, newSymNode(g.getSysMagic(e.info, "not", mNot), e.info), e) + result.typ = g.getSysType(e.info, tyBool) + +proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode = + result = n + case n.kind + of nkSkip: + discard + + of nkYieldStmt: + var ns = false + for i in 0 ..< n.len: + n[i] = ctx.lowerStmtListExprs(n[i], ns) + + if ns: + result = newNodeI(nkStmtList, n.info) + let (st, ex) = exprToStmtList(n[0]) + result.add(st) + n[0] = ex + result.add(n) + + needsSplit = true + + of nkPar, nkObjConstr, nkTupleConstr, nkBracket: + var ns = false + for i in 0 ..< n.len: + n[i] = ctx.lowerStmtListExprs(n[i], ns) + + if ns: + needsSplit = true + + result = newNodeI(nkStmtListExpr, n.info) + if n.typ.isNil: internalError(ctx.g.config, "lowerStmtListExprs: constr typ.isNil") + result.typ = n.typ + + for i in 0 ..< n.len: + if n[i].kind == nkStmtListExpr: + let (st, ex) = exprToStmtList(n[i]) + result.add(st) + n[i] = ex + result.add(n) + + of nkIfStmt, nkIfExpr: + var ns = false + for i in 0 ..< n.len: + n[i] = ctx.lowerStmtListExprs(n[i], ns) + + if ns: + needsSplit = true + var tmp: PSym + var s: PNode + let isExpr = not isEmptyType(n.typ) + if isExpr: + tmp = ctx.newTempVar(n.typ) + result = newNodeI(nkStmtListExpr, n.info) + result.typ = n.typ + else: + result = newNodeI(nkStmtList, n.info) + + var curS = result + + for branch in n: + case branch.kind + of nkElseExpr, nkElse: + if isExpr: + let branchBody = newNodeI(nkStmtList, branch.info) + ctx.addExprAssgn(branchBody, branch[0], tmp) + let newBranch = newTree(nkElse, branchBody) + curS.add(newBranch) + else: + curS.add(branch) + + of nkElifExpr, nkElifBranch: + var newBranch: PNode + if branch[0].kind == nkStmtListExpr: + let (st, res) = exprToStmtList(branch[0]) + let elseBody = newTree(nkStmtList, st) + + newBranch = newTree(nkElifBranch, res, branch[1]) + + let newIf = newTree(nkIfStmt, newBranch) + elseBody.add(newIf) + if curS.kind == nkIfStmt: + let newElse = newNodeI(nkElse, branch.info) + newElse.add(elseBody) + curS.add(newElse) + else: + curS.add(elseBody) + curS = newIf + else: + newBranch = branch + if curS.kind == nkIfStmt: + curS.add(newBranch) + else: + let newIf = newTree(nkIfStmt, newBranch) + curS.add(newIf) + curS = newIf + + if isExpr: + let branchBody = newNodeI(nkStmtList, branch[1].info) + ctx.addExprAssgn(branchBody, branch[1], tmp) + newBranch[1] = branchBody + + else: + internalError(ctx.g.config, "lowerStmtListExpr(nkIf): " & $branch.kind) + + if isExpr: result.add(ctx.newEnvVarAccess(tmp)) + + of nkTryStmt: + var ns = false + for i in 0 ..< n.len: + n[i] = ctx.lowerStmtListExprs(n[i], ns) + + if ns: + needsSplit = true + let isExpr = not isEmptyType(n.typ) + + if isExpr: + result = newNodeI(nkStmtListExpr, n.info) + result.typ = n.typ + let tmp = ctx.newTempVar(n.typ) + + n[0] = ctx.convertExprBodyToAsgn(n[0], tmp) + for i in 1 ..< n.len: + let branch = n[i] + case branch.kind + of nkExceptBranch: + if branch[0].kind == nkType: + branch[1] = ctx.convertExprBodyToAsgn(branch[1], tmp) + else: + branch[0] = ctx.convertExprBodyToAsgn(branch[0], tmp) + of nkFinally: + discard + else: + internalError(ctx.g.config, "lowerStmtListExpr(nkTryStmt): " & $branch.kind) + result.add(n) + result.add(ctx.newEnvVarAccess(tmp)) + + of nkCaseStmt: + var ns = false + for i in 0 ..< n.len: + n[i] = ctx.lowerStmtListExprs(n[i], ns) + + if ns: + needsSplit = true + + let isExpr = not isEmptyType(n.typ) + + if isExpr: + let tmp = ctx.newTempVar(n.typ) + result = newNodeI(nkStmtListExpr, n.info) + result.typ = n.typ + + if n[0].kind == nkStmtListExpr: + let (st, ex) = exprToStmtList(n[0]) + result.add(st) + n[0] = ex + + for i in 1 ..< n.len: + let branch = n[i] + case branch.kind + of nkOfBranch: + branch[^1] = ctx.convertExprBodyToAsgn(branch[^1], tmp) + of nkElse: + branch[0] = ctx.convertExprBodyToAsgn(branch[0], tmp) + else: + internalError(ctx.g.config, "lowerStmtListExpr(nkCaseStmt): " & $branch.kind) + result.add(n) + result.add(ctx.newEnvVarAccess(tmp)) + + of nkCallKinds: + var ns = false + for i in 0 ..< n.len: + n[i] = ctx.lowerStmtListExprs(n[i], ns) + + if ns: + needsSplit = true + let isExpr = not isEmptyType(n.typ) + + if isExpr: + result = newNodeI(nkStmtListExpr, n.info) + result.typ = n.typ + else: + result = newNodeI(nkStmtList, n.info) + + if n[0].kind == nkSym and n[0].sym.magic in {mAnd, mOr}: # `and`/`or` short cirquiting + var cond = n[1] + if cond.kind == nkStmtListExpr: + let (st, ex) = exprToStmtList(cond) + result.add(st) + cond = ex + + let tmp = ctx.newTempVar(cond.typ) + result.add(ctx.newEnvVarAsgn(tmp, cond)) + + var check = ctx.newEnvVarAccess(tmp) + if n[0].sym.magic == mOr: + check = ctx.g.newNotCall(check) + + cond = n[2] + let ifBody = newNodeI(nkStmtList, cond.info) + if cond.kind == nkStmtListExpr: + let (st, ex) = exprToStmtList(cond) + ifBody.add(st) + cond = ex + ifBody.add(ctx.newEnvVarAsgn(tmp, cond)) + + let ifBranch = newTree(nkElifBranch, check, ifBody) + let ifNode = newTree(nkIfStmt, ifBranch) + result.add(ifNode) + result.add(ctx.newEnvVarAccess(tmp)) + else: + for i in 0 ..< n.len: + if n[i].kind == nkStmtListExpr: + let (st, ex) = exprToStmtList(n[i]) + result.add(st) + n[i] = ex + + if n[i].kind in nkCallKinds: # XXX: This should better be some sort of side effect tracking + let tmp = ctx.newTempVar(n[i].typ) + result.add(ctx.newEnvVarAsgn(tmp, n[i])) + n[i] = ctx.newEnvVarAccess(tmp) + + result.add(n) + + of nkVarSection, nkLetSection: + result = newNodeI(nkStmtList, n.info) + for c in n: + let varSect = newNodeI(n.kind, n.info) + varSect.add(c) + var ns = false + c[^1] = ctx.lowerStmtListExprs(c[^1], ns) + if ns: + needsSplit = true + let (st, ex) = exprToStmtList(c[^1]) + result.add(st) + c[^1] = ex + result.add(varSect) + + of nkDiscardStmt, nkReturnStmt, nkRaiseStmt: + var ns = false + for i in 0 ..< n.len: + n[i] = ctx.lowerStmtListExprs(n[i], ns) + + if ns: + needsSplit = true + result = newNodeI(nkStmtList, n.info) + let (st, ex) = exprToStmtList(n[0]) + result.add(st) + n[0] = ex + result.add(n) + + of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv, nkObjDownConv: + var ns = false + for i in 0 ..< n.len: + n[i] = ctx.lowerStmtListExprs(n[i], ns) + + if ns: + needsSplit = true + result = newNodeI(nkStmtListExpr, n.info) + result.typ = n.typ + let (st, ex) = exprToStmtList(n[^1]) + result.add(st) + n[^1] = ex + result.add(n) + + of nkAsgn, nkFastAsgn: + var ns = false + for i in 0 ..< n.len: + n[i] = ctx.lowerStmtListExprs(n[i], ns) + + if ns: + needsSplit = true + result = newNodeI(nkStmtList, n.info) + if n[0].kind == nkStmtListExpr: + let (st, ex) = exprToStmtList(n[0]) + result.add(st) + n[0] = ex + + if n[1].kind == nkStmtListExpr: + let (st, ex) = exprToStmtList(n[1]) + result.add(st) + n[1] = ex + + result.add(n) + + of nkBracketExpr: + var lhsNeedsSplit = false + var rhsNeedsSplit = false + n[0] = ctx.lowerStmtListExprs(n[0], lhsNeedsSplit) + n[1] = ctx.lowerStmtListExprs(n[1], rhsNeedsSplit) + if lhsNeedsSplit or rhsNeedsSplit: + needsSplit = true + result = newNodeI(nkStmtListExpr, n.info) + if lhsNeedsSplit: + let (st, ex) = exprToStmtList(n[0]) + result.add(st) + n[0] = ex + + if rhsNeedsSplit: + let (st, ex) = exprToStmtList(n[1]) + result.add(st) + n[1] = ex + result.add(n) + + of nkWhileStmt: + var ns = false + + var condNeedsSplit = false + n[0] = ctx.lowerStmtListExprs(n[0], condNeedsSplit) + var bodyNeedsSplit = false + n[1] = ctx.lowerStmtListExprs(n[1], bodyNeedsSplit) + + if condNeedsSplit or bodyNeedsSplit: + needsSplit = true + + if condNeedsSplit: + let (st, ex) = exprToStmtList(n[0]) + let brk = newTree(nkBreakStmt, ctx.g.emptyNode) + let branch = newTree(nkElifBranch, ctx.g.newNotCall(ex), brk) + let check = newTree(nkIfStmt, branch) + let newBody = newTree(nkStmtList, st, check, n[1]) + + n[0] = newSymNode(ctx.g.getSysSym(n[0].info, "true")) + n[1] = newBody + + of nkDotExpr: + var ns = false + n[0] = ctx.lowerStmtListExprs(n[0], ns) + if ns: + needsSplit = true + result = newNodeI(nkStmtListExpr, n.info) + result.typ = n.typ + let (st, ex) = exprToStmtList(n[0]) + result.add(st) + n[0] = ex + result.add(n) + + of nkBlockExpr: + var ns = false + n[1] = ctx.lowerStmtListExprs(n[1], ns) + if ns: + needsSplit = true + result = newNodeI(nkStmtListExpr, n.info) + result.typ = n.typ + let (st, ex) = exprToStmtList(n[1]) + n.kind = nkBlockStmt + n.typ = nil + n[1] = st + result.add(n) + result.add(ex) + + else: + for i in 0 ..< n.len: + n[i] = ctx.lowerStmtListExprs(n[i], needsSplit) + +proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode = + # Generate the following code: + # if :unrollFinally: + # if :curExc.isNil: + # return :tmpResult + # else: + # raise + let curExc = ctx.newCurExcAccess() + let nilnode = newNode(nkNilLit) + nilnode.typ = curExc.typ + let cmp = newTree(nkCall, newSymNode(ctx.g.getSysMagic(info, "==", mEqRef), info), curExc, nilnode) + cmp.typ = ctx.g.getSysType(info, tyBool) + + let asgn = newTree(nkFastAsgn, + newSymNode(getClosureIterResult(ctx.g, ctx.fn), info), + ctx.newTmpResultAccess()) + + let retStmt = newTree(nkReturnStmt, asgn) + let branch = newTree(nkElifBranch, cmp, retStmt) + + # The C++ backend requires `getCurrentException` here. + let raiseStmt = newTree(nkRaiseStmt, ctx.g.callCodegenProc("getCurrentException", ctx.g.emptyNode)) + raiseStmt.info = info + let elseBranch = newTree(nkElse, raiseStmt) + + let ifBody = newTree(nkIfStmt, branch, elseBranch) + let elifBranch = newTree(nkElifBranch, ctx.newUnrollFinallyAccess(info), ifBody) + elifBranch.info = info + result = newTree(nkIfStmt, elifBranch) + +proc transformReturnsInTry(ctx: var Ctx, n: PNode): PNode = + result = n + # TODO: This is very inefficient. It traverses the node, looking for nkYieldStmt. + case n.kind + of nkReturnStmt: + # We're somewhere in try, transform to finally unrolling + assert(ctx.nearestFinally != 0) + + result = newNodeI(nkStmtList, n.info) + + block: # :unrollFinally = true + let asgn = newNodeI(nkAsgn, n.info) + asgn.add(ctx.newUnrollFinallyAccess(n.info)) + asgn.add(newIntTypeNode(nkIntLit, 1, ctx.g.getSysType(n.info, tyBool))) + result.add(asgn) + + if n[0].kind != nkEmpty: + let asgnTmpResult = newNodeI(nkAsgn, n.info) + asgnTmpResult.add(ctx.newTmpResultAccess()) + asgnTmpResult.add(n[0]) + result.add(asgnTmpResult) + + result.add(ctx.newNullifyCurExc(n.info)) + + let goto = newTree(nkGotoState, ctx.g.newIntLit(n.info, ctx.nearestFinally)) + result.add(goto) + + of nkSkip: + discard + else: + for i in 0 ..< n.len: + n[i] = ctx.transformReturnsInTry(n[i]) + +proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode = + result = n + case n.kind: + of nkSkip: + discard + + of nkStmtList, nkStmtListExpr: + assert(isEmptyType(n.typ), "nkStmtListExpr not lowered") + + result = addGotoOut(result, gotoOut) + for i in 0 ..< n.len: + if n[i].hasYieldsInExpressions: + # Lower nkStmtListExpr nodes inside `n[i]` first + var ns = false + n[i] = ctx.lowerStmtListExprs(n[i], ns) + + if n[i].hasYields: + # Create a new split + let go = newNodeI(nkGotoState, n[i].info) + n[i] = ctx.transformClosureIteratorBody(n[i], go) + + let s = newNodeI(nkStmtList, n[i + 1].info) + for j in i + 1 ..< n.len: + s.add(n[j]) + + n.sons.setLen(i + 1) + discard ctx.newState(s, go) + if ctx.transformClosureIteratorBody(s, gotoOut) != s: + internalError(ctx.g.config, "transformClosureIteratorBody != s") + break + + of nkYieldStmt: + result = newNodeI(nkStmtList, n.info) + result.add(n) + result.add(gotoOut) + + of nkElse, nkElseExpr: + result[0] = addGotoOut(result[0], gotoOut) + result[0] = ctx.transformClosureIteratorBody(result[0], gotoOut) + + of nkElifBranch, nkElifExpr, nkOfBranch: + result[^1] = addGotoOut(result[^1], gotoOut) + result[^1] = ctx.transformClosureIteratorBody(result[^1], gotoOut) + + of nkIfStmt, nkCaseStmt: + for i in 0 ..< n.len: + n[i] = ctx.transformClosureIteratorBody(n[i], gotoOut) + if n[^1].kind != nkElse: + # We don't have an else branch, but every possible branch has to end with + # gotoOut, so add else here. + let elseBranch = newTree(nkElse, gotoOut) + n.add(elseBranch) + + of nkWhileStmt: + # while e: + # s + # -> + # BEGIN_STATE: + # if e: + # s + # goto BEGIN_STATE + # else: + # goto OUT + + result = newNodeI(nkGotoState, n.info) + + let s = newNodeI(nkStmtList, n.info) + discard ctx.newState(s, result) + let ifNode = newNodeI(nkIfStmt, n.info) + let elifBranch = newNodeI(nkElifBranch, n.info) + elifBranch.add(n[0]) + + var body = addGotoOut(n[1], result) + + body = ctx.transformBreaksAndContinuesInWhile(body, result, gotoOut) + body = ctx.transformClosureIteratorBody(body, result) + + elifBranch.add(body) + ifNode.add(elifBranch) + + let elseBranch = newTree(nkElse, gotoOut) + ifNode.add(elseBranch) + s.add(ifNode) + + of nkBlockStmt: + result[1] = addGotoOut(result[1], gotoOut) + result[1] = ctx.transformBreaksInBlock(result[1], result[0], gotoOut) + result[1] = ctx.transformClosureIteratorBody(result[1], gotoOut) + + of nkTryStmt: + # See explanation above about how this works + ctx.hasExceptions = true + + result = newNodeI(nkGotoState, n.info) + var tryBody = toStmtList(n[0]) + var exceptBody = ctx.collectExceptState(n) + var finallyBody = newTree(nkStmtList, getFinallyNode(ctx, n)) + finallyBody = ctx.transformReturnsInTry(finallyBody) + finallyBody.add(ctx.newEndFinallyNode(finallyBody.info)) + + # The following index calculation is based on the knowledge how state + # indexes are assigned + let tryIdx = ctx.states.len + var exceptIdx, finallyIdx: int + if exceptBody.kind != nkEmpty: + exceptIdx = -(tryIdx + 1) + finallyIdx = tryIdx + 2 + else: + exceptIdx = tryIdx + 1 + finallyIdx = tryIdx + 1 + + let outToFinally = newNodeI(nkGotoState, finallyBody.info) + + block: # Create initial states. + let oldExcHandlingState = ctx.curExcHandlingState + ctx.curExcHandlingState = exceptIdx + let realTryIdx = ctx.newState(tryBody, result) + assert(realTryIdx == tryIdx) + + if exceptBody.kind != nkEmpty: + ctx.curExcHandlingState = finallyIdx + let realExceptIdx = ctx.newState(exceptBody, nil) + assert(realExceptIdx == -exceptIdx) + + ctx.curExcHandlingState = oldExcHandlingState + let realFinallyIdx = ctx.newState(finallyBody, outToFinally) + assert(realFinallyIdx == finallyIdx) + + block: # Subdivide the states + let oldNearestFinally = ctx.nearestFinally + ctx.nearestFinally = finallyIdx + + let oldExcHandlingState = ctx.curExcHandlingState + + ctx.curExcHandlingState = exceptIdx + + if ctx.transformReturnsInTry(tryBody) != tryBody: + internalError(ctx.g.config, "transformReturnsInTry != tryBody") + if ctx.transformClosureIteratorBody(tryBody, outToFinally) != tryBody: + internalError(ctx.g.config, "transformClosureIteratorBody != tryBody") + + ctx.curExcHandlingState = finallyIdx + ctx.addElseToExcept(exceptBody) + if ctx.transformReturnsInTry(exceptBody) != exceptBody: + internalError(ctx.g.config, "transformReturnsInTry != exceptBody") + if ctx.transformClosureIteratorBody(exceptBody, outToFinally) != exceptBody: + internalError(ctx.g.config, "transformClosureIteratorBody != exceptBody") + + ctx.curExcHandlingState = oldExcHandlingState + ctx.nearestFinally = oldNearestFinally + if ctx.transformClosureIteratorBody(finallyBody, gotoOut) != finallyBody: + internalError(ctx.g.config, "transformClosureIteratorBody != finallyBody") + + of nkGotoState, nkForStmt: + internalError(ctx.g.config, "closure iter " & $n.kind) + + else: + for i in 0 ..< n.len: + n[i] = ctx.transformClosureIteratorBody(n[i], gotoOut) + +proc stateFromGotoState(n: PNode): int = + assert(n.kind == nkGotoState) + result = n[0].intVal.int + +proc tranformStateAssignments(ctx: var Ctx, n: PNode): PNode = + # This transforms 3 patterns: + ########################## 1 + # yield e + # goto STATE + # -> + # :state = STATE + # return e + ########################## 2 + # goto STATE + # -> + # :state = STATE + # break :stateLoop + ########################## 3 + # return e + # -> + # :state = -1 + # return e + # + result = n + case n.kind + of nkStmtList, nkStmtListExpr: + if n.len != 0 and n[0].kind == nkYieldStmt: + assert(n.len == 2) + assert(n[1].kind == nkGotoState) + + result = newNodeI(nkStmtList, n.info) + result.add(ctx.newStateAssgn(stateFromGotoState(n[1]))) + + var retStmt = newNodeI(nkReturnStmt, n.info) + if n[0].sons[0].kind != nkEmpty: + var a = newNodeI(nkAsgn, n[0].sons[0].info) + var retVal = n[0].sons[0] #liftCapturedVars(n.sons[0], owner, d, c) + addSon(a, newSymNode(getClosureIterResult(ctx.g, ctx.fn))) + addSon(a, retVal) + retStmt.add(a) + else: + retStmt.add(ctx.g.emptyNode) + + result.add(retStmt) + else: + for i in 0 ..< n.len: + n[i] = ctx.tranformStateAssignments(n[i]) + + of nkSkip: + discard + + of nkReturnStmt: + result = newNodeI(nkStmtList, n.info) + result.add(ctx.newStateAssgn(-1)) + result.add(n) + + of nkGotoState: + result = newNodeI(nkStmtList, n.info) + result.add(ctx.newStateAssgn(stateFromGotoState(n))) + + let breakState = newNodeI(nkBreakStmt, n.info) + breakState.add(newSymNode(ctx.stateLoopLabel)) + result.add(breakState) + + else: + for i in 0 ..< n.len: + n[i] = ctx.tranformStateAssignments(n[i]) + +proc skipStmtList(ctx: Ctx; n: PNode): PNode = + result = n + while result.kind in {nkStmtList}: + if result.len == 0: return ctx.g.emptyNode + result = result[0] + +proc skipEmptyStates(ctx: Ctx, stateIdx: int): int = + # Returns first non-empty state idx for `stateIdx`. Returns `stateIdx` if + # it is not empty + var maxJumps = ctx.states.len # maxJumps used only for debugging purposes. + var stateIdx = stateIdx + while true: + let label = stateIdx + if label == ctx.exitStateIdx: break + var newLabel = label + if label == -1: + newLabel = ctx.exitStateIdx + else: + let fs = skipStmtList(ctx, ctx.states[label][1]) + if fs.kind == nkGotoState: + newLabel = fs[0].intVal.int + if label == newLabel: break + stateIdx = newLabel + dec maxJumps + if maxJumps == 0: + assert(false, "Internal error") + + result = ctx.states[stateIdx][0].intVal.int + +proc skipThroughEmptyStates(ctx: var Ctx, n: PNode): PNode = + result = n + case n.kind + of nkSkip: + discard + of nkGotoState: + result = copyTree(n) + result[0].intVal = ctx.skipEmptyStates(result[0].intVal.int) + else: + for i in 0 ..< n.len: + n[i] = ctx.skipThroughEmptyStates(n[i]) + +proc newArrayType(g: ModuleGraph; n: int, t: PType, owner: PSym): PType = + result = newType(tyArray, owner) + + let rng = newType(tyRange, owner) + rng.n = newTree(nkRange, g.newIntLit(owner.info, 0), g.newIntLit(owner.info, n)) + rng.rawAddSon(t) + + result.rawAddSon(rng) + result.rawAddSon(t) + +proc createExceptionTable(ctx: var Ctx): PNode {.inline.} = + result = newNodeI(nkBracket, ctx.fn.info) + result.typ = ctx.g.newArrayType(ctx.exceptionTable.len, ctx.g.getSysType(ctx.fn.info, tyInt16), ctx.fn) + + for i in ctx.exceptionTable: + let elem = newIntNode(nkIntLit, i) + elem.typ = ctx.g.getSysType(ctx.fn.info, tyInt16) + result.add(elem) + +proc newCatchBody(ctx: var Ctx, info: TLineInfo): PNode {.inline.} = + # Generates the code: + # :state = exceptionTable[:state] + # if :state == 0: raise + # :unrollFinally = :state > 0 + # if :state < 0: + # :state = -:state + # :curExc = getCurrentException() + + result = newNodeI(nkStmtList, info) + + let intTyp = ctx.g.getSysType(info, tyInt) + let boolTyp = ctx.g.getSysType(info, tyBool) + + # :state = exceptionTable[:state] + block: + + # exceptionTable[:state] + let getNextState = newTree(nkBracketExpr, + ctx.createExceptionTable(), + ctx.newStateAccess()) + getNextState.typ = intTyp + + # :state = exceptionTable[:state] + result.add(ctx.newStateAssgn(getNextState)) + + # if :state == 0: raise + block: + let cond = newTree(nkCall, + ctx.g.getSysMagic(info, "==", mEqI).newSymNode(), + ctx.newStateAccess(), + newIntTypeNode(nkIntLit, 0, intTyp)) + cond.typ = boolTyp + + let raiseStmt = newTree(nkRaiseStmt, ctx.g.emptyNode) + let ifBranch = newTree(nkElifBranch, cond, raiseStmt) + let ifStmt = newTree(nkIfStmt, ifBranch) + result.add(ifStmt) + + # :unrollFinally = :state > 0 + block: + let cond = newTree(nkCall, + ctx.g.getSysMagic(info, "<", mLtI).newSymNode, + newIntTypeNode(nkIntLit, 0, intTyp), + ctx.newStateAccess()) + cond.typ = boolTyp + + let asgn = newTree(nkAsgn, ctx.newUnrollFinallyAccess(info), cond) + result.add(asgn) + + # if :state < 0: :state = -:state + block: + let cond = newTree(nkCall, + ctx.g.getSysMagic(info, "<", mLtI).newSymNode, + ctx.newStateAccess(), + newIntTypeNode(nkIntLit, 0, intTyp)) + cond.typ = boolTyp + + let negateState = newTree(nkCall, + ctx.g.getSysMagic(info, "-", mUnaryMinusI).newSymNode, + ctx.newStateAccess()) + negateState.typ = intTyp + + let ifBranch = newTree(nkElifBranch, cond, ctx.newStateAssgn(negateState)) + let ifStmt = newTree(nkIfStmt, ifBranch) + result.add(ifStmt) + + # :curExc = getCurrentException() + block: + result.add(newTree(nkAsgn, + ctx.newCurExcAccess(), + ctx.g.callCodegenProc("getCurrentException", ctx.g.emptyNode))) + +proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} = + let setupExc = newTree(nkCall, + newSymNode(ctx.g.getCompilerProc("closureIterSetupExc")), + ctx.newCurExcAccess()) + + let tryBody = newTree(nkStmtList, setupExc, n) + let exceptBranch = newTree(nkExceptBranch, ctx.newCatchBody(ctx.fn.info)) + + result = newTree(nkTryStmt, tryBody, exceptBranch) + +proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode = + # while true: + # block :stateLoop: + # gotoState :state + # body # Might get wrapped in try-except + let loopBody = newNodeI(nkStmtList, n.info) + result = newTree(nkWhileStmt, newSymNode(ctx.g.getSysSym(n.info, "true")), loopBody) + result.info = n.info + + if not ctx.stateVarSym.isNil: + let varSect = newNodeI(nkVarSection, n.info) + addVar(varSect, newSymNode(ctx.stateVarSym)) + loopBody.add(varSect) + + if not ctx.tempVars.isNil: + loopBody.add(ctx.tempVars) + + let blockStmt = newNodeI(nkBlockStmt, n.info) + blockStmt.add(newSymNode(ctx.stateLoopLabel)) + + let gs = newNodeI(nkGotoState, n.info) + gs.add(ctx.newStateAccess()) + gs.add(ctx.g.newIntLit(n.info, ctx.states.len - 1)) + + var blockBody = newTree(nkStmtList, gs, n) + if ctx.hasExceptions: + blockBody = ctx.wrapIntoTryExcept(blockBody) + + blockStmt.add(blockBody) + loopBody.add(blockStmt) + +proc deleteEmptyStates(ctx: var Ctx) = + let goOut = newTree(nkGotoState, ctx.g.newIntLit(TLineInfo(), -1)) + ctx.exitStateIdx = ctx.newState(goOut, nil) + + # Apply new state indexes and mark unused states with -1 + var iValid = 0 + for i, s in ctx.states: + let body = skipStmtList(ctx, s[1]) + if body.kind == nkGotoState and i != ctx.states.len - 1 and i != 0: + # This is an empty state. Mark with -1. + s[0].intVal = -1 + else: + s[0].intVal = iValid + inc iValid + + for i, s in ctx.states: + let body = skipStmtList(ctx, s[1]) + if body.kind != nkGotoState or i == 0: + discard ctx.skipThroughEmptyStates(s) + let excHandlState = ctx.exceptionTable[i] + if excHandlState < 0: + ctx.exceptionTable[i] = -ctx.skipEmptyStates(-excHandlState) + elif excHandlState != 0: + ctx.exceptionTable[i] = ctx.skipEmptyStates(excHandlState) + + var i = 0 + while i < ctx.states.len - 1: + let fs = skipStmtList(ctx, ctx.states[i][1]) + if fs.kind == nkGotoState and i != 0: + ctx.states.delete(i) + ctx.exceptionTable.delete(i) + else: + inc i + +proc transformClosureIterator*(g: ModuleGraph; fn: PSym, n: PNode): PNode = + var ctx: Ctx + ctx.g = g + ctx.fn = fn + + if getEnvParam(fn).isNil: + # Lambda lifting was not done yet. Use temporary :state sym, which + # be handled specially by lambda lifting. Local temp vars (if needed) + # should folllow the same logic. + ctx.stateVarSym = newSym(skVar, getIdent(ctx.g.cache, ":state"), fn, fn.info) + ctx.stateVarSym.typ = g.createClosureIterStateType(fn) + + ctx.stateLoopLabel = newSym(skLabel, getIdent(ctx.g.cache, ":stateLoop"), fn, fn.info) + let n = n.toStmtList + + discard ctx.newState(n, nil) + let gotoOut = newTree(nkGotoState, g.newIntLit(n.info, -1)) + + # Splitting transformation + discard ctx.transformClosureIteratorBody(n, gotoOut) + + # Optimize empty states away + ctx.deleteEmptyStates() + + # Make new body by concating the list of states + result = newNodeI(nkStmtList, n.info) + for s in ctx.states: + assert(s.len == 2) + let body = s[1] + s.sons.del(1) + result.add(s) + result.add(body) + + result = ctx.tranformStateAssignments(result) + result = ctx.wrapIntoStateLoop(result) + + # echo "TRANSFORM TO STATES: " + # echo renderTree(result) + + # echo "exception table:" + # for i, e in ctx.exceptionTable: + # echo i, " -> ", e diff --git a/compiler/cmdlinehelper.nim b/compiler/cmdlinehelper.nim new file mode 100644 index 000000000..9d2334af5 --- /dev/null +++ b/compiler/cmdlinehelper.nim @@ -0,0 +1,85 @@ +## Helpers for binaries that use compiler passes, eg: nim, nimsuggest, nimfix + +# TODO: nimfix should use this; currently out of sync + +import + compiler/[options, idents, nimconf, scriptconfig, extccomp, commands, msgs, lineinfos, modulegraphs, condsyms], + std/os + +type + NimProg* = ref object + suggestMode*: bool + supportsStdinFile*: bool + processCmdLine*: proc(pass: TCmdLinePass, cmd: string; config: ConfigRef) + mainCommand*: proc(graph: ModuleGraph) + +proc initDefinesProg*(self: NimProg, conf: ConfigRef, name: string) = + condsyms.initDefines(conf.symbols) + defineSymbol conf.symbols, name + +proc processCmdLineAndProjectPath*(self: NimProg, conf: ConfigRef) = + self.processCmdLine(passCmd1, "", conf) + if self.supportsStdinFile and conf.projectName == "-": + conf.projectName = "stdinfile" + conf.projectFull = "stdinfile" + conf.projectPath = canonicalizePath(conf, getCurrentDir()) + conf.projectIsStdin = true + elif conf.projectName != "": + try: + conf.projectFull = canonicalizePath(conf, conf.projectName) + except OSError: + conf.projectFull = conf.projectName + let p = splitFile(conf.projectFull) + let dir = if p.dir.len > 0: p.dir else: getCurrentDir() + conf.projectPath = canonicalizePath(conf, dir) + conf.projectName = p.name + else: + conf.projectPath = canonicalizePath(conf, getCurrentDir()) + +proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: ConfigRef): bool = + loadConfigs(DefaultConfig, cache, conf) # load all config files + if self.suggestMode: + conf.command = "nimsuggest" + + proc runNimScriptIfExists(path: string)= + if fileExists(path): + runNimScript(cache, path, freshDefines = false, conf) + + # Caution: make sure this stays in sync with `loadConfigs` + if optSkipSystemConfigFile notin conf.globalOptions: + runNimScriptIfExists(getSystemConfigPath(conf, DefaultConfigNims)) + + if optSkipUserConfigFile notin conf.globalOptions: + runNimScriptIfExists(getUserConfigPath(DefaultConfigNims)) + + if optSkipParentConfigFiles notin conf.globalOptions: + for dir in parentDirs(conf.projectPath, fromRoot = true, inclusive = false): + runNimScriptIfExists(dir / DefaultConfigNims) + + if optSkipProjConfigFile notin conf.globalOptions: + runNimScriptIfExists(conf.projectPath / DefaultConfigNims) + block: + let scriptFile = conf.projectFull.changeFileExt("nims") + if not self.suggestMode: + runNimScriptIfExists(scriptFile) + # 'nim foo.nims' means to just run the NimScript file and do nothing more: + if fileExists(scriptFile) and scriptFile.cmpPaths(conf.projectFull) == 0: + return false + else: + if scriptFile.cmpPaths(conf.projectFull) != 0: + runNimScriptIfExists(scriptFile) + else: + # 'nimsuggest foo.nims' means to just auto-complete the NimScript file + discard + + # now process command line arguments again, because some options in the + # command line can overwite the config file's settings + extccomp.initVars(conf) + self.processCmdLine(passCmd2, "", conf) + if conf.command == "": + rawMessage(conf, errGenerated, "command missing") + + let graph = newModuleGraph(cache, conf) + graph.suggestMode = self.suggestMode + self.mainCommand(graph) + return true diff --git a/compiler/commands.nim b/compiler/commands.nim index 766b78798..b47ccf610 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -18,7 +18,6 @@ template bootSwitch(name, expr, userString) = bootSwitch(usedRelease, defined(release), "-d:release") bootSwitch(usedGnuReadline, defined(useLinenoise), "-d:useLinenoise") -bootSwitch(usedNoCaas, defined(noCaas), "-d:noCaas") bootSwitch(usedBoehm, defined(boehmgc), "--gc:boehm") bootSwitch(usedMarkAndSweep, defined(gcmarkandsweep), "--gc:markAndSweep") bootSwitch(usedGenerational, defined(gcgenerational), "--gc:generational") @@ -27,90 +26,100 @@ bootSwitch(usedNoGC, defined(nogc), "--gc:none") import os, msgs, options, nversion, condsyms, strutils, extccomp, platform, - wordrecg, parseutils, nimblecmd, idents, parseopt, sequtils + wordrecg, parseutils, nimblecmd, idents, parseopt, sequtils, lineinfos # but some have deps to imported modules. Yay. bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc") -bootSwitch(usedAvoidTimeMachine, noTimeMachine, "-d:avoidTimeMachine") bootSwitch(usedNativeStacktrace, defined(nativeStackTrace) and nativeStackTraceSupported, "-d:nativeStackTrace") bootSwitch(usedFFI, hasFFI, "-d:useFFI") - -proc writeCommandLineUsage*() - type TCmdLinePass* = enum passCmd1, # first pass over the command line passCmd2, # second pass over the command line passPP # preprocessor called processCommand() -proc processCommand*(switch: string, pass: TCmdLinePass) -proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; - config: ConfigRef = nil) - -# implementation - const HelpMessage = "Nim Compiler Version $1 [$2: $3]\n" & + "Compiled at $4\n" & "Copyright (c) 2006-" & copyrightYear & " by Andreas Rumpf\n" const Usage = slurp"../doc/basicopt.txt".replace("//", "") - AdvancedUsage = slurp"../doc/advopt.txt".replace("//", "") - -proc getCommandLineDesc(): string = - result = (HelpMessage % [VersionAsString, platform.OS[platform.hostOS].name, - CPU[platform.hostCPU].name]) & Usage + FeatureDesc = block: + var x = "" + for f in low(Feature)..high(Feature): + if x.len > 0: x.add "|" + x.add $f + x + AdvancedUsage = slurp"../doc/advopt.txt".replace("//", "") % FeatureDesc + +proc getCommandLineDesc(conf: ConfigRef): string = + result = (HelpMessage % [VersionAsString, platform.OS[conf.target.hostOS].name, + CPU[conf.target.hostCPU].name, CompileDate]) & + Usage + +proc helpOnError(conf: ConfigRef; pass: TCmdLinePass) = + if pass == passCmd1: + msgWriteln(conf, getCommandLineDesc(conf), {msgStdout}) + msgQuit(0) -proc helpOnError(pass: TCmdLinePass) = +proc writeAdvancedUsage(conf: ConfigRef; pass: TCmdLinePass) = if pass == passCmd1: - msgWriteln(getCommandLineDesc(), {msgStdout}) + msgWriteln(conf, (HelpMessage % [VersionAsString, + platform.OS[conf.target.hostOS].name, + CPU[conf.target.hostCPU].name, CompileDate]) & + AdvancedUsage, + {msgStdout}) msgQuit(0) -proc writeAdvancedUsage(pass: TCmdLinePass) = +proc writeFullhelp(conf: ConfigRef; pass: TCmdLinePass) = if pass == passCmd1: - msgWriteln(`%`(HelpMessage, [VersionAsString, - platform.OS[platform.hostOS].name, - CPU[platform.hostCPU].name]) & AdvancedUsage, + msgWriteln(conf, `%`(HelpMessage, [VersionAsString, + platform.OS[conf.target.hostOS].name, + CPU[conf.target.hostCPU].name, CompileDate]) & + Usage & AdvancedUsage, {msgStdout}) msgQuit(0) -proc writeVersionInfo(pass: TCmdLinePass) = +proc writeVersionInfo(conf: ConfigRef; pass: TCmdLinePass) = if pass == passCmd1: - msgWriteln(`%`(HelpMessage, [VersionAsString, - platform.OS[platform.hostOS].name, - CPU[platform.hostCPU].name]), + msgWriteln(conf, `%`(HelpMessage, [VersionAsString, + platform.OS[conf.target.hostOS].name, + CPU[conf.target.hostCPU].name, CompileDate]), {msgStdout}) const gitHash = gorge("git log -n 1 --format=%H").strip when gitHash.len == 40: - msgWriteln("git hash: " & gitHash, {msgStdout}) + msgWriteln(conf, "git hash: " & gitHash, {msgStdout}) - msgWriteln("active boot switches:" & usedRelease & usedAvoidTimeMachine & - usedTinyC & usedGnuReadline & usedNativeStacktrace & usedNoCaas & + msgWriteln(conf, "active boot switches:" & usedRelease & + usedTinyC & usedGnuReadline & usedNativeStacktrace & usedFFI & usedBoehm & usedMarkAndSweep & usedGenerational & usedGoGC & usedNoGC, {msgStdout}) msgQuit(0) -var - helpWritten: bool - -proc writeCommandLineUsage() = +proc writeCommandLineUsage*(conf: ConfigRef; helpWritten: var bool) = if not helpWritten: - msgWriteln(getCommandLineDesc(), {msgStdout}) + msgWriteln(conf, getCommandLineDesc(conf), {msgStdout}) helpWritten = true proc addPrefix(switch: string): string = if len(switch) == 1: result = "-" & switch else: result = "--" & switch -proc invalidCmdLineOption(pass: TCmdLinePass, switch: string, info: TLineInfo) = - if switch == " ": localError(info, errInvalidCmdLineOption, "-") - else: localError(info, errInvalidCmdLineOption, addPrefix(switch)) +const + errInvalidCmdLineOption = "invalid command line option: '$1'" + errOnOrOffExpectedButXFound = "'on' or 'off' expected, but '$1' found" + errOnOffOrListExpectedButXFound = "'on', 'off' or 'list' expected, but '$1' found" + +proc invalidCmdLineOption(conf: ConfigRef; pass: TCmdLinePass, switch: string, info: TLineInfo) = + if switch == " ": localError(conf, info, errInvalidCmdLineOption % "-") + else: localError(conf, info, errInvalidCmdLineOption % addPrefix(switch)) -proc splitSwitch(switch: string, cmd, arg: var string, pass: TCmdLinePass, +proc splitSwitch(conf: ConfigRef; switch: string, cmd, arg: var string, pass: TCmdLinePass, info: TLineInfo) = cmd = "" var i = 0 @@ -122,618 +131,652 @@ proc splitSwitch(switch: string, cmd, arg: var string, pass: TCmdLinePass, else: break inc(i) if i >= len(switch): arg = "" - elif switch[i] in {':', '=', '['}: arg = substr(switch, i + 1) - else: invalidCmdLineOption(pass, switch, info) + # cmd:arg => (cmd,arg) + elif switch[i] in {':', '='}: arg = substr(switch, i + 1) + # cmd[sub]:rest => (cmd,[sub]:rest) + elif switch[i] == '[': arg = substr(switch, i) + else: invalidCmdLineOption(conf, pass, switch, info) -proc processOnOffSwitch(op: TOptions, arg: string, pass: TCmdLinePass, +proc processOnOffSwitch(conf: ConfigRef; op: TOptions, arg: string, pass: TCmdLinePass, info: TLineInfo) = case arg.normalize - of "on": gOptions = gOptions + op - of "off": gOptions = gOptions - op - else: localError(info, errOnOrOffExpectedButXFound, arg) + of "on": conf.options = conf.options + op + of "off": conf.options = conf.options - op + else: localError(conf, info, errOnOrOffExpectedButXFound % arg) -proc processOnOffSwitchOrList(op: TOptions, arg: string, pass: TCmdLinePass, +proc processOnOffSwitchOrList(conf: ConfigRef; op: TOptions, arg: string, pass: TCmdLinePass, info: TLineInfo): bool = result = false case arg.normalize - of "on": gOptions = gOptions + op - of "off": gOptions = gOptions - op - else: - if arg == "list": - result = true - else: - localError(info, errOnOffOrListExpectedButXFound, arg) + of "on": conf.options = conf.options + op + of "off": conf.options = conf.options - op + of "list": result = true + else: localError(conf, info, errOnOffOrListExpectedButXFound % arg) -proc processOnOffSwitchG(op: TGlobalOptions, arg: string, pass: TCmdLinePass, +proc processOnOffSwitchG(conf: ConfigRef; op: TGlobalOptions, arg: string, pass: TCmdLinePass, info: TLineInfo) = case arg.normalize - of "on": gGlobalOptions = gGlobalOptions + op - of "off": gGlobalOptions = gGlobalOptions - op - else: localError(info, errOnOrOffExpectedButXFound, arg) + of "on": conf.globalOptions = conf.globalOptions + op + of "off": conf.globalOptions = conf.globalOptions - op + else: localError(conf, info, errOnOrOffExpectedButXFound % arg) -proc expectArg(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) = - if arg == "": localError(info, errCmdLineArgExpected, addPrefix(switch)) +proc expectArg(conf: ConfigRef; switch, arg: string, pass: TCmdLinePass, info: TLineInfo) = + if arg == "": + localError(conf, info, "argument for command line option expected: '$1'" % addPrefix(switch)) -proc expectNoArg(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) = - if arg != "": localError(info, errCmdLineNoArgExpected, addPrefix(switch)) - -var - enableNotes: TNoteKinds - disableNotes: TNoteKinds +proc expectNoArg(conf: ConfigRef; switch, arg: string, pass: TCmdLinePass, info: TLineInfo) = + if arg != "": + localError(conf, info, "invalid argument for command line option: '$1'" % addPrefix(switch)) proc processSpecificNote*(arg: string, state: TSpecialWord, pass: TCmdLinePass, - info: TLineInfo; orig: string) = - var id = "" # arg = "X]:on|off" + info: TLineInfo; orig: string; conf: ConfigRef) = + var id = "" # arg = key:val or [key]:val; with val=on|off var i = 0 var n = hintMin - while i < len(arg) and (arg[i] != ']'): + var isBracket = false + if i < len(arg) and arg[i] == '[': + isBracket = true + inc(i) + while i < len(arg) and (arg[i] notin {':', '=', ']'}): add(id, arg[i]) inc(i) - if i < len(arg) and (arg[i] == ']'): inc(i) - else: invalidCmdLineOption(pass, orig, info) + if isBracket: + if i < len(arg) and arg[i] == ']': inc(i) + else: invalidCmdLineOption(conf, pass, orig, info) + if i < len(arg) and (arg[i] in {':', '='}): inc(i) - else: invalidCmdLineOption(pass, orig, info) + else: invalidCmdLineOption(conf, pass, orig, info) if state == wHint: - var x = findStr(msgs.HintsToStr, id) + let x = findStr(lineinfos.HintsToStr, id) if x >= 0: n = TNoteKind(x + ord(hintMin)) - else: localError(info, "unknown hint: " & id) + else: localError(conf, info, "unknown hint: " & id) else: - var x = findStr(msgs.WarningsToStr, id) + let x = findStr(lineinfos.WarningsToStr, id) if x >= 0: n = TNoteKind(x + ord(warnMin)) - else: localError(info, "unknown warning: " & id) + else: localError(conf, info, "unknown warning: " & id) case substr(arg, i).normalize of "on": - incl(gNotes, n) - incl(gMainPackageNotes, n) - incl(enableNotes, n) + incl(conf.notes, n) + incl(conf.mainPackageNotes, n) + incl(conf.enableNotes, n) of "off": - excl(gNotes, n) - excl(gMainPackageNotes, n) - incl(disableNotes, n) - excl(ForeignPackageNotes, n) - else: localError(info, errOnOrOffExpectedButXFound, arg) - -proc processCompile(filename: string) = - var found = findFile(filename) + excl(conf.notes, n) + excl(conf.mainPackageNotes, n) + incl(conf.disableNotes, n) + excl(conf.foreignPackageNotes, n) + else: localError(conf, info, errOnOrOffExpectedButXFound % arg) + +proc processCompile(conf: ConfigRef; filename: string) = + var found = findFile(conf, filename) if found == "": found = filename - extccomp.addExternalFileToCompile(found) + extccomp.addExternalFileToCompile(conf, found) + +const + errNoneBoehmRefcExpectedButXFound = "'none', 'boehm' or 'refc' expected, but '$1' found" + errNoneSpeedOrSizeExpectedButXFound = "'none', 'speed' or 'size' expected, but '$1' found" + errGuiConsoleOrLibExpectedButXFound = "'gui', 'console' or 'lib' expected, but '$1' found" -proc testCompileOptionArg*(switch, arg: string, info: TLineInfo): bool = +proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo): bool = case switch.normalize of "gc": case arg.normalize - of "boehm": result = gSelectedGC == gcBoehm - of "refc": result = gSelectedGC == gcRefc - of "v2": result = gSelectedGC == gcV2 - of "markandsweep": result = gSelectedGC == gcMarkAndSweep - of "generational": result = gSelectedGC == gcGenerational - of "go": result = gSelectedGC == gcGo - of "none": result = gSelectedGC == gcNone - of "stack", "regions": result = gSelectedGC == gcRegions - else: localError(info, errNoneBoehmRefcExpectedButXFound, arg) + of "boehm": result = conf.selectedGC == gcBoehm + of "refc": result = conf.selectedGC == gcRefc + of "v2": result = conf.selectedGC == gcV2 + of "markandsweep": result = conf.selectedGC == gcMarkAndSweep + of "generational": result = false + of "destructors": result = conf.selectedGC == gcDestructors + of "go": result = conf.selectedGC == gcGo + of "none": result = conf.selectedGC == gcNone + of "stack", "regions": result = conf.selectedGC == gcRegions + else: localError(conf, info, errNoneBoehmRefcExpectedButXFound % arg) of "opt": case arg.normalize - of "speed": result = contains(gOptions, optOptimizeSpeed) - of "size": result = contains(gOptions, optOptimizeSize) - of "none": result = gOptions * {optOptimizeSpeed, optOptimizeSize} == {} - else: localError(info, errNoneSpeedOrSizeExpectedButXFound, arg) - of "verbosity": result = $gVerbosity == arg + of "speed": result = contains(conf.options, optOptimizeSpeed) + of "size": result = contains(conf.options, optOptimizeSize) + of "none": result = conf.options * {optOptimizeSpeed, optOptimizeSize} == {} + else: localError(conf, info, errNoneSpeedOrSizeExpectedButXFound % arg) + of "verbosity": result = $conf.verbosity == arg of "app": case arg.normalize - of "gui": result = contains(gGlobalOptions, optGenGuiApp) - of "console": result = not contains(gGlobalOptions, optGenGuiApp) - of "lib": result = contains(gGlobalOptions, optGenDynLib) and - not contains(gGlobalOptions, optGenGuiApp) - of "staticlib": result = contains(gGlobalOptions, optGenStaticLib) and - not contains(gGlobalOptions, optGenGuiApp) - else: localError(info, errGuiConsoleOrLibExpectedButXFound, arg) + of "gui": result = contains(conf.globalOptions, optGenGuiApp) + of "console": result = not contains(conf.globalOptions, optGenGuiApp) + of "lib": result = contains(conf.globalOptions, optGenDynLib) and + not contains(conf.globalOptions, optGenGuiApp) + of "staticlib": result = contains(conf.globalOptions, optGenStaticLib) and + not contains(conf.globalOptions, optGenGuiApp) + else: localError(conf, info, errGuiConsoleOrLibExpectedButXFound % arg) of "dynliboverride": - result = isDynlibOverride(arg) - else: invalidCmdLineOption(passCmd1, switch, info) + result = isDynlibOverride(conf, arg) + else: invalidCmdLineOption(conf, passCmd1, switch, info) -proc testCompileOption*(switch: string, info: TLineInfo): bool = +proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool = case switch.normalize - of "debuginfo": result = contains(gGlobalOptions, optCDebug) - of "compileonly", "c": result = contains(gGlobalOptions, optCompileOnly) - of "nolinking": result = contains(gGlobalOptions, optNoLinking) - of "nomain": result = contains(gGlobalOptions, optNoMain) - of "forcebuild", "f": result = contains(gGlobalOptions, optForceFullMake) - of "warnings", "w": result = contains(gOptions, optWarns) - of "hints": result = contains(gOptions, optHints) - of "threadanalysis": result = contains(gGlobalOptions, optThreadAnalysis) - of "stacktrace": result = contains(gOptions, optStackTrace) - of "linetrace": result = contains(gOptions, optLineTrace) - of "debugger": result = contains(gOptions, optEndb) - of "profiler": result = contains(gOptions, optProfiler) - of "memtracker": result = contains(gOptions, optMemTracker) - of "checks", "x": result = gOptions * ChecksOptions == ChecksOptions + of "debuginfo": result = contains(conf.globalOptions, optCDebug) + of "compileonly", "c": result = contains(conf.globalOptions, optCompileOnly) + of "nolinking": result = contains(conf.globalOptions, optNoLinking) + of "nomain": result = contains(conf.globalOptions, optNoMain) + of "forcebuild", "f": result = contains(conf.globalOptions, optForceFullMake) + of "warnings", "w": result = contains(conf.options, optWarns) + of "hints": result = contains(conf.options, optHints) + of "threadanalysis": result = contains(conf.globalOptions, optThreadAnalysis) + of "stacktrace": result = contains(conf.options, optStackTrace) + of "linetrace": result = contains(conf.options, optLineTrace) + of "debugger": result = contains(conf.options, optEndb) + of "profiler": result = contains(conf.options, optProfiler) + of "memtracker": result = contains(conf.options, optMemTracker) + of "checks", "x": result = conf.options * ChecksOptions == ChecksOptions of "floatchecks": - result = gOptions * {optNaNCheck, optInfCheck} == {optNaNCheck, optInfCheck} - of "infchecks": result = contains(gOptions, optInfCheck) - of "nanchecks": result = contains(gOptions, optNaNCheck) - of "nilchecks": result = contains(gOptions, optNilCheck) - of "objchecks": result = contains(gOptions, optObjCheck) - of "fieldchecks": result = contains(gOptions, optFieldCheck) - of "rangechecks": result = contains(gOptions, optRangeCheck) - of "boundchecks": result = contains(gOptions, optBoundsCheck) - of "overflowchecks": result = contains(gOptions, optOverflowCheck) - of "linedir": result = contains(gOptions, optLineDir) - of "assertions", "a": result = contains(gOptions, optAssert) - of "deadcodeelim": result = contains(gGlobalOptions, optDeadCodeElim) - of "run", "r": result = contains(gGlobalOptions, optRun) - of "symbolfiles": result = gSymbolFiles != disabledSf - of "genscript": result = contains(gGlobalOptions, optGenScript) - of "threads": result = contains(gGlobalOptions, optThreads) - of "taintmode": result = contains(gGlobalOptions, optTaintMode) - of "tlsemulation": result = contains(gGlobalOptions, optTlsEmulation) - of "implicitstatic": result = contains(gOptions, optImplicitStatic) - of "patterns": result = contains(gOptions, optPatterns) - of "experimental": result = gExperimentalMode - of "excessivestacktrace": result = contains(gGlobalOptions, optExcessiveStackTrace) - else: invalidCmdLineOption(passCmd1, switch, info) - -proc processPath(path: string, info: TLineInfo, + result = conf.options * {optNaNCheck, optInfCheck} == {optNaNCheck, optInfCheck} + of "infchecks": result = contains(conf.options, optInfCheck) + of "nanchecks": result = contains(conf.options, optNaNCheck) + of "nilchecks": result = contains(conf.options, optNilCheck) + of "objchecks": result = contains(conf.options, optObjCheck) + of "fieldchecks": result = contains(conf.options, optFieldCheck) + of "rangechecks": result = contains(conf.options, optRangeCheck) + of "boundchecks": result = contains(conf.options, optBoundsCheck) + of "overflowchecks": result = contains(conf.options, optOverflowCheck) + of "movechecks": result = contains(conf.options, optMoveCheck) + of "linedir": result = contains(conf.options, optLineDir) + of "assertions", "a": result = contains(conf.options, optAssert) + of "run", "r": result = contains(conf.globalOptions, optRun) + of "symbolfiles": result = conf.symbolFiles != disabledSf + of "genscript": result = contains(conf.globalOptions, optGenScript) + of "threads": result = contains(conf.globalOptions, optThreads) + of "taintmode": result = contains(conf.globalOptions, optTaintMode) + of "tlsemulation": result = contains(conf.globalOptions, optTlsEmulation) + of "implicitstatic": result = contains(conf.options, optImplicitStatic) + of "patterns": result = contains(conf.options, optPatterns) + of "excessivestacktrace": result = contains(conf.globalOptions, optExcessiveStackTrace) + of "nilseqs": result = contains(conf.options, optNilSeqs) + else: invalidCmdLineOption(conf, passCmd1, switch, info) + +proc processPath(conf: ConfigRef; path: string, info: TLineInfo, notRelativeToProj = false): string = let p = if os.isAbsolute(path) or '$' in path: path elif notRelativeToProj: getCurrentDir() / path else: - options.gProjectPath / path + conf.projectPath / path try: - result = pathSubs(p, info.toFullPath().splitFile().dir) + result = pathSubs(conf, p, toFullPath(conf, info).splitFile().dir) except ValueError: - localError(info, "invalid path: " & p) + localError(conf, info, "invalid path: " & p) result = p -proc processCfgPath(path: string, info: TLineInfo): string = +proc processCfgPath(conf: ConfigRef; path: string, info: TLineInfo): string = let path = if path[0] == '"': strutils.unescape(path) else: path - let basedir = info.toFullPath().splitFile().dir + let basedir = toFullPath(conf, info).splitFile().dir let p = if os.isAbsolute(path) or '$' in path: path else: basedir / path try: - result = pathSubs(p, basedir) + result = pathSubs(conf, p, basedir) except ValueError: - localError(info, "invalid path: " & p) + localError(conf, info, "invalid path: " & p) result = p -proc trackDirty(arg: string, info: TLineInfo) = +const + errInvalidNumber = "$1 is not a valid number" + +proc trackDirty(conf: ConfigRef; arg: string, info: TLineInfo) = var a = arg.split(',') - if a.len != 4: localError(info, errTokenExpected, - "DIRTY_BUFFER,ORIGINAL_FILE,LINE,COLUMN") + if a.len != 4: localError(conf, info, + "DIRTY_BUFFER,ORIGINAL_FILE,LINE,COLUMN expected") var line, column: int if parseUtils.parseInt(a[2], line) <= 0: - localError(info, errInvalidNumber, a[1]) + localError(conf, info, errInvalidNumber % a[1]) if parseUtils.parseInt(a[3], column) <= 0: - localError(info, errInvalidNumber, a[2]) + localError(conf, info, errInvalidNumber % a[2]) - let dirtyOriginalIdx = a[1].fileInfoIdx - if dirtyOriginalIdx >= 0: - msgs.setDirtyFile(dirtyOriginalIdx, a[0]) + let dirtyOriginalIdx = fileInfoIdx(conf, a[1]) + if dirtyOriginalIdx.int32 >= 0: + msgs.setDirtyFile(conf, dirtyOriginalIdx, a[0]) - gTrackPos = newLineInfo(dirtyOriginalIdx, line, column) + conf.m.trackPos = newLineInfo(dirtyOriginalIdx, line, column) -proc track(arg: string, info: TLineInfo) = +proc track(conf: ConfigRef; arg: string, info: TLineInfo) = var a = arg.split(',') - if a.len != 3: localError(info, errTokenExpected, "FILE,LINE,COLUMN") + if a.len != 3: localError(conf, info, "FILE,LINE,COLUMN expected") var line, column: int if parseUtils.parseInt(a[1], line) <= 0: - localError(info, errInvalidNumber, a[1]) + localError(conf, info, errInvalidNumber % a[1]) if parseUtils.parseInt(a[2], column) <= 0: - localError(info, errInvalidNumber, a[2]) - gTrackPos = newLineInfo(a[0], line, column) + localError(conf, info, errInvalidNumber % a[2]) + conf.m.trackPos = newLineInfo(conf, a[0], line, column) -proc dynlibOverride(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) = +proc dynlibOverride(conf: ConfigRef; switch, arg: string, pass: TCmdLinePass, info: TLineInfo) = if pass in {passCmd2, passPP}: - expectArg(switch, arg, pass, info) - options.inclDynlibOverride(arg) + expectArg(conf, switch, arg, pass, info) + options.inclDynlibOverride(conf, arg) -proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; - config: ConfigRef = nil) = +proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; + conf: ConfigRef) = var - theOS: TSystemOS - cpu: TSystemCPU key, val: string case switch.normalize of "path", "p": - expectArg(switch, arg, pass, info) - addPath(if pass == passPP: processCfgPath(arg, info) else: processPath(arg, info), info) + expectArg(conf, switch, arg, pass, info) + addPath(conf, if pass == passPP: processCfgPath(conf, arg, info) else: processPath(conf, arg, info), info) of "nimblepath", "babelpath": # keep the old name for compat - if pass in {passCmd2, passPP} and not options.gNoNimblePath: - expectArg(switch, arg, pass, info) - var path = processPath(arg, info, notRelativeToProj=true) + if pass in {passCmd2, passPP} and optNoNimblePath notin conf.globalOptions: + expectArg(conf, switch, arg, pass, info) + var path = processPath(conf, arg, info, notRelativeToProj=true) let nimbleDir = getEnv("NIMBLE_DIR") if nimbleDir.len > 0 and pass == passPP: path = nimbleDir / "pkgs" - nimblePath(path, info) + nimblePath(conf, path, info) of "nonimblepath", "nobabelpath": - expectNoArg(switch, arg, pass, info) - disableNimblePath() + expectNoArg(conf, switch, arg, pass, info) + disableNimblePath(conf) of "excludepath": - expectArg(switch, arg, pass, info) - let path = processPath(arg, info) + expectArg(conf, switch, arg, pass, info) + let path = processPath(conf, arg, info) - options.searchPaths.keepItIf( cmpPaths(it, path) != 0 ) - options.lazyPaths.keepItIf( cmpPaths(it, path) != 0 ) + conf.searchPaths.keepItIf(cmpPaths(it, path) != 0) + conf.lazyPaths.keepItIf(cmpPaths(it, path) != 0) if (len(path) > 0) and (path[len(path) - 1] == DirSep): let strippedPath = path[0 .. (len(path) - 2)] - options.searchPaths.keepItIf( cmpPaths(it, strippedPath) != 0 ) - options.lazyPaths.keepItIf( cmpPaths(it, strippedPath) != 0 ) + conf.searchPaths.keepItIf(cmpPaths(it, strippedPath) != 0) + conf.lazyPaths.keepItIf(cmpPaths(it, strippedPath) != 0) of "nimcache": - expectArg(switch, arg, pass, info) - options.nimcacheDir = processPath(arg, info, true) + expectArg(conf, switch, arg, pass, info) + conf.nimcacheDir = processPath(conf, arg, info, true) of "out", "o": - expectArg(switch, arg, pass, info) - options.outFile = arg + expectArg(conf, switch, arg, pass, info) + conf.outFile = arg of "docseesrcurl": - expectArg(switch, arg, pass, info) - options.docSeeSrcUrl = arg + expectArg(conf, switch, arg, pass, info) + conf.docSeeSrcUrl = arg of "mainmodule", "m": discard "allow for backwards compatibility, but don't do anything" of "define", "d": - expectArg(switch, arg, pass, info) + expectArg(conf, switch, arg, pass, info) if {':', '='} in arg: - splitSwitch(arg, key, val, pass, info) - defineSymbol(key, val) + splitSwitch(conf, arg, key, val, pass, info) + defineSymbol(conf.symbols, key, val) else: - defineSymbol(arg) + defineSymbol(conf.symbols, arg) of "undef", "u": - expectArg(switch, arg, pass, info) - undefSymbol(arg) + expectArg(conf, switch, arg, pass, info) + undefSymbol(conf.symbols, arg) of "symbol": - expectArg(switch, arg, pass, info) + expectArg(conf, switch, arg, pass, info) # deprecated, do nothing of "compile": - expectArg(switch, arg, pass, info) - if pass in {passCmd2, passPP}: processCompile(arg) + expectArg(conf, switch, arg, pass, info) + if pass in {passCmd2, passPP}: processCompile(conf, arg) of "link": - expectArg(switch, arg, pass, info) - if pass in {passCmd2, passPP}: addExternalFileToLink(arg) + expectArg(conf, switch, arg, pass, info) + if pass in {passCmd2, passPP}: addExternalFileToLink(conf, arg) of "debuginfo": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optCDebug) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optCDebug) of "embedsrc": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optEmbedOrigSrc) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optEmbedOrigSrc) of "compileonly", "c": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optCompileOnly) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optCompileOnly) of "nolinking": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optNoLinking) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optNoLinking) of "nomain": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optNoMain) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optNoMain) of "forcebuild", "f": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optForceFullMake) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optForceFullMake) of "project": - expectNoArg(switch, arg, pass, info) - gWholeProject = true + expectNoArg(conf, switch, arg, pass, info) + incl conf.globalOptions, optWholeProject of "gc": - expectArg(switch, arg, pass, info) + expectArg(conf, switch, arg, pass, info) case arg.normalize of "boehm": - gSelectedGC = gcBoehm - defineSymbol("boehmgc") + conf.selectedGC = gcBoehm + defineSymbol(conf.symbols, "boehmgc") of "refc": - gSelectedGC = gcRefc + conf.selectedGC = gcRefc of "v2": - gSelectedGC = gcV2 + conf.selectedGC = gcV2 of "markandsweep": - gSelectedGC = gcMarkAndSweep - defineSymbol("gcmarkandsweep") - of "generational": - gSelectedGC = gcGenerational - defineSymbol("gcgenerational") + conf.selectedGC = gcMarkAndSweep + defineSymbol(conf.symbols, "gcmarkandsweep") + of "destructors": + conf.selectedGC = gcDestructors + defineSymbol(conf.symbols, "gcdestructors") of "go": - gSelectedGC = gcGo - defineSymbol("gogc") + conf.selectedGC = gcGo + defineSymbol(conf.symbols, "gogc") of "none": - gSelectedGC = gcNone - defineSymbol("nogc") + conf.selectedGC = gcNone + defineSymbol(conf.symbols, "nogc") of "stack", "regions": - gSelectedGC= gcRegions - defineSymbol("gcregions") - else: localError(info, errNoneBoehmRefcExpectedButXFound, arg) + conf.selectedGC= gcRegions + defineSymbol(conf.symbols, "gcregions") + else: localError(conf, info, errNoneBoehmRefcExpectedButXFound % arg) of "warnings", "w": - if processOnOffSwitchOrList({optWarns}, arg, pass, info): listWarnings() - of "warning": processSpecificNote(arg, wWarning, pass, info, switch) - of "hint": processSpecificNote(arg, wHint, pass, info, switch) + if processOnOffSwitchOrList(conf, {optWarns}, arg, pass, info): listWarnings(conf) + of "warning": processSpecificNote(arg, wWarning, pass, info, switch, conf) + of "hint": processSpecificNote(arg, wHint, pass, info, switch, conf) of "hints": - if processOnOffSwitchOrList({optHints}, arg, pass, info): listHints() - of "threadanalysis": processOnOffSwitchG({optThreadAnalysis}, arg, pass, info) - of "stacktrace": processOnOffSwitch({optStackTrace}, arg, pass, info) - of "excessivestacktrace": processOnOffSwitchG({optExcessiveStackTrace}, arg, pass, info) - of "linetrace": processOnOffSwitch({optLineTrace}, arg, pass, info) + if processOnOffSwitchOrList(conf, {optHints}, arg, pass, info): listHints(conf) + of "threadanalysis": processOnOffSwitchG(conf, {optThreadAnalysis}, arg, pass, info) + of "stacktrace": processOnOffSwitch(conf, {optStackTrace}, arg, pass, info) + of "excessivestacktrace": processOnOffSwitchG(conf, {optExcessiveStackTrace}, arg, pass, info) + of "linetrace": processOnOffSwitch(conf, {optLineTrace}, arg, pass, info) of "debugger": case arg.normalize of "on", "endb": - gOptions.incl optEndb - defineSymbol("endb") + conf.options.incl optEndb + defineSymbol(conf.symbols, "endb") of "off": - gOptions.excl optEndb - undefSymbol("endb") + conf.options.excl optEndb + undefSymbol(conf.symbols, "endb") of "native", "gdb": - incl(gGlobalOptions, optCDebug) - gOptions = gOptions + {optLineDir} - {optEndb} - defineSymbol("nimTypeNames", nil) # type names are used in gdb pretty printing - undefSymbol("endb") + incl(conf.globalOptions, optCDebug) + conf.options = conf.options + {optLineDir} - {optEndb} + defineSymbol(conf.symbols, "nimTypeNames") # type names are used in gdb pretty printing + undefSymbol(conf.symbols, "endb") else: - localError(info, "expected endb|gdb but found " & arg) + localError(conf, info, "expected endb|gdb but found " & arg) of "profiler": - processOnOffSwitch({optProfiler}, arg, pass, info) - if optProfiler in gOptions: defineSymbol("profiler") - else: undefSymbol("profiler") + processOnOffSwitch(conf, {optProfiler}, arg, pass, info) + if optProfiler in conf.options: defineSymbol(conf.symbols, "profiler") + else: undefSymbol(conf.symbols, "profiler") of "memtracker": - processOnOffSwitch({optMemTracker}, arg, pass, info) - if optMemTracker in gOptions: defineSymbol("memtracker") - else: undefSymbol("memtracker") + processOnOffSwitch(conf, {optMemTracker}, arg, pass, info) + if optMemTracker in conf.options: defineSymbol(conf.symbols, "memtracker") + else: undefSymbol(conf.symbols, "memtracker") + of "hotcodereloading": + processOnOffSwitch(conf, {optHotCodeReloading}, arg, pass, info) + if optHotCodeReloading in conf.options: defineSymbol(conf.symbols, "hotcodereloading") + else: undefSymbol(conf.symbols, "hotcodereloading") of "oldnewlines": case arg.normalize of "on": - options.gOldNewlines = true - defineSymbol("nimOldNewlines") + conf.oldNewlines = true + defineSymbol(conf.symbols, "nimOldNewlines") of "off": - options.gOldNewlines = false - undefSymbol("nimOldNewlines") + conf.oldNewlines = false + undefSymbol(conf.symbols, "nimOldNewlines") else: - localError(info, errOnOrOffExpectedButXFound, arg) - of "checks", "x": processOnOffSwitch(ChecksOptions, arg, pass, info) + localError(conf, info, errOnOrOffExpectedButXFound % arg) + of "laxstrings": processOnOffSwitch(conf, {optLaxStrings}, arg, pass, info) + of "nilseqs": processOnOffSwitch(conf, {optNilSeqs}, arg, pass, info) + of "checks", "x": processOnOffSwitch(conf, ChecksOptions, arg, pass, info) of "floatchecks": - processOnOffSwitch({optNaNCheck, optInfCheck}, arg, pass, info) - of "infchecks": processOnOffSwitch({optInfCheck}, arg, pass, info) - of "nanchecks": processOnOffSwitch({optNaNCheck}, arg, pass, info) - of "nilchecks": processOnOffSwitch({optNilCheck}, arg, pass, info) - of "objchecks": processOnOffSwitch({optObjCheck}, arg, pass, info) - of "fieldchecks": processOnOffSwitch({optFieldCheck}, arg, pass, info) - of "rangechecks": processOnOffSwitch({optRangeCheck}, arg, pass, info) - of "boundchecks": processOnOffSwitch({optBoundsCheck}, arg, pass, info) - of "overflowchecks": processOnOffSwitch({optOverflowCheck}, arg, pass, info) - of "linedir": processOnOffSwitch({optLineDir}, arg, pass, info) - of "assertions", "a": processOnOffSwitch({optAssert}, arg, pass, info) - of "deadcodeelim": processOnOffSwitchG({optDeadCodeElim}, arg, pass, info) + processOnOffSwitch(conf, {optNaNCheck, optInfCheck}, arg, pass, info) + of "infchecks": processOnOffSwitch(conf, {optInfCheck}, arg, pass, info) + of "nanchecks": processOnOffSwitch(conf, {optNaNCheck}, arg, pass, info) + of "nilchecks": processOnOffSwitch(conf, {optNilCheck}, arg, pass, info) + of "objchecks": processOnOffSwitch(conf, {optObjCheck}, arg, pass, info) + of "fieldchecks": processOnOffSwitch(conf, {optFieldCheck}, arg, pass, info) + of "rangechecks": processOnOffSwitch(conf, {optRangeCheck}, arg, pass, info) + of "boundchecks": processOnOffSwitch(conf, {optBoundsCheck}, arg, pass, info) + of "overflowchecks": processOnOffSwitch(conf, {optOverflowCheck}, arg, pass, info) + of "movechecks": processOnOffSwitch(conf, {optMoveCheck}, arg, pass, info) + of "linedir": processOnOffSwitch(conf, {optLineDir}, arg, pass, info) + of "assertions", "a": processOnOffSwitch(conf, {optAssert}, arg, pass, info) + of "deadcodeelim": discard # deprecated, dead code elim always on of "threads": - processOnOffSwitchG({optThreads}, arg, pass, info) - #if optThreads in gGlobalOptions: incl(gNotes, warnGcUnsafe) - of "tlsemulation": processOnOffSwitchG({optTlsEmulation}, arg, pass, info) - of "taintmode": processOnOffSwitchG({optTaintMode}, arg, pass, info) + processOnOffSwitchG(conf, {optThreads}, arg, pass, info) + #if optThreads in conf.globalOptions: incl(conf.notes, warnGcUnsafe) + of "tlsemulation": processOnOffSwitchG(conf, {optTlsEmulation}, arg, pass, info) + of "taintmode": processOnOffSwitchG(conf, {optTaintMode}, arg, pass, info) of "implicitstatic": - processOnOffSwitch({optImplicitStatic}, arg, pass, info) + processOnOffSwitch(conf, {optImplicitStatic}, arg, pass, info) of "patterns": - processOnOffSwitch({optPatterns}, arg, pass, info) + processOnOffSwitch(conf, {optPatterns}, arg, pass, info) of "opt": - expectArg(switch, arg, pass, info) + expectArg(conf, switch, arg, pass, info) case arg.normalize of "speed": - incl(gOptions, optOptimizeSpeed) - excl(gOptions, optOptimizeSize) + incl(conf.options, optOptimizeSpeed) + excl(conf.options, optOptimizeSize) of "size": - excl(gOptions, optOptimizeSpeed) - incl(gOptions, optOptimizeSize) + excl(conf.options, optOptimizeSpeed) + incl(conf.options, optOptimizeSize) of "none": - excl(gOptions, optOptimizeSpeed) - excl(gOptions, optOptimizeSize) - else: localError(info, errNoneSpeedOrSizeExpectedButXFound, arg) + excl(conf.options, optOptimizeSpeed) + excl(conf.options, optOptimizeSize) + else: localError(conf, info, errNoneSpeedOrSizeExpectedButXFound % arg) of "app": - expectArg(switch, arg, pass, info) + expectArg(conf, switch, arg, pass, info) case arg.normalize of "gui": - incl(gGlobalOptions, optGenGuiApp) - defineSymbol("executable") - defineSymbol("guiapp") + incl(conf.globalOptions, optGenGuiApp) + defineSymbol(conf.symbols, "executable") + defineSymbol(conf.symbols, "guiapp") of "console": - excl(gGlobalOptions, optGenGuiApp) - defineSymbol("executable") - defineSymbol("consoleapp") + excl(conf.globalOptions, optGenGuiApp) + defineSymbol(conf.symbols, "executable") + defineSymbol(conf.symbols, "consoleapp") of "lib": - incl(gGlobalOptions, optGenDynLib) - excl(gGlobalOptions, optGenGuiApp) - defineSymbol("library") - defineSymbol("dll") + incl(conf.globalOptions, optGenDynLib) + excl(conf.globalOptions, optGenGuiApp) + defineSymbol(conf.symbols, "library") + defineSymbol(conf.symbols, "dll") of "staticlib": - incl(gGlobalOptions, optGenStaticLib) - excl(gGlobalOptions, optGenGuiApp) - defineSymbol("library") - defineSymbol("staticlib") - else: localError(info, errGuiConsoleOrLibExpectedButXFound, arg) + incl(conf.globalOptions, optGenStaticLib) + excl(conf.globalOptions, optGenGuiApp) + defineSymbol(conf.symbols, "library") + defineSymbol(conf.symbols, "staticlib") + else: localError(conf, info, errGuiConsoleOrLibExpectedButXFound % arg) of "passc", "t": - expectArg(switch, arg, pass, info) - if pass in {passCmd2, passPP}: extccomp.addCompileOptionCmd(arg) + expectArg(conf, switch, arg, pass, info) + if pass in {passCmd2, passPP}: extccomp.addCompileOptionCmd(conf, arg) of "passl", "l": - expectArg(switch, arg, pass, info) - if pass in {passCmd2, passPP}: extccomp.addLinkOptionCmd(arg) + expectArg(conf, switch, arg, pass, info) + if pass in {passCmd2, passPP}: extccomp.addLinkOptionCmd(conf, arg) of "cincludes": - expectArg(switch, arg, pass, info) - if pass in {passCmd2, passPP}: cIncludes.add arg.processPath(info) + expectArg(conf, switch, arg, pass, info) + if pass in {passCmd2, passPP}: conf.cIncludes.add processPath(conf, arg, info) of "clibdir": - expectArg(switch, arg, pass, info) - if pass in {passCmd2, passPP}: cLibs.add arg.processPath(info) + expectArg(conf, switch, arg, pass, info) + if pass in {passCmd2, passPP}: conf.cLibs.add processPath(conf, arg, info) of "clib": - expectArg(switch, arg, pass, info) - if pass in {passCmd2, passPP}: cLinkedLibs.add arg.processPath(info) + expectArg(conf, switch, arg, pass, info) + if pass in {passCmd2, passPP}: conf.cLinkedLibs.add processPath(conf, arg, info) of "header": - if config != nil: config.headerFile = arg - incl(gGlobalOptions, optGenIndex) + if conf != nil: conf.headerFile = arg + incl(conf.globalOptions, optGenIndex) of "index": - processOnOffSwitchG({optGenIndex}, arg, pass, info) + processOnOffSwitchG(conf, {optGenIndex}, arg, pass, info) of "import": - expectArg(switch, arg, pass, info) - if pass in {passCmd2, passPP}: implicitImports.add arg + expectArg(conf, switch, arg, pass, info) + if pass in {passCmd2, passPP}: conf.implicitImports.add arg of "include": - expectArg(switch, arg, pass, info) - if pass in {passCmd2, passPP}: implicitIncludes.add arg + expectArg(conf, switch, arg, pass, info) + if pass in {passCmd2, passPP}: conf.implicitIncludes.add arg of "listcmd": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optListCmd) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optListCmd) of "genmapping": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optGenMapping) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optGenMapping) of "os": - expectArg(switch, arg, pass, info) + expectArg(conf, switch, arg, pass, info) if pass in {passCmd1, passPP}: - theOS = platform.nameToOS(arg) - if theOS == osNone: localError(info, errUnknownOS, arg) - elif theOS != platform.hostOS: - setTarget(theOS, targetCPU) + let theOS = platform.nameToOS(arg) + if theOS == osNone: localError(conf, info, "unknown OS: '$1'" % arg) + elif theOS != conf.target.hostOS: + setTarget(conf.target, theOS, conf.target.targetCPU) of "cpu": - expectArg(switch, arg, pass, info) + expectArg(conf, switch, arg, pass, info) if pass in {passCmd1, passPP}: - cpu = platform.nameToCPU(arg) - if cpu == cpuNone: localError(info, errUnknownCPU, arg) - elif cpu != platform.hostCPU: - setTarget(targetOS, cpu) + let cpu = platform.nameToCPU(arg) + if cpu == cpuNone: localError(conf, info, "unknown CPU: '$1'" % arg) + elif cpu != conf.target.hostCPU: + setTarget(conf.target, conf.target.targetOS, cpu) of "run", "r": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optRun) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optRun) of "verbosity": - expectArg(switch, arg, pass, info) - gVerbosity = parseInt(arg) - gNotes = NotesVerbosity[gVerbosity] - incl(gNotes, enableNotes) - excl(gNotes, disableNotes) - gMainPackageNotes = gNotes + expectArg(conf, switch, arg, pass, info) + let verbosity = parseInt(arg) + if verbosity notin {0..3}: + localError(conf, info, "invalid verbosity level: '$1'" % arg) + conf.verbosity = verbosity + conf.notes = NotesVerbosity[conf.verbosity] + incl(conf.notes, conf.enableNotes) + excl(conf.notes, conf.disableNotes) + conf.mainPackageNotes = conf.notes of "parallelbuild": - expectArg(switch, arg, pass, info) - gNumberOfProcessors = parseInt(arg) + expectArg(conf, switch, arg, pass, info) + conf.numberOfProcessors = parseInt(arg) of "version", "v": - expectNoArg(switch, arg, pass, info) - writeVersionInfo(pass) + expectNoArg(conf, switch, arg, pass, info) + writeVersionInfo(conf, pass) of "advanced": - expectNoArg(switch, arg, pass, info) - writeAdvancedUsage(pass) + expectNoArg(conf, switch, arg, pass, info) + writeAdvancedUsage(conf, pass) + of "fullhelp": + expectNoArg(conf, switch, arg, pass, info) + writeFullhelp(conf, pass) of "help", "h": - expectNoArg(switch, arg, pass, info) - helpOnError(pass) - of "symbolfiles": + expectNoArg(conf, switch, arg, pass, info) + helpOnError(conf, pass) + of "symbolfiles", "incremental": case arg.normalize - of "on": gSymbolFiles = enabledSf - of "off": gSymbolFiles = disabledSf - of "writeonly": gSymbolFiles = writeOnlySf - of "readonly": gSymbolFiles = readOnlySf - of "v2": gSymbolFiles = v2Sf - else: localError(info, errOnOrOffExpectedButXFound, arg) + of "on": conf.symbolFiles = v2Sf + of "off": conf.symbolFiles = disabledSf + of "writeonly": conf.symbolFiles = writeOnlySf + of "readonly": conf.symbolFiles = readOnlySf + of "v2": conf.symbolFiles = v2Sf + else: localError(conf, info, "invalid option for --symbolFiles: " & arg) of "skipcfg": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optSkipConfigFile) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optSkipSystemConfigFile) of "skipprojcfg": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optSkipProjConfigFile) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optSkipProjConfigFile) of "skipusercfg": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optSkipUserConfigFile) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optSkipUserConfigFile) of "skipparentcfg": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optSkipParentConfigFiles) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optSkipParentConfigFiles) of "genscript", "gendeps": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optGenScript) - of "colors": processOnOffSwitchG({optUseColors}, arg, pass, info) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optGenScript) + incl(conf.globalOptions, optCompileOnly) + of "colors": processOnOffSwitchG(conf, {optUseColors}, arg, pass, info) of "lib": - expectArg(switch, arg, pass, info) - libpath = processPath(arg, info, notRelativeToProj=true) + expectArg(conf, switch, arg, pass, info) + conf.libpath = processPath(conf, arg, info, notRelativeToProj=true) of "putenv": - expectArg(switch, arg, pass, info) - splitSwitch(arg, key, val, pass, info) + expectArg(conf, switch, arg, pass, info) + splitSwitch(conf, arg, key, val, pass, info) os.putEnv(key, val) of "cc": - expectArg(switch, arg, pass, info) - setCC(arg) + expectArg(conf, switch, arg, pass, info) + setCC(conf, arg, info) of "track": - expectArg(switch, arg, pass, info) - track(arg, info) + expectArg(conf, switch, arg, pass, info) + track(conf, arg, info) of "trackdirty": - expectArg(switch, arg, pass, info) - trackDirty(arg, info) + expectArg(conf, switch, arg, pass, info) + trackDirty(conf, arg, info) of "suggest": - expectNoArg(switch, arg, pass, info) - gIdeCmd = ideSug + expectNoArg(conf, switch, arg, pass, info) + conf.ideCmd = ideSug of "def": - expectNoArg(switch, arg, pass, info) - gIdeCmd = ideDef + expectNoArg(conf, switch, arg, pass, info) + conf.ideCmd = ideDef of "eval": - expectArg(switch, arg, pass, info) - gEvalExpr = arg + expectArg(conf, switch, arg, pass, info) + conf.evalExpr = arg of "context": - expectNoArg(switch, arg, pass, info) - gIdeCmd = ideCon + expectNoArg(conf, switch, arg, pass, info) + conf.ideCmd = ideCon of "usages": - expectNoArg(switch, arg, pass, info) - gIdeCmd = ideUse + expectNoArg(conf, switch, arg, pass, info) + conf.ideCmd = ideUse of "stdout": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optStdout) + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optStdout) of "listfullpaths": - expectNoArg(switch, arg, pass, info) - gListFullPaths = true + expectNoArg(conf, switch, arg, pass, info) + incl conf.globalOptions, optListFullPaths of "dynliboverride": - dynlibOverride(switch, arg, pass, info) + dynlibOverride(conf, switch, arg, pass, info) of "dynliboverrideall": - expectNoArg(switch, arg, pass, info) - gDynlibOverrideAll = true + expectNoArg(conf, switch, arg, pass, info) + incl conf.globalOptions, optDynlibOverrideAll of "cs": # only supported for compatibility. Does nothing. - expectArg(switch, arg, pass, info) + expectArg(conf, switch, arg, pass, info) of "experimental": - expectNoArg(switch, arg, pass, info) - gExperimentalMode = true + if arg.len == 0: + conf.features.incl oldExperimentalFeatures + else: + try: + conf.features.incl parseEnum[Feature](arg) + except ValueError: + localError(conf, info, "unknown experimental feature") of "nocppexceptions": - expectNoArg(switch, arg, pass, info) - incl(gGlobalOptions, optNoCppExceptions) - defineSymbol("noCppExceptions") + expectNoArg(conf, switch, arg, pass, info) + incl(conf.globalOptions, optNoCppExceptions) + defineSymbol(conf.symbols, "noCppExceptions") of "cppdefine": - expectArg(switch, arg, pass, info) - if config != nil: - config.cppDefine(arg) + expectArg(conf, switch, arg, pass, info) + if conf != nil: + conf.cppDefine(arg) of "newruntime": - expectNoArg(switch, arg, pass, info) - newDestructors = true - defineSymbol("nimNewRuntime") + expectNoArg(conf, switch, arg, pass, info) + doAssert(conf != nil) + incl(conf.features, destructor) + defineSymbol(conf.symbols, "nimNewRuntime") + of "nep1": + processOnOffSwitchG(conf, {optCheckNep1}, arg, pass, info) + of "cppcompiletonamespace": + expectNoArg(conf, switch, arg, pass, info) + incl conf.globalOptions, optUseNimNamespace + defineSymbol(conf.symbols, "cppCompileToNamespace") else: - if strutils.find(switch, '.') >= 0: options.setConfigVar(switch, arg) - else: invalidCmdLineOption(pass, switch, info) + if strutils.find(switch, '.') >= 0: options.setConfigVar(conf, switch, arg) + else: invalidCmdLineOption(conf, pass, switch, info) -proc processCommand(switch: string, pass: TCmdLinePass) = - var cmd, arg: string - splitSwitch(switch, cmd, arg, pass, gCmdLineInfo) - processSwitch(cmd, arg, pass, gCmdLineInfo) +template gCmdLineInfo*(): untyped = newLineInfo(config, "command line", 1, 1) +proc processCommand*(switch: string, pass: TCmdLinePass; config: ConfigRef) = + var cmd, arg: string + splitSwitch(config, switch, cmd, arg, pass, gCmdLineInfo) + processSwitch(cmd, arg, pass, gCmdLineInfo, config) -var - arguments* = "" - # the arguments to be passed to the program that - # should be run -proc processSwitch*(pass: TCmdLinePass; p: OptParser) = +proc processSwitch*(pass: TCmdLinePass; p: OptParser; config: ConfigRef) = # hint[X]:off is parsed as (p.key = "hint[X]", p.val = "off") - # we fix this here + # we transform it to (key = hint, val = [X]:off) var bracketLe = strutils.find(p.key, '[') if bracketLe >= 0: var key = substr(p.key, 0, bracketLe - 1) - var val = substr(p.key, bracketLe + 1) & ':' & p.val - processSwitch(key, val, pass, gCmdLineInfo) + var val = substr(p.key, bracketLe) & ':' & p.val + processSwitch(key, val, pass, gCmdLineInfo, config) else: - processSwitch(p.key, p.val, pass, gCmdLineInfo) + processSwitch(p.key, p.val, pass, gCmdLineInfo, config) proc processArgument*(pass: TCmdLinePass; p: OptParser; - argsCount: var int): bool = + argsCount: var int; config: ConfigRef): bool = if argsCount == 0: # nim filename.nims is the same as "nim e filename.nims": if p.key.endswith(".nims"): - options.command = "e" - options.gProjectName = unixToNativePath(p.key) - arguments = cmdLineRest(p) + config.command = "e" + config.projectName = unixToNativePath(p.key) + config.arguments = cmdLineRest(p) result = true elif pass != passCmd2: - options.command = p.key + config.command = p.key else: - if pass == passCmd1: options.commandArgs.add p.key + if pass == passCmd1: config.commandArgs.add p.key if argsCount == 1: # support UNIX style filenames everywhere for portable build scripts: - options.gProjectName = unixToNativePath(p.key) - arguments = cmdLineRest(p) + config.projectName = unixToNativePath(p.key) + config.arguments = cmdLineRest(p) result = true inc argsCount diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 0be2899be..a22b613f0 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -12,78 +12,33 @@ import strtabs, platform, strutils, idents -# We need to use a StringTableRef here as defined symbols are always guaranteed -# to be style insensitive. Otherwise hell would break lose. -var gSymbols: StringTableRef +from options import Feature +from lineinfos import HintsToStr, WarningsToStr const catNone = "false" -proc defineSymbol*(symbol: string, value: string = "true") = - gSymbols[symbol] = value +proc defineSymbol*(symbols: StringTableRef; symbol: string, value: string = "true") = + symbols[symbol] = value -proc undefSymbol*(symbol: string) = - gSymbols[symbol] = catNone +proc undefSymbol*(symbols: StringTableRef; symbol: string) = + symbols[symbol] = catNone -proc isDefined*(symbol: string): bool = - if gSymbols.hasKey(symbol): - result = gSymbols[symbol] != catNone - elif cmpIgnoreStyle(symbol, CPU[targetCPU].name) == 0: - result = true - elif cmpIgnoreStyle(symbol, platform.OS[targetOS].name) == 0: - result = true - else: - case symbol.normalize - of "x86": result = targetCPU == cpuI386 - of "itanium": result = targetCPU == cpuIa64 - of "x8664": result = targetCPU == cpuAmd64 - of "posix", "unix": - result = targetOS in {osLinux, osMorphos, osSkyos, osIrix, osPalmos, - osQnx, osAtari, osAix, - osHaiku, osVxWorks, osSolaris, osNetbsd, - osFreebsd, osOpenbsd, osDragonfly, osMacosx, - osAndroid} - of "linux": - result = targetOS in {osLinux, osAndroid} - of "bsd": - result = targetOS in {osNetbsd, osFreebsd, osOpenbsd, osDragonfly} - of "emulatedthreadvars": - result = platform.OS[targetOS].props.contains(ospLacksThreadVars) - of "msdos": result = targetOS == osDos - of "mswindows", "win32": result = targetOS == osWindows - of "macintosh": result = targetOS in {osMacos, osMacosx} - of "sunos": result = targetOS == osSolaris - of "littleendian": result = CPU[targetCPU].endian == platform.littleEndian - of "bigendian": result = CPU[targetCPU].endian == platform.bigEndian - of "cpu8": result = CPU[targetCPU].bit == 8 - of "cpu16": result = CPU[targetCPU].bit == 16 - of "cpu32": result = CPU[targetCPU].bit == 32 - of "cpu64": result = CPU[targetCPU].bit == 64 - of "nimrawsetjmp": - result = targetOS in {osSolaris, osNetbsd, osFreebsd, osOpenbsd, - osDragonfly, osMacosx} - else: discard +#proc lookupSymbol*(symbols: StringTableRef; symbol: string): string = +# result = if isDefined(symbol): gSymbols[symbol] else: nil -proc isDefined*(symbol: PIdent): bool = isDefined(symbol.s) - -proc lookupSymbol*(symbol: string): string = - result = if isDefined(symbol): gSymbols[symbol] else: nil - -proc lookupSymbol*(symbol: PIdent): string = lookupSymbol(symbol.s) - -iterator definedSymbolNames*: string = - for key, val in pairs(gSymbols): +iterator definedSymbolNames*(symbols: StringTableRef): string = + for key, val in pairs(symbols): if val != catNone: yield key -proc countDefinedSymbols*(): int = +proc countDefinedSymbols*(symbols: StringTableRef): int = result = 0 - for key, val in pairs(gSymbols): + for key, val in pairs(symbols): if val != catNone: inc(result) -proc initDefines*() = - gSymbols = newStringTable(modeStyleInsensitive) - defineSymbol("nimrod") # 'nimrod' is always defined +proc initDefines*(symbols: StringTableRef) = # for bootstrapping purposes and old code: + template defineSymbol(s) = symbols.defineSymbol(s) defineSymbol("nimhygiene") defineSymbol("niminheritable") defineSymbol("nimmixin") @@ -92,6 +47,7 @@ proc initDefines*() = defineSymbol("nimcomputedgoto") defineSymbol("nimunion") defineSymbol("nimnewshared") + defineSymbol("nimNewTypedesc") defineSymbol("nimrequiresnimframe") defineSymbol("nimparsebiggestfloatmagic") defineSymbol("nimalias") @@ -113,3 +69,24 @@ proc initDefines*() = defineSymbol("nimHasRunnableExamples") defineSymbol("nimNewDot") defineSymbol("nimHasNilChecks") + defineSymbol("nimSymKind") + defineSymbol("nimVmEqIdent") + defineSymbol("nimNoNil") + defineSymbol("nimNoZeroTerminator") + defineSymbol("nimNotNil") + defineSymbol("nimVmExportFixed") + defineSymbol("nimHasSymOwnerInMacro") + defineSymbol("nimNewRuntime") + defineSymbol("nimIncrSeqV3") + defineSymbol("nimAshr") + defineSymbol("nimNoNilSeqs") + defineSymbol("nimNoNilSeqs2") + + defineSymbol("nimHasNilSeqs") + for f in low(Feature)..high(Feature): + defineSymbol("nimHas" & $f) + + for s in WarningsToStr: + defineSymbol("nimHasWarning" & s) + for s in HintsToStr: + defineSymbol("nimHasHint" & s) diff --git a/compiler/configuration.nim b/compiler/configuration.nim new file mode 100644 index 000000000..22e0b834e --- /dev/null +++ b/compiler/configuration.nim @@ -0,0 +1,6 @@ +## Use the module 'lineinfos' instead! + +{.deprecated.} + +import lineinfos +export lineinfos diff --git a/compiler/depends.nim b/compiler/depends.nim index 2b600c1da..d0a1139ef 100644 --- a/compiler/depends.nim +++ b/compiler/depends.nim @@ -14,44 +14,51 @@ import from modulegraphs import ModuleGraph -proc generateDot*(project: string) - type TGen = object of TPassContext - module*: PSym + module: PSym + config: ConfigRef + graph: ModuleGraph PGen = ref TGen -var gDotGraph: Rope # the generated DOT file; we need a global variable + Backend = ref object of RootRef + dotGraph: Rope -proc addDependencyAux(importing, imported: string) = - addf(gDotGraph, "$1 -> \"$2\";$n", [rope(importing), rope(imported)]) +proc addDependencyAux(b: Backend; importing, imported: string) = + addf(b.dotGraph, "$1 -> \"$2\";$n", [rope(importing), rope(imported)]) # s1 -> s2_4[label="[0-9]"]; proc addDotDependency(c: PPassContext, n: PNode): PNode = result = n - var g = PGen(c) + let g = PGen(c) + let b = Backend(g.graph.backend) case n.kind of nkImportStmt: for i in countup(0, sonsLen(n) - 1): - var imported = getModuleName(n.sons[i]) - addDependencyAux(g.module.name.s, imported) + var imported = getModuleName(g.config, n.sons[i]) + addDependencyAux(b, g.module.name.s, imported) of nkFromStmt, nkImportExceptStmt: - var imported = getModuleName(n.sons[0]) - addDependencyAux(g.module.name.s, imported) + var imported = getModuleName(g.config, n.sons[0]) + addDependencyAux(b, g.module.name.s, imported) of nkStmtList, nkBlockStmt, nkStmtListExpr, nkBlockExpr: for i in countup(0, sonsLen(n) - 1): discard addDotDependency(c, n.sons[i]) else: discard -proc generateDot(project: string) = - writeRope("digraph $1 {$n$2}$n" % [ - rope(changeFileExt(extractFilename(project), "")), gDotGraph], +proc generateDot*(graph: ModuleGraph; project: string) = + let b = Backend(graph.backend) + discard writeRope("digraph $1 {$n$2}$n" % [ + rope(changeFileExt(extractFilename(project), "")), b.dotGraph], changeFileExt(project, "dot")) -proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = +proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = var g: PGen new(g) g.module = module + g.config = graph.config + g.graph = graph + if graph.backend == nil: + graph.backend = Backend(dotGraph: nil) result = g const gendependPass* = makePass(open = myOpen, process = addDotDependency) diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim index 55da69985..bd735560a 100644 --- a/compiler/destroyer.nim +++ b/compiler/destroyer.nim @@ -89,11 +89,35 @@ ## tmp.bar)) ## destroy(tmp.bar) ## destroy(tmp.x); destroy(tmp.y) +## + +##[ +From https://github.com/nim-lang/Nim/wiki/Destructors + +Rule Pattern Transformed into +---- ------- ---------------- +1.1 var x: T; stmts var x: T; try stmts + finally: `=destroy`(x) +1.2 var x: sink T; stmts var x: sink T; stmts; ensureEmpty(x) +2 x = f() `=sink`(x, f()) +3 x = lastReadOf z `=sink`(x, z); wasMoved(z) +4.1 y = sinkParam `=sink`(y, sinkParam) +4.2 x = y `=`(x, y) # a copy +5.1 f_sink(g()) f_sink(g()) +5.2 f_sink(y) f_sink(copy y); # copy unless we can see it's the last read +5.3 f_sink(move y) f_sink(y); wasMoved(y) # explicit moves empties 'y' +5.4 f_noSink(g()) var tmp = bitwiseCopy(g()); f(tmp); `=destroy`(tmp) +Remarks: Rule 1.2 is not yet implemented because ``sink`` is currently + not allowed as a local variable. + +``move`` builtin needs to be implemented. +]## import intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees, - strutils, options, dfa, lowerings, rodread + strutils, options, dfa, lowerings, tables, modulegraphs, + lineinfos const InterestingSyms = {skVar, skResult, skLet} @@ -106,6 +130,16 @@ type tmpObj: PType tmp: PSym destroys, topLevelVars: PNode + toDropBit: Table[int, PSym] + graph: ModuleGraph + emptyNode: PNode + +proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode = + # XXX why are temps fields in an object here? + let f = newSym(skField, getIdent(c.graph.cache, ":d" & $c.tmpObj.n.len), c.owner, info) + f.typ = typ + rawAddField c.tmpObj, f + result = rawDirectAccess(c.tmp, f) proc isHarmlessVar*(s: PSym; c: Con): bool = # 's' is harmless if it used only once and its @@ -191,26 +225,43 @@ proc patchHead(s: PSym) = template genOp(opr, opname) = let op = opr if op == nil: - globalError(dest.info, "internal error: '" & opname & "' operator not found for type " & typeToString(t)) + globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator not found for type " & typeToString(t)) elif op.ast[genericParamsPos].kind != nkEmpty: - globalError(dest.info, "internal error: '" & opname & "' operator is generic") + globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator is generic") patchHead op result = newTree(nkCall, newSymNode(op), newTree(nkHiddenAddr, dest)) -proc genSink(t: PType; dest: PNode): PNode = +proc genSink(c: Con; t: PType; dest: PNode): PNode = let t = t.skipTypes({tyGenericInst, tyAlias, tySink}) genOp(if t.sink != nil: t.sink else: t.assignment, "=sink") -proc genCopy(t: PType; dest: PNode): PNode = +proc genCopy(c: Con; t: PType; dest: PNode): PNode = let t = t.skipTypes({tyGenericInst, tyAlias, tySink}) genOp(t.assignment, "=") -proc genDestroy(t: PType; dest: PNode): PNode = +proc genDestroy(c: Con; t: PType; dest: PNode): PNode = let t = t.skipTypes({tyGenericInst, tyAlias, tySink}) genOp(t.destructor, "=destroy") proc addTopVar(c: var Con; v: PNode) = - c.topLevelVars.add newTree(nkIdentDefs, v, emptyNode, emptyNode) + c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode) + +proc dropBit(c: var Con; s: PSym): PSym = + result = c.toDropBit.getOrDefault(s.id) + assert result != nil + +proc registerDropBit(c: var Con; s: PSym) = + let result = newSym(skTemp, getIdent(c.graph.cache, s.name.s & "_AliveBit"), c.owner, s.info) + result.typ = getSysType(c.graph, s.info, tyBool) + let trueVal = newIntTypeNode(nkIntLit, 1, result.typ) + c.topLevelVars.add newTree(nkIdentDefs, newSymNode result, c.emptyNode, trueVal) + c.toDropBit[s.id] = result + # generate: + # if not sinkParam_AliveBit: `=destroy`(sinkParam) + let t = s.typ.skipTypes({tyGenericInst, tyAlias, tySink}) + if t.destructor != nil: + c.destroys.add newTree(nkIfStmt, + newTree(nkElifBranch, newSymNode result, genDestroy(c, t, newSymNode s))) proc p(n: PNode; c: var Con): PNode @@ -218,20 +269,81 @@ template recurse(n, dest) = for i in 0..<n.len: dest.add p(n[i], c) +proc isSinkParam(s: PSym): bool {.inline.} = + result = s.kind == skParam and s.typ.kind == tySink + +const constrExprs = nkCallKinds+{nkObjConstr} + +proc destructiveMoveSink(n: PNode; c: var Con): PNode = + # generate: (chckMove(sinkParam_AliveBit); sinkParam_AliveBit = false; sinkParam) + result = newNodeIT(nkStmtListExpr, n.info, n.typ) + let bit = newSymNode dropBit(c, n.sym) + if optMoveCheck in c.owner.options: + result.add callCodegenProc(c.graph, "chckMove", bit) + result.add newTree(nkAsgn, bit, + newIntTypeNode(nkIntLit, 0, getSysType(c.graph, n.info, tyBool))) + result.add n + +proc genMagicCall(n: PNode; c: var Con; magicname: string; m: TMagic): PNode = + result = newNodeI(nkCall, n.info) + result.add(newSymNode(createMagic(c.graph, magicname, m))) + result.add n + proc moveOrCopy(dest, ri: PNode; c: var Con): PNode = - if ri.kind in nkCallKinds+{nkObjConstr}: - result = genSink(ri.typ, dest) + if ri.kind in constrExprs: + result = genSink(c, ri.typ, dest) # watch out and no not transform 'ri' twice if it's a call: let ri2 = copyNode(ri) recurse(ri, ri2) result.add ri2 elif ri.kind == nkSym and isHarmlessVar(ri.sym, c): - result = genSink(ri.typ, dest) - result.add p(ri, c) + # Rule 3: `=sink`(x, z); wasMoved(z) + var snk = genSink(c, ri.typ, dest) + snk.add p(ri, c) + result = newTree(nkStmtList, snk, genMagicCall(ri, c, "wasMoved", mWasMoved)) + elif ri.kind == nkSym and isSinkParam(ri.sym): + result = genSink(c, ri.typ, dest) + result.add destructiveMoveSink(ri, c) else: - result = genCopy(ri.typ, dest) + result = genCopy(c, ri.typ, dest) result.add p(ri, c) +proc passCopyToSink(n: PNode; c: var Con): PNode = + result = newNodeIT(nkStmtListExpr, n.info, n.typ) + let tmp = getTemp(c, n.typ, n.info) + if hasDestructor(n.typ): + var m = genCopy(c, n.typ, tmp) + m.add p(n, c) + result.add m + message(c.graph.config, n.info, hintPerformance, + "passing '$1' to a sink parameter introduces an implicit copy; " & + "use 'move($1)' to prevent it" % $n) + else: + result.add newTree(nkAsgn, tmp, p(n, c)) + result.add tmp + +proc genWasMoved(n: PNode; c: var Con): PNode = + # The mWasMoved builtin does not take the address. + result = genMagicCall(n, c, "wasMoved", mWasMoved) + +proc destructiveMoveVar(n: PNode; c: var Con): PNode = + # generate: (let tmp = v; reset(v); tmp) + result = newNodeIT(nkStmtListExpr, n.info, n.typ) + + var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.owner, n.info) + var v = newNodeI(nkLetSection, n.info) + let tempAsNode = newSymNode(temp) + + var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3) + vpart.sons[0] = tempAsNode + vpart.sons[1] = c.emptyNode + vpart.sons[2] = n + add(v, vpart) + + result.add v + result.add genWasMoved(n, c) + result.add tempAsNode + proc p(n: PNode; c: var Con): PNode = case n.kind of nkVarSection, nkLetSection: @@ -243,7 +355,7 @@ proc p(n: PNode; c: var Con): PNode = let L = it.len-1 let ri = it[L] if it.kind == nkVarTuple and hasDestructor(ri.typ): - let x = lowerTupleUnpacking(it, c.owner) + let x = lowerTupleUnpacking(c.graph, it, c.owner) result.add p(x, c) elif it.kind == nkIdentDefs and hasDestructor(it[0].typ): for j in 0..L-2: @@ -252,7 +364,7 @@ proc p(n: PNode; c: var Con): PNode = # move the variable declaration to the top of the frame: c.addTopVar v # make sure it's destroyed at the end of the proc: - c.destroys.add genDestroy(v.typ, v) + c.destroys.add genDestroy(c, v.typ, v) if ri.kind != nkEmpty: let r = moveOrCopy(v, ri, c) result.add r @@ -266,22 +378,45 @@ proc p(n: PNode; c: var Con): PNode = varSection.add itCopy result.add varSection of nkCallKinds: + let parameters = n[0].typ + let L = if parameters != nil: parameters.len else: 0 + for i in 1 ..< n.len: + let arg = n[i] + if i < L and parameters[i].kind == tySink: + if arg.kind in nkCallKinds: + # recurse but skip the call expression in order to prevent + # destructor injections: Rule 5.1 is different from rule 5.4! + let a = copyNode(arg) + recurse(arg, a) + n.sons[i] = a + elif arg.kind in {nkObjConstr, nkCharLit..nkFloat128Lit}: + discard "object construction to sink parameter: nothing to do" + elif arg.kind == nkSym and isHarmlessVar(arg.sym, c): + # if x is a variable and it its last read we eliminate its + # destructor invokation, but don't. We need to reset its memory + # to disable its destructor which we have not elided: + n.sons[i] = destructiveMoveVar(arg, c) + elif arg.kind == nkSym and isSinkParam(arg.sym): + # mark the sink parameter as used: + n.sons[i] = destructiveMoveSink(arg, c) + else: + # an object that is not temporary but passed to a 'sink' parameter + # results in a copy. + n.sons[i] = passCopyToSink(arg, c) + else: + n.sons[i] = p(arg, c) + if n.typ != nil and hasDestructor(n.typ): discard "produce temp creation" result = newNodeIT(nkStmtListExpr, n.info, n.typ) - let f = newSym(skField, getIdent(":d" & $c.tmpObj.n.len), c.owner, n.info) - f.typ = n.typ - rawAddField c.tmpObj, f - var m = genSink(n.typ, rawDirectAccess(c.tmp, f)) - var call = copyNode(n) - recurse(n, call) - m.add call - result.add m - result.add rawDirectAccess(c.tmp, f) - c.destroys.add genDestroy(n.typ, rawDirectAccess(c.tmp, f)) + let tmp = getTemp(c, n.typ, n.info) + var sinkExpr = genSink(c, n.typ, tmp) + sinkExpr.add n + result.add sinkExpr + result.add tmp + c.destroys.add genDestroy(c, n.typ, tmp) else: - result = copyNode(n) - recurse(n, result) + result = n of nkAsgn, nkFastAsgn: if hasDestructor(n[0].typ): result = moveOrCopy(n[0], n[1], c) @@ -295,22 +430,30 @@ proc p(n: PNode; c: var Con): PNode = result = copyNode(n) recurse(n, result) -proc injectDestructorCalls*(owner: PSym; n: PNode): PNode = +proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode = when defined(nimDebugDestroys): echo "injecting into ", n var c: Con c.owner = owner - c.tmp = newSym(skTemp, getIdent":d", owner, n.info) - c.tmpObj = createObj(owner, n.info) + c.tmp = newSym(skTemp, getIdent(g.cache, ":d"), owner, n.info) + c.tmpObj = createObj(g, owner, n.info) c.tmp.typ = c.tmpObj c.destroys = newNodeI(nkStmtList, n.info) c.topLevelVars = newNodeI(nkVarSection, n.info) + c.toDropBit = initTable[int, PSym]() + c.graph = g + c.emptyNode = newNodeI(nkEmpty, n.info) let cfg = constructCfg(owner, n) shallowCopy(c.g, cfg) c.jumpTargets = initIntSet() for i in 0..<c.g.len: if c.g[i].kind in {goto, fork}: c.jumpTargets.incl(i+c.g[i].dest) + if owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter}: + let params = owner.typ.n + for i in 1 ..< params.len: + let param = params[i].sym + if param.typ.kind == tySink: registerDropBit(c, param) let body = p(n, c) if c.tmp.typ.n.len > 0: c.addTopVar(newSymNode c.tmp) diff --git a/compiler/dfa.nim b/compiler/dfa.nim index b648995f4..013242f62 100644 --- a/compiler/dfa.nim +++ b/compiler/dfa.nim @@ -23,7 +23,7 @@ ## "A Graph–Free Approach to Data–Flow Analysis" by Markus Mohnen. ## https://link.springer.com/content/pdf/10.1007/3-540-45937-5_6.pdf -import ast, astalgo, types, intsets, tables, msgs +import ast, astalgo, types, intsets, tables, msgs, options, lineinfos type InstrKind* = enum @@ -54,7 +54,7 @@ type blocks: seq[TBlock] proc debugInfo(info: TLineInfo): string = - result = info.toFilename & ":" & $info.line + result = $info.line #info.toFilename & ":" & $info.line proc codeListing(c: ControlFlowGraph, result: var string, start=0; last = -1) = # for debugging purposes @@ -87,7 +87,8 @@ proc echoCfg*(c: ControlFlowGraph; start=0; last = -1) {.deprecated.} = ## echos the ControlFlowGraph for debugging purposes. var buf = "" codeListing(c, buf, start, last) - echo buf + when declared(echo): + echo buf proc forkI(c: var Con; n: PNode): TPosition = result = TPosition(c.code.len) @@ -102,14 +103,14 @@ proc genLabel(c: Con): TPosition = proc jmpBack(c: var Con, n: PNode, p = TPosition(0)) = let dist = p.int - c.code.len - internalAssert(-0x7fff < dist and dist < 0x7fff) + doAssert(-0x7fff < dist and dist < 0x7fff) c.code.add Instr(n: n, kind: goto, dest: dist) proc patch(c: var Con, p: TPosition) = # patch with current index let p = p.int let diff = c.code.len - p - internalAssert(-0x7fff < diff and diff < 0x7fff) + doAssert(-0x7fff < diff and diff < 0x7fff) c.code[p].dest = diff proc popBlock(c: var Con; oldLen: int) = @@ -160,7 +161,7 @@ proc genBreak(c: var Con; n: PNode) = if c.blocks[i].label == n.sons[0].sym: c.blocks[i].fixups.add L1 return - globalError(n.info, errGenerated, "VM problem: cannot find 'break' target") + #globalError(n.info, "VM problem: cannot find 'break' target") else: c.blocks[c.blocks.high].fixups.add L1 @@ -323,7 +324,7 @@ proc gen(c: var Con; n: PNode) = of nkBreakStmt: genBreak(c, n) of nkTryStmt: genTry(c, n) of nkStmtList, nkStmtListExpr, nkChckRangeF, nkChckRange64, nkChckRange, - nkBracket, nkCurly, nkPar, nkClosure, nkObjConstr: + nkBracket, nkCurly, nkPar, nkTupleConstr, nkClosure, nkObjConstr: for x in n: gen(c, x) of nkPragmaBlock: gen(c, n.lastSon) of nkDiscardStmt: gen(c, n.sons[0]) @@ -334,7 +335,7 @@ proc gen(c: var Con; n: PNode) = of nkVarSection, nkLetSection: genVarSection(c, n) else: discard -proc dfa(code: seq[Instr]) = +proc dfa(code: seq[Instr]; conf: ConfigRef) = var u = newSeq[IntSet](code.len) # usages var d = newSeq[IntSet](code.len) # defs var c = newSeq[IntSet](code.len) # consumed @@ -426,17 +427,17 @@ proc dfa(code: seq[Instr]) = of use, useWithinCall: let s = code[i].sym if s.id notin d[i]: - localError(code[i].n.info, "usage of uninitialized variable: " & s.name.s) + localError(conf, code[i].n.info, "usage of uninitialized variable: " & s.name.s) if s.id in c[i]: - localError(code[i].n.info, "usage of an already consumed variable: " & s.name.s) + localError(conf, code[i].n.info, "usage of an already consumed variable: " & s.name.s) else: discard -proc dataflowAnalysis*(s: PSym; body: PNode) = +proc dataflowAnalysis*(s: PSym; body: PNode; conf: ConfigRef) = var c = Con(code: @[], blocks: @[]) gen(c, body) when defined(useDfa) and defined(debugDfa): echoCfg(c.code) - dfa(c.code) + dfa(c.code, conf) proc constructCfg*(s: PSym; body: PNode): ControlFlowGraph = ## constructs a control flow graph for ``body``. diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 65dcb73c9..6f26bcf10 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -14,9 +14,9 @@ import ast, strutils, strtabs, options, msgs, os, ropes, idents, wordrecg, syntaxes, renderer, lexer, packages/docutils/rstast, - packages/docutils/rst, packages/docutils/rstgen, times, + packages/docutils/rst, packages/docutils/rstgen, packages/docutils/highlite, sempass2, json, xmltree, cgi, - typesrenderer, astalgo, modulepaths + typesrenderer, astalgo, modulepaths, lineinfos, sequtils, intsets type TSections = array[TSymKind, Rope] @@ -29,6 +29,11 @@ type jArray: JsonNode types: TStrTable isPureRst: bool + conf*: ConfigRef + cache*: IdentCache + exampleCounter: int + emitted: IntSet # we need to track which symbols have been emitted + # already. See bug #3655 PDoc* = ref TDocumentor ## Alias to type less. @@ -53,42 +58,48 @@ proc attachToType(d: PDoc; p: PSym): PSym = if params.len > 0: check(0) for i in 2..<params.len: check(i) -proc compilerMsgHandler(filename: string, line, col: int, - msgKind: rst.MsgKind, arg: string) {.procvar.} = - # translate msg kind: - var k: msgs.TMsgKind - case msgKind - of meCannotOpenFile: k = errCannotOpenFile - of meExpected: k = errXExpected - of meGridTableNotImplemented: k = errGridTableNotImplemented - of meNewSectionExpected: k = errNewSectionExpected - of meGeneralParseError: k = errGeneralParseError - of meInvalidDirective: k = errInvalidDirectiveX - of mwRedefinitionOfLabel: k = warnRedefinitionOfLabel - of mwUnknownSubstitution: k = warnUnknownSubstitutionX - of mwUnsupportedLanguage: k = warnLanguageXNotSupported - of mwUnsupportedField: k = warnFieldXNotSupported - globalError(newLineInfo(filename, line, col), k, arg) - -proc docgenFindFile(s: string): string {.procvar.} = - result = options.findFile(s) - if result.len == 0: - result = getCurrentDir() / s - if not existsFile(result): result = "" +template declareClosures = + proc compilerMsgHandler(filename: string, line, col: int, + msgKind: rst.MsgKind, arg: string) {.procvar.} = + # translate msg kind: + var k: TMsgKind + case msgKind + of meCannotOpenFile: k = errCannotOpenFile + of meExpected: k = errXExpected + of meGridTableNotImplemented: k = errGridTableNotImplemented + of meNewSectionExpected: k = errNewSectionExpected + of meGeneralParseError: k = errGeneralParseError + of meInvalidDirective: k = errInvalidDirectiveX + of mwRedefinitionOfLabel: k = warnRedefinitionOfLabel + of mwUnknownSubstitution: k = warnUnknownSubstitutionX + of mwUnsupportedLanguage: k = warnLanguageXNotSupported + of mwUnsupportedField: k = warnFieldXNotSupported + globalError(conf, newLineInfo(conf, filename, line, col), k, arg) + + proc docgenFindFile(s: string): string {.procvar.} = + result = options.findFile(conf, s) + if result.len == 0: + result = getCurrentDir() / s + if not existsFile(result): result = "" proc parseRst(text, filename: string, line, column: int, hasToc: var bool, - rstOptions: RstParseOptions): PRstNode = + rstOptions: RstParseOptions; + conf: ConfigRef): PRstNode = + declareClosures() result = rstParse(text, filename, line, column, hasToc, rstOptions, docgenFindFile, compilerMsgHandler) -proc newDocumentor*(filename: string, config: StringTableRef): PDoc = +proc newDocumentor*(filename: string; cache: IdentCache; conf: ConfigRef): PDoc = + declareClosures() new(result) - initRstGenerator(result[], (if gCmd != cmdRst2tex: outHtml else: outLatex), - options.gConfigVars, filename, {roSupportRawDirective}, + result.conf = conf + result.cache = cache + initRstGenerator(result[], (if conf.cmd != cmdRst2tex: outHtml else: outLatex), + conf.configVars, filename, {roSupportRawDirective}, docgenFindFile, compilerMsgHandler) - if config.hasKey("doc.googleAnalytics"): + if conf.configVars.hasKey("doc.googleAnalytics"): result.analytics = """ <script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ @@ -100,7 +111,7 @@ proc newDocumentor*(filename: string, config: StringTableRef): PDoc = ga('send', 'pageview'); </script> - """ % [config.getOrDefault"doc.googleAnalytics"] + """ % [conf.configVars.getOrDefault"doc.googleAnalytics"] else: result.analytics = "" @@ -109,10 +120,11 @@ proc newDocumentor*(filename: string, config: StringTableRef): PDoc = result.jArray = newJArray() initStrTable result.types result.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; status: int; content: string) = - localError(newLineInfo(d.filename, -1, -1), warnUser, "only 'rst2html' supports the ':test:' attribute") + localError(conf, newLineInfo(conf, d.filename, -1, -1), warnUser, "only 'rst2html' supports the ':test:' attribute") + result.emitted = initIntSet() -proc dispA(dest: var Rope, xml, tex: string, args: openArray[Rope]) = - if gCmd != cmdRst2tex: addf(dest, xml, args) +proc dispA(conf: ConfigRef; dest: var Rope, xml, tex: string, args: openArray[Rope]) = + if conf.cmd != cmdRst2tex: addf(dest, xml, args) else: addf(dest, tex, args) proc getVarIdx(varnames: openArray[string], id: string): int = @@ -121,7 +133,8 @@ proc getVarIdx(varnames: openArray[string], id: string): int = return i result = -1 -proc ropeFormatNamedVars(frmt: FormatStr, varnames: openArray[string], +proc ropeFormatNamedVars(conf: ConfigRef; frmt: FormatStr, + varnames: openArray[string], varvalues: openArray[Rope]): Rope = var i = 0 var L = len(frmt) @@ -144,7 +157,8 @@ proc ropeFormatNamedVars(frmt: FormatStr, varnames: openArray[string], j = (j * 10) + ord(frmt[i]) - ord('0') inc(i) if (i > L + 0 - 1) or not (frmt[i] in {'0'..'9'}): break - if j > high(varvalues) + 1: internalError("ropeFormatNamedVars") + if j > high(varvalues) + 1: + rawMessage(conf, errGenerated, "Invalid format string; too many $s: " & frmt) num = j add(result, varvalues[j - 1]) of 'A'..'Z', 'a'..'z', '\x80'..'\xFF': @@ -155,20 +169,23 @@ proc ropeFormatNamedVars(frmt: FormatStr, varnames: openArray[string], if not (frmt[i] in {'A'..'Z', '_', 'a'..'z', '\x80'..'\xFF'}): break var idx = getVarIdx(varnames, id) if idx >= 0: add(result, varvalues[idx]) - else: rawMessage(errUnknownSubstitionVar, id) + else: rawMessage(conf, errGenerated, "unknown substition variable: " & id) of '{': var id = "" inc(i) - while frmt[i] != '}': - if frmt[i] == '\0': rawMessage(errTokenExpected, "}") + while i < frmt.len and frmt[i] != '}': add(id, frmt[i]) inc(i) - inc(i) # skip } - # search for the variable: - var idx = getVarIdx(varnames, id) + if i >= frmt.len: + rawMessage(conf, errGenerated, "expected closing '}'") + else: + inc(i) # skip } + # search for the variable: + let idx = getVarIdx(varnames, id) if idx >= 0: add(result, varvalues[idx]) - else: rawMessage(errUnknownSubstitionVar, id) - else: internalError("ropeFormatNamedVars") + else: rawMessage(conf, errGenerated, "unknown substition variable: " & id) + else: + add(result, "$") var start = i while i < L: if frmt[i] != '$': inc(i) @@ -178,10 +195,10 @@ proc ropeFormatNamedVars(frmt: FormatStr, varnames: openArray[string], proc genComment(d: PDoc, n: PNode): string = result = "" var dummyHasToc: bool - if n.comment != nil: - renderRstToOut(d[], parseRst(n.comment, toFilename(n.info), + if n.comment.len > 0: + renderRstToOut(d[], parseRst(n.comment, toFilename(d.conf, n.info), toLinenumber(n.info), toColumn(n.info), - dummyHasToc, d.options), result) + dummyHasToc, d.options, d.conf), result) proc genRecComment(d: PDoc, n: PNode): Rope = if n == nil: return nil @@ -192,7 +209,8 @@ proc genRecComment(d: PDoc, n: PNode): Rope = result = genRecComment(d, n.sons[i]) if result != nil: return else: - n.comment = nil + when defined(nimNoNilSeqs): n.comment = "" + else: n.comment = nil proc getPlainDocstring(n: PNode): string = ## Gets the plain text docstring of a node non destructively. @@ -202,7 +220,7 @@ proc getPlainDocstring(n: PNode): string = ## the concatenated ``##`` comments of the node. result = "" if n == nil: return - if n.comment != nil and startsWith(n.comment, "##"): + if startsWith(n.comment, "##"): result = n.comment if result.len < 1: for i in countup(0, safeLen(n)-1): @@ -220,53 +238,107 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe of tkEof: break of tkComment: - dispA(result, "<span class=\"Comment\">$1</span>", "\\spanComment{$1}", + dispA(d.conf, result, "<span class=\"Comment\">$1</span>", "\\spanComment{$1}", [rope(esc(d.target, literal))]) of tokKeywordLow..tokKeywordHigh: - dispA(result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}", + dispA(d.conf, result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}", [rope(literal)]) of tkOpr: - dispA(result, "<span class=\"Operator\">$1</span>", "\\spanOperator{$1}", + dispA(d.conf, result, "<span class=\"Operator\">$1</span>", "\\spanOperator{$1}", [rope(esc(d.target, literal))]) of tkStrLit..tkTripleStrLit: - dispA(result, "<span class=\"StringLit\">$1</span>", + dispA(d.conf, result, "<span class=\"StringLit\">$1</span>", "\\spanStringLit{$1}", [rope(esc(d.target, literal))]) of tkCharLit: - dispA(result, "<span class=\"CharLit\">$1</span>", "\\spanCharLit{$1}", + dispA(d.conf, result, "<span class=\"CharLit\">$1</span>", "\\spanCharLit{$1}", [rope(esc(d.target, literal))]) of tkIntLit..tkUInt64Lit: - dispA(result, "<span class=\"DecNumber\">$1</span>", + dispA(d.conf, result, "<span class=\"DecNumber\">$1</span>", "\\spanDecNumber{$1}", [rope(esc(d.target, literal))]) of tkFloatLit..tkFloat128Lit: - dispA(result, "<span class=\"FloatNumber\">$1</span>", + dispA(d.conf, result, "<span class=\"FloatNumber\">$1</span>", "\\spanFloatNumber{$1}", [rope(esc(d.target, literal))]) of tkSymbol: - dispA(result, "<span class=\"Identifier\">$1</span>", + dispA(d.conf, result, "<span class=\"Identifier\">$1</span>", "\\spanIdentifier{$1}", [rope(esc(d.target, literal))]) of tkSpaces, tkInvalid: add(result, literal) of tkCurlyDotLe: - dispA(result, """<span class="Other pragmabegin">$1</span><div class="pragma">""", + dispA(d.conf, result, "<span>" & # This span is required for the JS to work properly + """<span class="Other">{</span><span class="Other pragmadots">...</span><span class="Other">}</span> +</span> +<span class="pragmawrap"> +<span class="Other">$1</span> +<span class="pragma">""".replace("\n", ""), # Must remove newlines because wrapped in a <pre> "\\spanOther{$1}", [rope(esc(d.target, literal))]) of tkCurlyDotRi: - dispA(result, "</div><span class=\"Other pragmaend\">$1</span>", + dispA(d.conf, result, """ +</span> +<span class="Other">$1</span> +</span>""".replace("\n", ""), "\\spanOther{$1}", [rope(esc(d.target, literal))]) of tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi, tkBracketDotLe, tkBracketDotRi, tkParDotLe, tkParDotRi, tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot, tkAccent, tkColonColon, - tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr: - dispA(result, "<span class=\"Other\">$1</span>", "\\spanOther{$1}", + tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr, + tkBracketLeColon: + dispA(d.conf, result, "<span class=\"Other\">$1</span>", "\\spanOther{$1}", [rope(esc(d.target, literal))]) +proc testExample(d: PDoc; ex: PNode) = + if d.conf.errorCounter > 0: return + let outputDir = d.conf.getNimcacheDir / "runnableExamples" + createDir(outputDir) + inc d.exampleCounter + let outp = outputDir / extractFilename(d.filename.changeFileExt"" & + "_examples" & $d.exampleCounter & ".nim") + #let nimcache = outp.changeFileExt"" & "_nimcache" + renderModule(ex, d.filename, outp, conf = d.conf) + let backend = if isDefined(d.conf, "js"): "js" + elif isDefined(d.conf, "cpp"): "cpp" + elif isDefined(d.conf, "objc"): "objc" + else: "c" + if os.execShellCmd(os.getAppFilename() & " " & backend & + " --nimcache:" & outputDir & " -r " & outp) != 0: + quit "[Examples] failed: see " & outp + else: + # keep generated source file `outp` to allow inspection. + rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp]) + removeFile(outp.changeFileExt(ExeExt)) + +proc extractImports(n: PNode; result: PNode) = + if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}: + result.add copyTree(n) + n.kind = nkEmpty + return + for i in 0..<n.safeLen: extractImports(n[i], result) + +proc prepareExamples(d: PDoc; n: PNode) = + var runnableExamples = newTree(nkStmtList, + newTree(nkImportStmt, newStrNode(nkStrLit, d.filename))) + runnableExamples.info = n.info + let imports = newTree(nkStmtList) + var savedLastSon = copyTree n.lastSon + extractImports(savedLastSon, imports) + for imp in imports: runnableExamples.add imp + runnableExamples.add newTree(nkBlockStmt, newNode(nkEmpty), copyTree savedLastSon) + testExample(d, runnableExamples) + +proc isRunnableExample(n: PNode): bool = + # Templates and generics don't perform symbol lookups. + result = n.kind == nkSym and n.sym.magic == mRunnableExamples or + n.kind == nkIdent and n.ident.s == "runnableExamples" + proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) = case n.kind of nkCallKinds: - if n[0].kind == nkSym and n[0].sym.magic == mRunnableExamples and + if isRunnableExample(n[0]) and n.len >= 2 and n.lastSon.kind == nkStmtList: - dispA(dest, "\n<strong class=\"examples_text\">$1</strong>\n", + prepareExamples(d, n) + dispA(d.conf, dest, "\n<p><strong class=\"examples_text\">$1</strong></p>\n", "\n\\textbf{$1}\n", [rope"Examples:"]) inc d.listingCounter let id = $d.listingCounter @@ -295,20 +367,20 @@ when false: result = findDocComment(n.sons[i]) if result != nil: return - proc extractDocComment*(s: PSym, d: PDoc = nil): string = + proc extractDocComment*(s: PSym, d: PDoc): string = let n = findDocComment(s.ast) result = "" if not n.isNil: if not d.isNil: var dummyHasToc: bool - renderRstToOut(d[], parseRst(n.comment, toFilename(n.info), + renderRstToOut(d[], parseRst(n.comment, toFilename(d.conf, n.info), toLinenumber(n.info), toColumn(n.info), dummyHasToc, d.options + {roSkipPounds}), result) else: result = n.comment.substr(2).replace("\n##", "\n").strip -proc isVisible(n: PNode): bool = +proc isVisible(d: PDoc; n: PNode): bool = result = false if n.kind == nkPostfix: if n.len == 2 and n.sons[0].kind == nkIdent: @@ -319,8 +391,10 @@ proc isVisible(n: PNode): bool = # exception tracking information here. Instead we copy over the comment # from the proc header. result = {sfExported, sfFromGeneric, sfForward}*n.sym.flags == {sfExported} + if result and containsOrIncl(d.emitted, n.sym.id): + result = false elif n.kind == nkPragmaExpr: - result = isVisible(n.sons[0]) + result = isVisible(d, n.sons[0]) proc getName(d: PDoc, n: PNode, splitAfter = -1): string = case n.kind @@ -335,21 +409,20 @@ proc getName(d: PDoc, n: PNode, splitAfter = -1): string = of nkOpenSymChoice, nkClosedSymChoice: result = getName(d, n[0], splitAfter) else: - internalError(n.info, "getName()") result = "" -proc getNameIdent(n: PNode): PIdent = +proc getNameIdent(cache: IdentCache; n: PNode): PIdent = case n.kind - of nkPostfix: result = getNameIdent(n.sons[1]) - of nkPragmaExpr: result = getNameIdent(n.sons[0]) + of nkPostfix: result = getNameIdent(cache, n.sons[1]) + of nkPragmaExpr: result = getNameIdent(cache, n.sons[0]) of nkSym: result = n.sym.name of nkIdent: result = n.ident of nkAccQuoted: var r = "" - for i in 0..<n.len: r.add(getNameIdent(n[i]).s) - result = getIdent(r) + for i in 0..<n.len: r.add(getNameIdent(cache, n[i]).s) + result = getIdent(cache, r) of nkOpenSymChoice, nkClosedSymChoice: - result = getNameIdent(n[0]) + result = getNameIdent(cache, n[0]) else: result = nil @@ -365,7 +438,6 @@ proc getRstName(n: PNode): PRstNode = of nkOpenSymChoice, nkClosedSymChoice: result = getRstName(n[0]) else: - internalError(n.info, "getRstName()") result = nil proc newUniquePlainSymbol(d: PDoc, original: string): string = @@ -453,7 +525,7 @@ proc docstringSummary(rstText: string): string = proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = - if not isVisible(nameNode): return + if not isVisible(d, nameNode): return let name = getName(d, nameNode) nameRope = name.rope @@ -489,22 +561,21 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = symbolOrIdEncRope = encodeUrl(symbolOrId).rope var seeSrcRope: Rope = nil - let docItemSeeSrc = getConfigVar("doc.item.seesrc") + let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc") if docItemSeeSrc.len > 0: - let cwd = getCurrentDir().canonicalizePath() - var path = n.info.toFullPath + let cwd = canonicalizePath(d.conf, getCurrentDir()) + var path = toFullPath(d.conf, n.info) if path.startsWith(cwd): path = path[cwd.len+1 .. ^1].replace('\\', '/') - let gitUrl = getConfigVar("git.url") + let gitUrl = getConfigVar(d.conf, "git.url") if gitUrl.len > 0: - var commit = getConfigVar("git.commit") - if commit.len == 0: commit = "master" - dispA(seeSrcRope, "$1", "", [ropeFormatNamedVars(docItemSeeSrc, - ["path", "line", "url", "commit"], [rope path, - rope($n.info.line), rope gitUrl, - rope commit])]) - - add(d.section[k], ropeFormatNamedVars(getConfigVar("doc.item"), + let commit = getConfigVar(d.conf, "git.commit", "master") + let develBranch = getConfigVar(d.conf, "git.devel", "devel") + dispA(d.conf, seeSrcRope, "$1", "", [ropeFormatNamedVars(d.conf, docItemSeeSrc, + ["path", "line", "url", "commit", "devel"], [rope path, + rope($n.info.line), rope gitUrl, rope commit, rope develBranch])]) + + add(d.section[k], ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.item"), ["name", "header", "desc", "itemID", "header_plain", "itemSym", "itemSymOrID", "itemSymEnc", "itemSymOrIDEnc", "seeSrc"], [nameRope, result, comm, itemIDRope, plainNameRope, plainSymbolRope, @@ -515,7 +586,7 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = let att = attachToType(d, nameNode.sym) if att != nil: attype = rope esc(d.target, att.name.s) - add(d.toc[k], ropeFormatNamedVars(getConfigVar("doc.item.toc"), + add(d.toc[k], ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.item.toc"), ["name", "header", "desc", "itemID", "header_plain", "itemSym", "itemSymOrID", "itemSymEnc", "itemSymOrIDEnc", "attype"], [rope(getName(d, nameNode, d.splitAfter)), result, comm, @@ -535,7 +606,7 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = d.types.strTableAdd nameNode.sym proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode = - if not isVisible(nameNode): return + if not isVisible(d, nameNode): return var name = getName(d, nameNode) comm = $genRecComment(d, n) @@ -543,11 +614,11 @@ proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode = initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments}) - result = %{ "name": %name, "type": %($k), "line": %n.info.line, + result = %{ "name": %name, "type": %($k), "line": %n.info.line.int, "col": %n.info.col} - if comm != nil and comm != "": + if comm.len > 0: result["description"] = %comm - if r.buf != nil: + if r.buf.len > 0: result["code"] = %r.buf proc checkForFalse(n: PNode): bool = @@ -568,29 +639,29 @@ proc traceDeps(d: PDoc, it: PNode) = traceDeps(d, a) else: if d.section[k] != nil: add(d.section[k], ", ") - dispA(d.section[k], + dispA(d.conf, d.section[k], "<a class=\"reference external\" href=\"$1.html\">$1</a>", - "$1", [rope(getModuleName(it))]) + "$1", [rope(splitFile(getModuleName(d.conf, it)).name)]) proc generateDoc*(d: PDoc, n: PNode) = case n.kind of nkCommentStmt: add(d.modDesc, genComment(d, n)) of nkProcDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) genItem(d, n, n.sons[namePos], skProc) of nkFuncDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) genItem(d, n, n.sons[namePos], skFunc) of nkMethodDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) genItem(d, n, n.sons[namePos], skMethod) of nkIteratorDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) genItem(d, n, n.sons[namePos], skIterator) of nkMacroDef: genItem(d, n, n.sons[namePos], skMacro) of nkTemplateDef: genItem(d, n, n.sons[namePos], skTemplate) of nkConverterDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) genItem(d, n, n.sons[namePos], skConverter) of nkTypeSection, nkVarSection, nkLetSection, nkConstSection: for i in countup(0, sonsLen(n) - 1): @@ -607,6 +678,10 @@ proc generateDoc*(d: PDoc, n: PNode) = of nkImportStmt: for i in 0 .. sonsLen(n)-1: traceDeps(d, n.sons[i]) of nkFromStmt, nkImportExceptStmt: traceDeps(d, n.sons[0]) + of nkCallKinds: + var comm: Rope = nil + getAllRunnableExamples(d, n, comm) + if comm > nil: add(d.modDesc, comm) else: discard proc add(d: PDoc; j: JsonNode) = @@ -615,28 +690,28 @@ proc add(d: PDoc; j: JsonNode) = proc generateJson*(d: PDoc, n: PNode) = case n.kind of nkCommentStmt: - if n.comment != nil and startsWith(n.comment, "##"): + if startsWith(n.comment, "##"): let stripped = n.comment.substr(2).strip - d.add %{ "comment": %stripped, "line": %n.info.line, + d.add %{ "comment": %stripped, "line": %n.info.line.int, "col": %n.info.col } of nkProcDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) d.add genJsonItem(d, n, n.sons[namePos], skProc) of nkFuncDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) d.add genJsonItem(d, n, n.sons[namePos], skFunc) of nkMethodDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) d.add genJsonItem(d, n, n.sons[namePos], skMethod) of nkIteratorDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) d.add genJsonItem(d, n, n.sons[namePos], skIterator) of nkMacroDef: d.add genJsonItem(d, n, n.sons[namePos], skMacro) of nkTemplateDef: d.add genJsonItem(d, n, n.sons[namePos], skTemplate) of nkConverterDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) d.add genJsonItem(d, n, n.sons[namePos], skConverter) of nkTypeSection, nkVarSection, nkLetSection, nkConstSection: for i in countup(0, sonsLen(n) - 1): @@ -659,27 +734,27 @@ proc genTagsItem(d: PDoc, n, nameNode: PNode, k: TSymKind): string = proc generateTags*(d: PDoc, n: PNode, r: var Rope) = case n.kind of nkCommentStmt: - if n.comment != nil and startsWith(n.comment, "##"): + if startsWith(n.comment, "##"): let stripped = n.comment.substr(2).strip r.add stripped of nkProcDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) r.add genTagsItem(d, n, n.sons[namePos], skProc) of nkFuncDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) r.add genTagsItem(d, n, n.sons[namePos], skFunc) of nkMethodDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) r.add genTagsItem(d, n, n.sons[namePos], skMethod) of nkIteratorDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) r.add genTagsItem(d, n, n.sons[namePos], skIterator) of nkMacroDef: r.add genTagsItem(d, n, n.sons[namePos], skMacro) of nkTemplateDef: r.add genTagsItem(d, n, n.sons[namePos], skTemplate) of nkConverterDef: - when useEffectSystem: documentRaises(n) + when useEffectSystem: documentRaises(d.cache, n) r.add genTagsItem(d, n, n.sons[namePos], skConverter) of nkTypeSection, nkVarSection, nkLetSection, nkConstSection: for i in countup(0, sonsLen(n) - 1): @@ -703,10 +778,10 @@ proc genSection(d: PDoc, kind: TSymKind) = ] if d.section[kind] == nil: return var title = sectionNames[kind].rope - d.section[kind] = ropeFormatNamedVars(getConfigVar("doc.section"), [ + d.section[kind] = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.section"), [ "sectionid", "sectionTitle", "sectionTitleID", "content"], [ ord(kind).rope, title, rope(ord(kind) + 50), d.section[kind]]) - d.toc[kind] = ropeFormatNamedVars(getConfigVar("doc.section.toc"), [ + d.toc[kind] = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.section.toc"), [ "sectionid", "sectionTitle", "sectionTitleID", "content"], [ ord(kind).rope, title, rope(ord(kind) + 50), d.toc[kind]]) @@ -722,7 +797,7 @@ proc genOutFile(d: PDoc): Rope = genSection(d, i) add(toc, d.toc[i]) if toc != nil: - toc = ropeFormatNamedVars(getConfigVar("doc.toc"), ["content"], [toc]) + toc = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.toc"), ["content"], [toc]) for i in countup(low(TSymKind), high(TSymKind)): add(code, d.section[i]) # Extract the title. Non API modules generate an entry in the index table. @@ -736,13 +811,13 @@ proc genOutFile(d: PDoc): Rope = let bodyname = if d.hasToc and not d.isPureRst: "doc.body_toc_group" elif d.hasToc: "doc.body_toc" else: "doc.body_no_toc" - content = ropeFormatNamedVars(getConfigVar(bodyname), ["title", + content = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, bodyname), ["title", "tableofcontents", "moduledesc", "date", "time", "content"], [title.rope, toc, d.modDesc, rope(getDateStr()), rope(getClockStr()), code]) - if optCompileOnly notin gGlobalOptions: + if optCompileOnly notin d.conf.globalOptions: # XXX what is this hack doing here? 'optCompileOnly' means raw output!? - code = ropeFormatNamedVars(getConfigVar("doc.file"), ["title", + code = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.file"), ["title", "tableofcontents", "moduledesc", "date", "time", "content", "author", "version", "analytics"], [title.rope, toc, d.modDesc, rope(getDateStr()), @@ -753,60 +828,63 @@ proc genOutFile(d: PDoc): Rope = result = code proc generateIndex*(d: PDoc) = - if optGenIndex in gGlobalOptions: - writeIndexFile(d[], splitFile(options.outFile).dir / + if optGenIndex in d.conf.globalOptions: + writeIndexFile(d[], splitFile(d.conf.outFile).dir / splitFile(d.filename).name & IndexExt) -proc getOutFile2(filename, ext, dir: string): string = - if gWholeProject: - let d = if options.outFile != "": options.outFile else: dir +proc getOutFile2(conf: ConfigRef; filename, ext, dir: string): string = + if optWholeProject in conf.globalOptions: + let d = if conf.outFile != "": conf.outFile else: dir createDir(d) result = d / changeFileExt(filename, ext) else: - result = getOutFile(filename, ext) + result = getOutFile(conf, filename, ext) proc writeOutput*(d: PDoc, filename, outExt: string, useWarning = false) = var content = genOutFile(d) - if optStdout in gGlobalOptions: + if optStdout in d.conf.globalOptions: writeRope(stdout, content) else: - writeRope(content, getOutFile2(filename, outExt, "htmldocs"), useWarning) + let outfile = getOutFile2(d.conf, filename, outExt, "htmldocs") + createDir(outfile.parentDir) + if not writeRope(content, outfile): + rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile, outfile) proc writeOutputJson*(d: PDoc, filename, outExt: string, useWarning = false) = let content = %*{"orig": d.filename, - "nimble": getPackageName(d.filename), + "nimble": getPackageName(d.conf, d.filename), "entries": d.jArray} - if optStdout in gGlobalOptions: + if optStdout in d.conf.globalOptions: write(stdout, $content) else: var f: File - if open(f, getOutFile2(splitFile(filename).name, + if open(f, getOutFile2(d.conf, splitFile(filename).name, outExt, "jsondocs"), fmWrite): write(f, $content) close(f) else: discard "fixme: error report" -proc commandDoc*() = - var ast = parseFile(gProjectMainIdx, newIdentCache()) +proc commandDoc*(cache: IdentCache, conf: ConfigRef) = + var ast = parseFile(conf.projectMainIdx, cache, conf) if ast == nil: return - var d = newDocumentor(gProjectFull, options.gConfigVars) + var d = newDocumentor(conf.projectFull, cache, conf) d.hasToc = true generateDoc(d, ast) - writeOutput(d, gProjectFull, HtmlExt) + writeOutput(d, conf.projectFull, HtmlExt) generateIndex(d) -proc commandRstAux(filename, outExt: string) = +proc commandRstAux(cache: IdentCache, conf: ConfigRef; filename, outExt: string) = var filen = addFileExt(filename, "txt") - var d = newDocumentor(filen, options.gConfigVars) + var d = newDocumentor(filen, cache, conf) d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; status: int; content: string) = var outp: string if filename.len == 0: inc(d.id) let nameOnly = splitFile(d.filename).name - let subdir = getNimcacheDir() / nameOnly + let subdir = getNimcacheDir(conf) / nameOnly createDir(subdir) outp = subdir / (nameOnly & "_snippet_" & $d.id & ".nim") elif isAbsolute(filename): @@ -815,14 +893,14 @@ proc commandRstAux(filename, outExt: string) = # Nim's convention: every path is relative to the file it was written in: outp = splitFile(d.filename).dir / filename writeFile(outp, content) - let cmd = unescape(cmd) % quoteShell(outp) - rawMessage(hintExecuting, cmd) + let cmd = cmd % quoteShell(outp) + rawMessage(conf, hintExecuting, cmd) if execShellCmd(cmd) != status: - rawMessage(errExecutionOfProgramFailed, cmd) + rawMessage(conf, errGenerated, "executing of external program failed: " & cmd) d.isPureRst = true var rst = parseRst(readFile(filen), filen, 0, 1, d.hasToc, - {roSupportRawDirective}) + {roSupportRawDirective}, conf) var modDesc = newStringOfCap(30_000) #d.modDesc = newMutableRope(30_000) renderRstToOut(d[], rst, modDesc) @@ -831,50 +909,55 @@ proc commandRstAux(filename, outExt: string) = writeOutput(d, filename, outExt) generateIndex(d) -proc commandRst2Html*() = - commandRstAux(gProjectFull, HtmlExt) +proc commandRst2Html*(cache: IdentCache, conf: ConfigRef) = + commandRstAux(cache, conf, conf.projectFull, HtmlExt) -proc commandRst2TeX*() = - splitter = "\\-" - commandRstAux(gProjectFull, TexExt) +proc commandRst2TeX*(cache: IdentCache, conf: ConfigRef) = + commandRstAux(cache, conf, conf.projectFull, TexExt) -proc commandJson*() = - var ast = parseFile(gProjectMainIdx, newIdentCache()) +proc commandJson*(cache: IdentCache, conf: ConfigRef) = + var ast = parseFile(conf.projectMainIdx, cache, conf) if ast == nil: return - var d = newDocumentor(gProjectFull, options.gConfigVars) + var d = newDocumentor(conf.projectFull, cache, conf) d.hasToc = true generateJson(d, ast) let json = d.jArray let content = rope(pretty(json)) - if optStdout in gGlobalOptions: + if optStdout in d.conf.globalOptions: writeRope(stdout, content) else: #echo getOutFile(gProjectFull, JsonExt) - writeRope(content, getOutFile(gProjectFull, JsonExt), useWarning = false) + let filename = getOutFile(conf, conf.projectFull, JsonExt) + if not writeRope(content, filename): + rawMessage(conf, errCannotOpenFile, filename) -proc commandTags*() = - var ast = parseFile(gProjectMainIdx, newIdentCache()) +proc commandTags*(cache: IdentCache, conf: ConfigRef) = + var ast = parseFile(conf.projectMainIdx, cache, conf) if ast == nil: return - var d = newDocumentor(gProjectFull, options.gConfigVars) + var d = newDocumentor(conf.projectFull, cache, conf) d.hasToc = true var content: Rope generateTags(d, ast, content) - if optStdout in gGlobalOptions: + if optStdout in d.conf.globalOptions: writeRope(stdout, content) else: #echo getOutFile(gProjectFull, TagsExt) - writeRope(content, getOutFile(gProjectFull, TagsExt), useWarning = false) + let filename = getOutFile(conf, conf.projectFull, TagsExt) + if not writeRope(content, filename): + rawMessage(conf, errCannotOpenFile, filename) -proc commandBuildIndex*() = - var content = mergeIndexes(gProjectFull).rope +proc commandBuildIndex*(cache: IdentCache, conf: ConfigRef) = + var content = mergeIndexes(conf.projectFull).rope - let code = ropeFormatNamedVars(getConfigVar("doc.file"), ["title", + let code = ropeFormatNamedVars(conf, getConfigVar(conf, "doc.file"), ["title", "tableofcontents", "moduledesc", "date", "time", "content", "author", "version", "analytics"], ["Index".rope, nil, nil, rope(getDateStr()), rope(getClockStr()), content, nil, nil, nil]) # no analytics because context is not available - writeRope(code, getOutFile("theindex", HtmlExt)) + let filename = getOutFile(conf, "theindex", HtmlExt) + if not writeRope(code, filename): + rawMessage(conf, errCannotOpenFile, filename) diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim index 118f1c7c5..22fef0d47 100644 --- a/compiler/docgen2.nim +++ b/compiler/docgen2.nim @@ -11,7 +11,7 @@ # semantic checking. import - os, options, ast, astalgo, msgs, ropes, idents, passes, docgen + os, options, ast, astalgo, msgs, ropes, idents, passes, docgen, lineinfos from modulegraphs import ModuleGraph @@ -21,12 +21,15 @@ type module: PSym PGen = ref TGen +template shouldProcess(g): bool = + (g.module.owner.id == g.doc.conf.mainPackageId and optWholeProject in g.doc.conf.globalOptions) or + sfMainModule in g.module.flags + template closeImpl(body: untyped) {.dirty.} = var g = PGen(p) let useWarning = sfMainModule notin g.module.flags #echo g.module.name.s, " ", g.module.owner.id, " ", gMainPackageId - if (g.module.owner.id == gMainPackageId and gWholeProject) or - sfMainModule in g.module.flags: + if shouldProcess(g): body try: generateIndex(g.doc) @@ -35,27 +38,29 @@ template closeImpl(body: untyped) {.dirty.} = proc close(graph: ModuleGraph; p: PPassContext, n: PNode): PNode = closeImpl: - writeOutput(g.doc, g.module.filename, HtmlExt, useWarning) + writeOutput(g.doc, toFullPath(graph.config, FileIndex g.module.position), HtmlExt, useWarning) proc closeJson(graph: ModuleGraph; p: PPassContext, n: PNode): PNode = closeImpl: - writeOutputJson(g.doc, g.module.filename, ".json", useWarning) + writeOutputJson(g.doc, toFullPath(graph.config, FileIndex g.module.position), ".json", useWarning) proc processNode(c: PPassContext, n: PNode): PNode = result = n var g = PGen(c) - generateDoc(g.doc, n) + if shouldProcess(g): + generateDoc(g.doc, n) proc processNodeJson(c: PPassContext, n: PNode): PNode = result = n var g = PGen(c) - generateJson(g.doc, n) + if shouldProcess(g): + generateJson(g.doc, n) -proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = +proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = var g: PGen new(g) g.module = module - var d = newDocumentor(module.filename, options.gConfigVars) + var d = newDocumentor(toFullPath(graph.config, FileIndex module.position), graph.cache, graph.config) d.hasToc = true g.doc = d result = g diff --git a/compiler/evalffi.nim b/compiler/evalffi.nim index 5bf8f358a..e863c8995 100644 --- a/compiler/evalffi.nim +++ b/compiler/evalffi.nim @@ -151,7 +151,7 @@ proc getField(n: PNode; position: int): PSym = else: discard proc packObject(x: PNode, typ: PType, res: pointer) = - internalAssert x.kind in {nkObjConstr, nkPar} + internalAssert x.kind in {nkObjConstr, nkPar, nkTupleConstr} # compute the field's offsets: discard typ.getSize for i in countup(ord(x.kind == nkObjConstr), sonsLen(x) - 1): @@ -260,14 +260,14 @@ proc unpackObject(x: pointer, typ: PType, n: PNode): PNode = # iterate over any actual field of 'n' ... if n is nil we need to create # the nkPar node: if n.isNil: - result = newNode(nkPar) + result = newNode(nkTupleConstr) result.typ = typ if typ.n.isNil: internalError("cannot unpack unnamed tuple") unpackObjectAdd(x, typ.n, result) else: result = n - if result.kind notin {nkObjConstr, nkPar}: + if result.kind notin {nkObjConstr, nkPar, nkTupleConstr}: globalError(n.info, "cannot map value from FFI") if typ.n.isNil: globalError(n.info, "cannot unpack unnamed tuple") @@ -442,7 +442,7 @@ proc callForeignFunction*(call: PNode): PNode = libffi.call(cif, fn, retVal, args) if retVal.isNil: - result = emptyNode + result = newNode(nkEmpty) else: result = unpack(retVal, typ.sons[0], nil) result.info = call.info @@ -484,7 +484,7 @@ proc callForeignFunction*(fn: PNode, fntyp: PType, libffi.call(cif, fn, retVal, cargs) if retVal.isNil: - result = emptyNode + result = newNode(nkEmpty) else: result = unpack(retVal, fntyp.sons[0], nil) result.info = info diff --git a/compiler/evaltempl.nim b/compiler/evaltempl.nim index 704ff819c..d6c630e79 100644 --- a/compiler/evaltempl.nim +++ b/compiler/evaltempl.nim @@ -11,14 +11,15 @@ import strutils, options, ast, astalgo, msgs, os, idents, wordrecg, renderer, - rodread + lineinfos type - TemplCtx {.pure, final.} = object + TemplCtx = object owner, genSymOwner: PSym instLines: bool # use the instantiation lines numbers mapping: TIdTable # every gensym'ed symbol needs to be mapped to some # new symbol + config: ConfigRef proc copyNode(ctx: TemplCtx, a, b: PNode): PNode = result = copyNode(a) @@ -42,7 +43,7 @@ proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) = s.kind == skType and s.typ != nil and s.typ.kind == tyGenericParam: handleParam actual.sons[s.owner.typ.len + s.position - 1] else: - internalAssert sfGenSym in s.flags or s.kind == skType + internalAssert c.config, sfGenSym in s.flags or s.kind == skType var x = PSym(idTableGet(c.mapping, s)) if x == nil: x = copySym(s, false) @@ -59,11 +60,16 @@ proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) = evalTemplateAux(templ.sons[i], actual, c, res) result.add res -proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode = +const + errWrongNumberOfArguments = "wrong number of arguments" + errMissingGenericParamsForTemplate = "'$1' has unspecified generic parameters" + errTemplateInstantiationTooNested = "template instantiation too nested" + +proc evalTemplateArgs(n: PNode, s: PSym; conf: ConfigRef; fromHlo: bool): PNode = # if the template has zero arguments, it can be called without ``()`` # `n` is then a nkSym or something similar var totalParams = case n.kind - of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: n.len-1 + of nkCallKinds: n.len-1 else: 0 var @@ -82,10 +88,10 @@ proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode = if givenRegularParams < 0: givenRegularParams = 0 if totalParams > expectedRegularParams + genericParams: - globalError(n.info, errWrongNumberOfArguments) + globalError(conf, n.info, errWrongNumberOfArguments) if totalParams < genericParams: - globalError(n.info, errMissingGenericParamsForTemplate, + globalError(conf, n.info, errMissingGenericParamsForTemplate % n.renderTree) result = newNodeI(nkArgList, n.info) @@ -97,8 +103,8 @@ proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode = for i in givenRegularParams+1 .. expectedRegularParams: let default = s.typ.n.sons[i].sym.ast if default.isNil or default.kind == nkEmpty: - localError(n.info, errWrongNumberOfArguments) - addSon(result, ast.emptyNode) + localError(conf, n.info, errWrongNumberOfArguments) + addSon(result, newNodeI(nkEmpty, n.info)) else: addSon(result, default.copyTree) @@ -106,8 +112,8 @@ proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode = for i in 1 .. genericParams: result.addSon n.sons[givenRegularParams + i] -var evalTemplateCounter* = 0 - # to prevent endless recursion in templates instantiation +# to prevent endless recursion in template instantiation +const evalTemplateLimit* = 1000 proc wrapInComesFrom*(info: TLineInfo; sym: PSym; res: PNode): PNode = when true: @@ -131,17 +137,19 @@ proc wrapInComesFrom*(info: TLineInfo; sym: PSym; res: PNode): PNode = result.add res result.typ = res.typ -proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym; fromHlo=false): PNode = - inc(evalTemplateCounter) - if evalTemplateCounter > 100: - globalError(n.info, errTemplateInstantiationTooNested) +proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym; + conf: ConfigRef; fromHlo=false): PNode = + inc(conf.evalTemplateCounter) + if conf.evalTemplateCounter > evalTemplateLimit: + globalError(conf, n.info, errTemplateInstantiationTooNested) result = n # replace each param by the corresponding node: - var args = evalTemplateArgs(n, tmpl, fromHlo) + var args = evalTemplateArgs(n, tmpl, conf, fromHlo) var ctx: TemplCtx ctx.owner = tmpl ctx.genSymOwner = genSymOwner + ctx.config = conf initIdTable(ctx.mapping) let body = tmpl.getBody @@ -150,7 +158,7 @@ proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym; fromHlo=false): PNode = evalTemplateAux(body, args, ctx, result) if result.len == 1: result = result.sons[0] else: - localError(result.info, errIllFormedAstX, + localError(conf, result.info, "illformed AST: " & renderTree(result, {renderNoComments})) else: result = copyNode(body) @@ -161,5 +169,5 @@ proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym; fromHlo=false): PNode = evalTemplateAux(body.sons[i], args, ctx, result) result.flags.incl nfFromTemplate result = wrapInComesFrom(n.info, tmpl, result) - dec(evalTemplateCounter) + dec(conf.evalTemplateCounter) diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index 8b5a3bf3d..16b0d614d 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -9,19 +9,14 @@ # Module providing functions for calling the different external C compilers # Uses some hard-wired facts about each C/C++ compiler, plus options read -# from a configuration file, to provide generalized procedures to compile +# from a lineinfos file, to provide generalized procedures to compile # nim files. import ropes, os, strutils, osproc, platform, condsyms, options, msgs, - std / sha1, streams - -#from debuginfo import writeDebugInfo + lineinfos, std / sha1, streams type - TSystemCC* = enum - ccNone, ccGcc, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc, - ccTcc, ccPcc, ccUcc, ccIcl, ccIcc TInfoCCProp* = enum # properties of the C compiler: hasSwitchRange, # CC allows ranges in switch statements (GNU C) hasComputedGoto, # CC has computed goto (GNU C extension) @@ -88,6 +83,31 @@ compiler gcc: props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm, hasAttribute}) +# GNU C and C++ Compiler +compiler nintendoSwitchGCC: + result = ( + name: "switch_gcc", + objExt: "o", + optSpeed: " -O3 -ffast-math ", + optSize: " -Os -ffast-math ", + compilerExe: "aarch64-none-elf-gcc", + cppCompiler: "aarch64-none-elf-g++", + compileTmpl: "-w -MMD -MP -MF $dfile -c $options $include -o $objfile $file", + buildGui: " -mwindows", + buildDll: " -shared", + buildLib: "aarch64-none-elf-gcc-ar rcs $libfile $objfiles", + linkerExe: "aarch64-none-elf-gcc", + linkTmpl: "$buildgui $builddll -Wl,-Map,$mapfile -o $exefile $objfiles $options", + includeCmd: " -I", + linkDirCmd: " -L", + linkLibCmd: " -l$1", + debug: "", + pic: "-fPIE", + asmStmtFrmt: "asm($1);$n", + structStmtFmt: "$1 $3 $2 ", # struct|union [packed] $name + props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm, + hasAttribute}) + # LLVM Frontend for GCC/G++ compiler llvmGcc: result = gcc() # Uses settings from GCC @@ -176,10 +196,10 @@ compiler bcc: result = ( name: "bcc", objExt: "obj", - optSpeed: " -O2 -6 ", + optSpeed: " -O3 -6 ", optSize: " -O1 -6 ", - compilerExe: "bcc32", - cppCompiler: "", + compilerExe: "bcc32c", + cppCompiler: "cpp32c", compileTmpl: "-c $options $include -o$objfile $file", buildGui: " -tW", buildDll: " -tWD", @@ -193,7 +213,9 @@ compiler bcc: pic: "", asmStmtFrmt: "__asm{$n$1$n}$n", structStmtFmt: "$1 $2", - props: {hasCpp}) + props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, + hasAttribute}) + # Digital Mars C Compiler compiler dmc: @@ -319,6 +341,7 @@ compiler ucc: const CC*: array[succ(low(TSystemCC))..high(TSystemCC), TInfoCC] = [ gcc(), + nintendoSwitchGCC(), llvmGcc(), clang(), lcc(), @@ -334,37 +357,8 @@ const hExt* = ".h" -var - cCompiler* = ccGcc # the used compiler - gMixedMode*: bool # true if some module triggered C++ codegen - cIncludes*: seq[string] = @[] # directories to search for included files - cLibs*: seq[string] = @[] # directories to search for lib files - cLinkedLibs*: seq[string] = @[] # libraries to link - -# implementation - -proc libNameTmpl(): string {.inline.} = - result = if targetOS == osWindows: "$1.lib" else: "lib$1.a" - -type - CfileFlag* {.pure.} = enum - Cached, ## no need to recompile this time - External ## file was introduced via .compile pragma - - Cfile* = object - cname*, obj*: string - flags*: set[CFileFlag] - CfileList = seq[Cfile] - -var - externalToLink: seq[string] = @[] # files to link in addition to the file - # we compiled - linkOptionsCmd: string = "" - compileOptionsCmd: seq[string] = @[] - linkOptions: string = "" - compileOptions: string = "" - ccompilerpath: string = "" - toCompile: CfileList = @[] +proc libNameTmpl(conf: ConfigRef): string {.inline.} = + result = if conf.target.targetOS == osWindows: "$1.lib" else: "lib$1.a" proc nameToCC*(name: string): TSystemCC = ## Returns the kind of compiler referred to by `name`, or ccNone @@ -374,240 +368,251 @@ proc nameToCC*(name: string): TSystemCC = return i result = ccNone -proc getConfigVar(c: TSystemCC, suffix: string): string = +proc getConfigVar(conf: ConfigRef; c: TSystemCC, suffix: string): string = # use ``cpu.os.cc`` for cross compilation, unless ``--compileOnly`` is given # for niminst support let fullSuffix = - if gCmd == cmdCompileToCpp: + if conf.cmd == cmdCompileToCpp: ".cpp" & suffix - elif gCmd == cmdCompileToOC: + elif conf.cmd == cmdCompileToOC: ".objc" & suffix - elif gCmd == cmdCompileToJS: + elif conf.cmd == cmdCompileToJS: ".js" & suffix else: suffix - if (platform.hostOS != targetOS or platform.hostCPU != targetCPU) and - optCompileOnly notin gGlobalOptions: - let fullCCname = platform.CPU[targetCPU].name & '.' & - platform.OS[targetOS].name & '.' & + if (conf.target.hostOS != conf.target.targetOS or conf.target.hostCPU != conf.target.targetCPU) and + optCompileOnly notin conf.globalOptions: + let fullCCname = platform.CPU[conf.target.targetCPU].name & '.' & + platform.OS[conf.target.targetOS].name & '.' & CC[c].name & fullSuffix - result = getConfigVar(fullCCname) + result = getConfigVar(conf, fullCCname) if result.len == 0: # not overriden for this cross compilation setting? - result = getConfigVar(CC[c].name & fullSuffix) + result = getConfigVar(conf, CC[c].name & fullSuffix) else: - result = getConfigVar(CC[c].name & fullSuffix) - -proc setCC*(ccname: string) = - cCompiler = nameToCC(ccname) - if cCompiler == ccNone: rawMessage(errUnknownCcompiler, ccname) - compileOptions = getConfigVar(cCompiler, ".options.always") - linkOptions = "" - ccompilerpath = getConfigVar(cCompiler, ".path") - for i in countup(low(CC), high(CC)): undefSymbol(CC[i].name) - defineSymbol(CC[cCompiler].name) + result = getConfigVar(conf, CC[c].name & fullSuffix) + +proc setCC*(conf: ConfigRef; ccname: string; info: TLineInfo) = + conf.cCompiler = nameToCC(ccname) + if conf.cCompiler == ccNone: + localError(conf, info, "unknown C compiler: '$1'" % ccname) + conf.compileOptions = getConfigVar(conf, conf.cCompiler, ".options.always") + conf.linkOptions = "" + conf.ccompilerpath = getConfigVar(conf, conf.cCompiler, ".path") + for i in countup(low(CC), high(CC)): undefSymbol(conf.symbols, CC[i].name) + defineSymbol(conf.symbols, CC[conf.cCompiler].name) proc addOpt(dest: var string, src: string) = if len(dest) == 0 or dest[len(dest)-1] != ' ': add(dest, " ") add(dest, src) -proc addLinkOption*(option: string) = - addOpt(linkOptions, option) +proc addLinkOption*(conf: ConfigRef; option: string) = + addOpt(conf.linkOptions, option) -proc addCompileOption*(option: string) = - if strutils.find(compileOptions, option, 0) < 0: - addOpt(compileOptions, option) +proc addCompileOption*(conf: ConfigRef; option: string) = + if strutils.find(conf.compileOptions, option, 0) < 0: + addOpt(conf.compileOptions, option) -proc addLinkOptionCmd*(option: string) = - addOpt(linkOptionsCmd, option) +proc addLinkOptionCmd*(conf: ConfigRef; option: string) = + addOpt(conf.linkOptionsCmd, option) -proc addCompileOptionCmd*(option: string) = - compileOptionsCmd.add(option) +proc addCompileOptionCmd*(conf: ConfigRef; option: string) = + conf.compileOptionsCmd.add(option) -proc initVars*() = +proc initVars*(conf: ConfigRef) = # we need to define the symbol here, because ``CC`` may have never been set! - for i in countup(low(CC), high(CC)): undefSymbol(CC[i].name) - defineSymbol(CC[cCompiler].name) - addCompileOption(getConfigVar(cCompiler, ".options.always")) + for i in countup(low(CC), high(CC)): undefSymbol(conf.symbols, CC[i].name) + defineSymbol(conf.symbols, CC[conf.cCompiler].name) + addCompileOption(conf, getConfigVar(conf, conf.cCompiler, ".options.always")) #addLinkOption(getConfigVar(cCompiler, ".options.linker")) - if len(ccompilerpath) == 0: - ccompilerpath = getConfigVar(cCompiler, ".path") + if len(conf.ccompilerpath) == 0: + conf.ccompilerpath = getConfigVar(conf, conf.cCompiler, ".path") -proc completeCFilePath*(cfile: string, createSubDir: bool = true): string = - result = completeGeneratedFilePath(cfile, createSubDir) +proc completeCFilePath*(conf: ConfigRef; cfile: string, createSubDir: bool = true): string = + result = completeGeneratedFilePath(conf, cfile, createSubDir) -proc toObjFile*(filename: string): string = +proc toObjFile*(conf: ConfigRef; filename: string): string = # Object file for compilation - #if filename.endsWith(".cpp"): - # result = changeFileExt(filename, "cpp." & CC[cCompiler].objExt) - #else: - result = changeFileExt(filename, CC[cCompiler].objExt) + result = filename & "." & CC[conf.cCompiler].objExt -proc addFileToCompile*(cf: Cfile) = - toCompile.add(cf) +proc addFileToCompile*(conf: ConfigRef; cf: Cfile) = + conf.toCompile.add(cf) -proc resetCompilationLists* = - toCompile.setLen 0 +proc resetCompilationLists*(conf: ConfigRef) = + conf.toCompile.setLen 0 ## XXX: we must associate these with their originating module # when the module is loaded/unloaded it adds/removes its items # That's because we still need to hash check the external files # Maybe we can do that in checkDep on the other hand? - externalToLink.setLen 0 + conf.externalToLink.setLen 0 -proc addExternalFileToLink*(filename: string) = - externalToLink.insert(filename, 0) +proc addExternalFileToLink*(conf: ConfigRef; filename: string) = + conf.externalToLink.insert(filename, 0) -proc execWithEcho(cmd: string, msg = hintExecuting): int = - rawMessage(msg, cmd) +proc execWithEcho(conf: ConfigRef; cmd: string, msg = hintExecuting): int = + rawMessage(conf, msg, cmd) result = execCmd(cmd) -proc execExternalProgram*(cmd: string, msg = hintExecuting) = - if execWithEcho(cmd, msg) != 0: - rawMessage(errExecutionOfProgramFailed, cmd) +proc execExternalProgram*(conf: ConfigRef; cmd: string, msg = hintExecuting) = + if execWithEcho(conf, cmd, msg) != 0: + rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" % + cmd) -proc generateScript(projectFile: string, script: Rope) = +proc generateScript(conf: ConfigRef; projectFile: string, script: Rope) = let (dir, name, ext) = splitFile(projectFile) - writeRope(script, dir / addFileExt("compile_" & name, - platform.OS[targetOS].scriptExt)) + let filename = getNimcacheDir(conf) / addFileExt("compile_" & name, + platform.OS[conf.target.targetOS].scriptExt) + if writeRope(script, filename): + copyFile(conf.libpath / "nimbase.h", getNimcacheDir(conf) / "nimbase.h") + else: + rawMessage(conf, errGenerated, "could not write to file: " & filename) -proc getOptSpeed(c: TSystemCC): string = - result = getConfigVar(c, ".options.speed") +proc getOptSpeed(conf: ConfigRef; c: TSystemCC): string = + result = getConfigVar(conf, c, ".options.speed") if result == "": result = CC[c].optSpeed # use default settings from this file -proc getDebug(c: TSystemCC): string = - result = getConfigVar(c, ".options.debug") +proc getDebug(conf: ConfigRef; c: TSystemCC): string = + result = getConfigVar(conf, c, ".options.debug") if result == "": result = CC[c].debug # use default settings from this file -proc getOptSize(c: TSystemCC): string = - result = getConfigVar(c, ".options.size") +proc getOptSize(conf: ConfigRef; c: TSystemCC): string = + result = getConfigVar(conf, c, ".options.size") if result == "": result = CC[c].optSize # use default settings from this file -proc noAbsolutePaths: bool {.inline.} = +proc noAbsolutePaths(conf: ConfigRef): bool {.inline.} = # We used to check current OS != specified OS, but this makes no sense # really: Cross compilation from Linux to Linux for example is entirely # reasonable. # `optGenMapping` is included here for niminst. - result = gGlobalOptions * {optGenScript, optGenMapping} != {} + result = conf.globalOptions * {optGenScript, optGenMapping} != {} -proc cFileSpecificOptions(cfilename: string): string = - result = compileOptions - for option in compileOptionsCmd: +proc cFileSpecificOptions(conf: ConfigRef; cfilename: string): string = + result = conf.compileOptions + for option in conf.compileOptionsCmd: if strutils.find(result, option, 0) < 0: addOpt(result, option) - var trunk = splitFile(cfilename).name - if optCDebug in gGlobalOptions: - var key = trunk & ".debug" - if existsConfigVar(key): addOpt(result, getConfigVar(key)) - else: addOpt(result, getDebug(cCompiler)) - if optOptimizeSpeed in gOptions: - var key = trunk & ".speed" - if existsConfigVar(key): addOpt(result, getConfigVar(key)) - else: addOpt(result, getOptSpeed(cCompiler)) - elif optOptimizeSize in gOptions: - var key = trunk & ".size" - if existsConfigVar(key): addOpt(result, getConfigVar(key)) - else: addOpt(result, getOptSize(cCompiler)) - var key = trunk & ".always" - if existsConfigVar(key): addOpt(result, getConfigVar(key)) - -proc getCompileOptions: string = - result = cFileSpecificOptions("__dummy__") - -proc getLinkOptions: string = - result = linkOptions & " " & linkOptionsCmd & " " - for linkedLib in items(cLinkedLibs): - result.add(CC[cCompiler].linkLibCmd % linkedLib.quoteShell) - for libDir in items(cLibs): - result.add(join([CC[cCompiler].linkDirCmd, libDir.quoteShell])) - -proc needsExeExt(): bool {.inline.} = - result = (optGenScript in gGlobalOptions and targetOS == osWindows) or - (platform.hostOS == osWindows) - -proc getCompilerExe(compiler: TSystemCC; cfile: string): string = - result = if gCmd == cmdCompileToCpp and not cfile.endsWith(".c"): + let trunk = splitFile(cfilename).name + if optCDebug in conf.globalOptions: + let key = trunk & ".debug" + if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key)) + else: addOpt(result, getDebug(conf, conf.cCompiler)) + if optOptimizeSpeed in conf.options: + let key = trunk & ".speed" + if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key)) + else: addOpt(result, getOptSpeed(conf, conf.cCompiler)) + elif optOptimizeSize in conf.options: + let key = trunk & ".size" + if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key)) + else: addOpt(result, getOptSize(conf, conf.cCompiler)) + let key = trunk & ".always" + if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key)) + +proc getCompileOptions(conf: ConfigRef): string = + result = cFileSpecificOptions(conf, "__dummy__") + +proc getLinkOptions(conf: ConfigRef): string = + result = conf.linkOptions & " " & conf.linkOptionsCmd & " " + for linkedLib in items(conf.cLinkedLibs): + result.add(CC[conf.cCompiler].linkLibCmd % linkedLib.quoteShell) + for libDir in items(conf.cLibs): + result.add(join([CC[conf.cCompiler].linkDirCmd, libDir.quoteShell])) + +proc needsExeExt(conf: ConfigRef): bool {.inline.} = + result = (optGenScript in conf.globalOptions and conf.target.targetOS == osWindows) or + (conf.target.hostOS == osWindows) + +proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; cfile: string): string = + result = if conf.cmd == cmdCompileToCpp and not cfile.endsWith(".c"): CC[compiler].cppCompiler else: CC[compiler].compilerExe if result.len == 0: - rawMessage(errCompilerDoesntSupportTarget, CC[compiler].name) + rawMessage(conf, errGenerated, + "Compiler '$1' doesn't support the requested target" % + CC[compiler].name) -proc getLinkerExe(compiler: TSystemCC): string = +proc getLinkerExe(conf: ConfigRef; compiler: TSystemCC): string = result = if CC[compiler].linkerExe.len > 0: CC[compiler].linkerExe - elif gMixedMode and gCmd != cmdCompileToCpp: CC[compiler].cppCompiler - else: compiler.getCompilerExe("") - -proc getCompileCFileCmd*(cfile: Cfile): string = - var c = cCompiler - var options = cFileSpecificOptions(cfile.cname) - var exe = getConfigVar(c, ".exe") - if exe.len == 0: exe = c.getCompilerExe(cfile.cname) - - if needsExeExt(): exe = addFileExt(exe, "exe") - if optGenDynLib in gGlobalOptions and - ospNeedsPIC in platform.OS[targetOS].props: + elif optMixedMode in conf.globalOptions and conf.cmd != cmdCompileToCpp: CC[compiler].cppCompiler + else: getCompilerExe(conf, compiler, "") + +proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile): string = + var c = conf.cCompiler + var options = cFileSpecificOptions(conf, cfile.cname) + var exe = getConfigVar(conf, c, ".exe") + if exe.len == 0: exe = getCompilerExe(conf, c, cfile.cname) + + if needsExeExt(conf): exe = addFileExt(exe, "exe") + if optGenDynLib in conf.globalOptions and + ospNeedsPIC in platform.OS[conf.target.targetOS].props: add(options, ' ' & CC[c].pic) var includeCmd, compilePattern: string - if not noAbsolutePaths(): + if not noAbsolutePaths(conf): # compute include paths: - includeCmd = CC[c].includeCmd & quoteShell(libpath) + includeCmd = CC[c].includeCmd & quoteShell(conf.libpath) - for includeDir in items(cIncludes): + for includeDir in items(conf.cIncludes): includeCmd.add(join([CC[c].includeCmd, includeDir.quoteShell])) - compilePattern = joinPath(ccompilerpath, exe) + compilePattern = joinPath(conf.ccompilerpath, exe) else: includeCmd = "" - compilePattern = c.getCompilerExe(cfile.cname) + compilePattern = getCompilerExe(conf, c, cfile.cname) - var cf = if noAbsolutePaths(): extractFilename(cfile.cname) + var cf = if noAbsolutePaths(conf): extractFilename(cfile.cname) else: cfile.cname var objfile = if cfile.obj.len == 0: - if not cfile.flags.contains(CfileFlag.External) or noAbsolutePaths(): - toObjFile(cf) + if not cfile.flags.contains(CfileFlag.External) or noAbsolutePaths(conf): + toObjFile(conf, cf) else: - completeCFilePath(toObjFile(cf)) - elif noAbsolutePaths(): + completeCFilePath(conf, toObjFile(conf, cf)) + elif noAbsolutePaths(conf): extractFilename(cfile.obj) else: cfile.obj + # D files are required by nintendo switch libs for + # compilation. They are basically a list of all includes. + let dfile = objfile.changeFileExt(".d").quoteShell() + objfile = quoteShell(objfile) cf = quoteShell(cf) result = quoteShell(compilePattern % [ + "dfile", dfile, "file", cf, "objfile", objfile, "options", options, - "include", includeCmd, "nim", getPrefixDir(), - "nim", getPrefixDir(), "lib", libpath]) + "include", includeCmd, "nim", getPrefixDir(conf), + "nim", getPrefixDir(conf), "lib", conf.libpath]) add(result, ' ') addf(result, CC[c].compileTmpl, [ + "dfile", dfile, "file", cf, "objfile", objfile, "options", options, "include", includeCmd, - "nim", quoteShell(getPrefixDir()), - "nim", quoteShell(getPrefixDir()), - "lib", quoteShell(libpath)]) + "nim", quoteShell(getPrefixDir(conf)), + "nim", quoteShell(getPrefixDir(conf)), + "lib", quoteShell(conf.libpath)]) -proc footprint(cfile: Cfile): SecureHash = +proc footprint(conf: ConfigRef; cfile: Cfile): SecureHash = result = secureHash( $secureHashFile(cfile.cname) & - platform.OS[targetOS].name & - platform.CPU[targetCPU].name & - extccomp.CC[extccomp.cCompiler].name & - getCompileCFileCmd(cfile)) + platform.OS[conf.target.targetOS].name & + platform.CPU[conf.target.targetCPU].name & + extccomp.CC[conf.cCompiler].name & + getCompileCFileCmd(conf, cfile)) -proc externalFileChanged(cfile: Cfile): bool = - if gCmd notin {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC, cmdCompileToLLVM}: +proc externalFileChanged(conf: ConfigRef; cfile: Cfile): bool = + if conf.cmd notin {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC, cmdCompileToLLVM}: return false - var hashFile = toGeneratedFile(cfile.cname.withPackageName, "sha1") - var currentHash = footprint(cfile) + var hashFile = toGeneratedFile(conf, conf.withPackageName(cfile.cname), "sha1") + var currentHash = footprint(conf, cfile) var f: File if open(f, hashFile, fmRead): let oldHash = parseSecureHash(f.readLine()) @@ -620,133 +625,146 @@ proc externalFileChanged(cfile: Cfile): bool = f.writeLine($currentHash) close(f) -proc addExternalFileToCompile*(c: var Cfile) = - if optForceFullMake notin gGlobalOptions and not externalFileChanged(c): +proc addExternalFileToCompile*(conf: ConfigRef; c: var Cfile) = + if optForceFullMake notin conf.globalOptions and not externalFileChanged(conf, c): c.flags.incl CfileFlag.Cached - toCompile.add(c) + conf.toCompile.add(c) -proc addExternalFileToCompile*(filename: string) = +proc addExternalFileToCompile*(conf: ConfigRef; filename: string) = var c = Cfile(cname: filename, - obj: toObjFile(completeCFilePath(changeFileExt(filename, ""), false)), + obj: toObjFile(conf, completeCFilePath(conf, filename, false)), flags: {CfileFlag.External}) - addExternalFileToCompile(c) + addExternalFileToCompile(conf, c) -proc compileCFile(list: CFileList, script: var Rope, cmds: var TStringSeq, +proc compileCFile(conf: ConfigRef; list: CFileList, script: var Rope, cmds: var TStringSeq, prettyCmds: var TStringSeq) = for it in list: # call the C compiler for the .c file: if it.flags.contains(CfileFlag.Cached): continue - var compileCmd = getCompileCFileCmd(it) - if optCompileOnly notin gGlobalOptions: + var compileCmd = getCompileCFileCmd(conf, it) + if optCompileOnly notin conf.globalOptions: add(cmds, compileCmd) let (_, name, _) = splitFile(it.cname) - add(prettyCmds, "CC: " & name) - if optGenScript in gGlobalOptions: + add(prettyCmds, if hintCC in conf.notes: "CC: " & name else: "") + if optGenScript in conf.globalOptions: add(script, compileCmd) - add(script, tnl) + add(script, "\n") -proc getLinkCmd(projectfile, objfiles: string): string = - if optGenStaticLib in gGlobalOptions: +proc getLinkCmd(conf: ConfigRef; projectfile, objfiles: string): string = + if optGenStaticLib in conf.globalOptions: var libname: string - if options.outFile.len > 0: - libname = options.outFile.expandTilde + if conf.outFile.len > 0: + libname = conf.outFile.expandTilde if not libname.isAbsolute(): libname = getCurrentDir() / libname else: - libname = (libNameTmpl() % splitFile(gProjectName).name) - result = CC[cCompiler].buildLib % ["libfile", libname, + libname = (libNameTmpl(conf) % splitFile(conf.projectName).name) + result = CC[conf.cCompiler].buildLib % ["libfile", quoteShell(libname), "objfiles", objfiles] else: - var linkerExe = getConfigVar(cCompiler, ".linkerexe") - if len(linkerExe) == 0: linkerExe = cCompiler.getLinkerExe + var linkerExe = getConfigVar(conf, conf.cCompiler, ".linkerexe") + if len(linkerExe) == 0: linkerExe = getLinkerExe(conf, conf.cCompiler) # bug #6452: We must not use ``quoteShell`` here for ``linkerExe`` - if needsExeExt(): linkerExe = addFileExt(linkerExe, "exe") - if noAbsolutePaths(): result = linkerExe - else: result = joinPath(ccompilerpath, linkerExe) - let buildgui = if optGenGuiApp in gGlobalOptions: CC[cCompiler].buildGui - else: "" + if needsExeExt(conf): linkerExe = addFileExt(linkerExe, "exe") + if noAbsolutePaths(conf): result = linkerExe + else: result = joinPath(conf.cCompilerpath, linkerExe) + let buildgui = if optGenGuiApp in conf.globalOptions and conf.target.targetOS == osWindows: + CC[conf.cCompiler].buildGui + else: + "" var exefile, builddll: string - if optGenDynLib in gGlobalOptions: - exefile = platform.OS[targetOS].dllFrmt % splitFile(projectfile).name - builddll = CC[cCompiler].buildDll + if optGenDynLib in conf.globalOptions: + exefile = platform.OS[conf.target.targetOS].dllFrmt % splitFile(projectfile).name + builddll = CC[conf.cCompiler].buildDll else: - exefile = splitFile(projectfile).name & platform.OS[targetOS].exeExt + exefile = splitFile(projectfile).name & platform.OS[conf.target.targetOS].exeExt builddll = "" - if options.outFile.len > 0: - exefile = options.outFile.expandTilde + if conf.outFile.len > 0: + exefile = conf.outFile.expandTilde if not exefile.isAbsolute(): exefile = getCurrentDir() / exefile - if not noAbsolutePaths(): + if not noAbsolutePaths(conf): if not exefile.isAbsolute(): exefile = joinPath(splitFile(projectfile).dir, exefile) when false: - if optCDebug in gGlobalOptions: + if optCDebug in conf.globalOptions: writeDebugInfo(exefile.changeFileExt("ndb")) exefile = quoteShell(exefile) - let linkOptions = getLinkOptions() & " " & - getConfigVar(cCompiler, ".options.linker") - var linkTmpl = getConfigVar(cCompiler, ".linkTmpl") + + # Map files are required by Nintendo Switch compilation. They are a list + # of all function calls in the library and where they come from. + let mapfile = quoteShell(getNimcacheDir(conf) / splitFile(projectFile).name & ".map") + + let linkOptions = getLinkOptions(conf) & " " & + getConfigVar(conf, conf.cCompiler, ".options.linker") + var linkTmpl = getConfigVar(conf, conf.cCompiler, ".linkTmpl") if linkTmpl.len == 0: - linkTmpl = CC[cCompiler].linkTmpl + linkTmpl = CC[conf.cCompiler].linkTmpl result = quoteShell(result % ["builddll", builddll, + "mapfile", mapfile, "buildgui", buildgui, "options", linkOptions, "objfiles", objfiles, - "exefile", exefile, "nim", getPrefixDir(), "lib", libpath]) + "exefile", exefile, "nim", getPrefixDir(conf), "lib", conf.libpath]) result.add ' ' addf(result, linkTmpl, ["builddll", builddll, + "mapfile", mapfile, "buildgui", buildgui, "options", linkOptions, "objfiles", objfiles, "exefile", exefile, - "nim", quoteShell(getPrefixDir()), - "lib", quoteShell(libpath)]) + "nim", quoteShell(getPrefixDir(conf)), + "lib", quoteShell(conf.libpath)]) -template tryExceptOSErrorMessage(errorPrefix: string = "", body: untyped): typed = +template tryExceptOSErrorMessage(conf: ConfigRef; errorPrefix: string = "", body: untyped): typed = try: body except OSError: let ose = (ref OSError)(getCurrentException()) if errorPrefix.len > 0: - rawMessage(errGenerated, errorPrefix & " " & ose.msg & " " & $ose.errorCode) + rawMessage(conf, errGenerated, errorPrefix & " " & ose.msg & " " & $ose.errorCode) else: - rawMessage(errExecutionOfProgramFailed, ose.msg & " " & $ose.errorCode) + rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" % + (ose.msg & " " & $ose.errorCode)) raise -proc execLinkCmd(linkCmd: string) = - tryExceptOSErrorMessage("invocation of external linker program failed."): - execExternalProgram(linkCmd, - if optListCmd in gGlobalOptions or gVerbosity > 1: hintExecuting else: hintLinking) +proc execLinkCmd(conf: ConfigRef; linkCmd: string) = + tryExceptOSErrorMessage(conf, "invocation of external linker program failed."): + execExternalProgram(conf, linkCmd, + if optListCmd in conf.globalOptions or conf.verbosity > 1: hintExecuting else: hintLinking) -proc execCmdsInParallel(cmds: seq[string]; prettyCb: proc (idx: int)) = +proc execCmdsInParallel(conf: ConfigRef; cmds: seq[string]; prettyCb: proc (idx: int)) = let runCb = proc (idx: int, p: Process) = let exitCode = p.peekExitCode if exitCode != 0: - rawMessage(errGenerated, "execution of an external compiler program '" & + rawMessage(conf, errGenerated, "execution of an external compiler program '" & cmds[idx] & "' failed with exit code: " & $exitCode & "\n\n" & p.outputStream.readAll.strip) - if gNumberOfProcessors == 0: gNumberOfProcessors = countProcessors() + if conf.numberOfProcessors == 0: conf.numberOfProcessors = countProcessors() var res = 0 - if gNumberOfProcessors <= 1: + if conf.numberOfProcessors <= 1: for i in countup(0, high(cmds)): - tryExceptOSErrorMessage("invocation of external compiler program failed."): - res = execWithEcho(cmds[i]) - if res != 0: rawMessage(errExecutionOfProgramFailed, cmds[i]) + tryExceptOSErrorMessage(conf, "invocation of external compiler program failed."): + res = execWithEcho(conf, cmds[i]) + if res != 0: + rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" % + cmds[i]) else: - tryExceptOSErrorMessage("invocation of external compiler program failed."): - if optListCmd in gGlobalOptions or gVerbosity > 1: + tryExceptOSErrorMessage(conf, "invocation of external compiler program failed."): + if optListCmd in conf.globalOptions or conf.verbosity > 1: res = execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath}, - gNumberOfProcessors, afterRunEvent=runCb) - elif gVerbosity == 1: + conf.numberOfProcessors, afterRunEvent=runCb) + elif conf.verbosity == 1: res = execProcesses(cmds, {poStdErrToStdOut, poUsePath}, - gNumberOfProcessors, prettyCb, afterRunEvent=runCb) + conf.numberOfProcessors, prettyCb, afterRunEvent=runCb) else: res = execProcesses(cmds, {poStdErrToStdOut, poUsePath}, - gNumberOfProcessors, afterRunEvent=runCb) + conf.numberOfProcessors, afterRunEvent=runCb) if res != 0: - if gNumberOfProcessors <= 1: - rawMessage(errExecutionOfProgramFailed, cmds.join()) + if conf.numberOfProcessors <= 1: + rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" % + cmds.join()) -proc callCCompiler*(projectfile: string) = +proc callCCompiler*(conf: ConfigRef; projectfile: string) = var linkCmd: string - if gGlobalOptions * {optCompileOnly, optGenScript} == {optCompileOnly}: + if conf.globalOptions * {optCompileOnly, optGenScript} == {optCompileOnly}: return # speed up that call if only compiling and no script shall be # generated #var c = cCompiler @@ -754,37 +772,39 @@ proc callCCompiler*(projectfile: string) = var cmds: TStringSeq = @[] var prettyCmds: TStringSeq = @[] let prettyCb = proc (idx: int) = - echo prettyCmds[idx] - compileCFile(toCompile, script, cmds, prettyCmds) - if optCompileOnly notin gGlobalOptions: - execCmdsInParallel(cmds, prettyCb) - if optNoLinking notin gGlobalOptions: + when declared(echo): + let cmd = prettyCmds[idx] + if cmd != "": echo cmd + compileCFile(conf, conf.toCompile, script, cmds, prettyCmds) + if optCompileOnly notin conf.globalOptions: + execCmdsInParallel(conf, cmds, prettyCb) + if optNoLinking notin conf.globalOptions: # call the linker: var objfiles = "" - for it in externalToLink: - let objFile = if noAbsolutePaths(): it.extractFilename else: it + for it in conf.externalToLink: + let objFile = if noAbsolutePaths(conf): it.extractFilename else: it add(objfiles, ' ') add(objfiles, quoteShell( - addFileExt(objFile, CC[cCompiler].objExt))) - for x in toCompile: - let objFile = if noAbsolutePaths(): x.obj.extractFilename else: x.obj + addFileExt(objFile, CC[conf.cCompiler].objExt))) + for x in conf.toCompile: + let objFile = if noAbsolutePaths(conf): x.obj.extractFilename else: x.obj add(objfiles, ' ') add(objfiles, quoteShell(objFile)) - linkCmd = getLinkCmd(projectfile, objfiles) - if optCompileOnly notin gGlobalOptions: - execLinkCmd(linkCmd) + linkCmd = getLinkCmd(conf, projectfile, objfiles) + if optCompileOnly notin conf.globalOptions: + execLinkCmd(conf, linkCmd) else: linkCmd = "" - if optGenScript in gGlobalOptions: + if optGenScript in conf.globalOptions: add(script, linkCmd) - add(script, tnl) - generateScript(projectfile, script) + add(script, "\n") + generateScript(conf, projectfile, script) #from json import escapeJson import json -proc writeJsonBuildInstructions*(projectfile: string) = +proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = template lit(x: untyped) = f.write x template str(x: untyped) = when compiles(escapeJson(x, buf)): @@ -794,11 +814,11 @@ proc writeJsonBuildInstructions*(projectfile: string) = else: f.write escapeJson(x) - proc cfiles(f: File; buf: var string; clist: CfileList, isExternal: bool) = + proc cfiles(conf: ConfigRef; f: File; buf: var string; clist: CfileList, isExternal: bool) = var pastStart = false for it in clist: if CfileFlag.Cached in it.flags: continue - let compileCmd = getCompileCFileCmd(it) + let compileCmd = getCompileCFileCmd(conf, it) if pastStart: lit "],\L" lit "[" str it.cname @@ -807,13 +827,13 @@ proc writeJsonBuildInstructions*(projectfile: string) = pastStart = true lit "]\L" - proc linkfiles(f: File; buf, objfiles: var string; clist: CfileList; + proc linkfiles(conf: ConfigRef; f: File; buf, objfiles: var string; clist: CfileList; llist: seq[string]) = var pastStart = false for it in llist: - let objfile = if noAbsolutePaths(): it.extractFilename + let objfile = if noAbsolutePaths(conf): it.extractFilename else: it - let objstr = addFileExt(objfile, CC[cCompiler].objExt) + let objstr = addFileExt(objfile, CC[conf.cCompiler].objExt) add(objfiles, ' ') add(objfiles, objstr) if pastStart: lit ",\L" @@ -832,25 +852,25 @@ proc writeJsonBuildInstructions*(projectfile: string) = var buf = newStringOfCap(50) let file = projectfile.splitFile.name - let jsonFile = toGeneratedFile(file, "json") + let jsonFile = toGeneratedFile(conf, file, "json") var f: File if open(f, jsonFile, fmWrite): lit "{\"compile\":[\L" - cfiles(f, buf, toCompile, false) + cfiles(conf, f, buf, conf.toCompile, false) lit "],\L\"link\":[\L" var objfiles = "" # XXX add every file here that is to link - linkfiles(f, buf, objfiles, toCompile, externalToLink) + linkfiles(conf, f, buf, objfiles, conf.toCompile, conf.externalToLink) lit "],\L\"linkcmd\": " - str getLinkCmd(projectfile, objfiles) + str getLinkCmd(conf, projectfile, objfiles) lit "\L}\L" close(f) -proc runJsonBuildInstructions*(projectfile: string) = +proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = let file = projectfile.splitFile.name - let jsonFile = toGeneratedFile(file, "json") + let jsonFile = toGeneratedFile(conf, file, "json") try: let data = json.parseFile(jsonFile) let toCompile = data["compile"] @@ -866,33 +886,37 @@ proc runJsonBuildInstructions*(projectfile: string) = add(prettyCmds, "CC: " & name) let prettyCb = proc (idx: int) = - echo prettyCmds[idx] - execCmdsInParallel(cmds, prettyCb) + when declared(echo): + echo prettyCmds[idx] + execCmdsInParallel(conf, cmds, prettyCb) let linkCmd = data["linkcmd"] doAssert linkCmd.kind == JString - execLinkCmd(linkCmd.getStr) + execLinkCmd(conf, linkCmd.getStr) except: - echo getCurrentException().getStackTrace() + when declared(echo): + echo getCurrentException().getStackTrace() quit "error evaluating JSON file: " & jsonFile -proc genMappingFiles(list: CFileList): Rope = +proc genMappingFiles(conf: ConfigRef; list: CFileList): Rope = for it in list: addf(result, "--file:r\"$1\"$N", [rope(it.cname)]) -proc writeMapping*(gSymbolMapping: Rope) = - if optGenMapping notin gGlobalOptions: return +proc writeMapping*(conf: ConfigRef; symbolMapping: Rope) = + if optGenMapping notin conf.globalOptions: return var code = rope("[C_Files]\n") - add(code, genMappingFiles(toCompile)) + add(code, genMappingFiles(conf, conf.toCompile)) add(code, "\n[C_Compiler]\nFlags=") - add(code, strutils.escape(getCompileOptions())) + add(code, strutils.escape(getCompileOptions(conf))) add(code, "\n[Linker]\nFlags=") - add(code, strutils.escape(getLinkOptions() & " " & - getConfigVar(cCompiler, ".options.linker"))) + add(code, strutils.escape(getLinkOptions(conf) & " " & + getConfigVar(conf, conf.cCompiler, ".options.linker"))) add(code, "\n[Environment]\nlibpath=") - add(code, strutils.escape(libpath)) + add(code, strutils.escape(conf.libpath)) - addf(code, "\n[Symbols]$n$1", [gSymbolMapping]) - writeRope(code, joinPath(gProjectPath, "mapping.txt")) + addf(code, "\n[Symbols]$n$1", [symbolMapping]) + let filename = joinPath(conf.projectPath, "mapping.txt") + if not writeRope(code, filename): + rawMessage(conf, errGenerated, "could not write to file: " & filename) diff --git a/compiler/filter_tmpl.nim b/compiler/filter_tmpl.nim index ca9a3a801..09455ced7 100644 --- a/compiler/filter_tmpl.nim +++ b/compiler/filter_tmpl.nim @@ -11,7 +11,7 @@ import llstream, os, wordrecg, idents, strutils, ast, astalgo, msgs, options, - renderer, filters + renderer, filters, lineinfos type TParseState = enum @@ -27,7 +27,7 @@ type emit, conc, toStr: string curly, bracket, par: int pendingExprLine: bool - + config: ConfigRef const PatternChars = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '.', '_'} @@ -35,16 +35,15 @@ const proc newLine(p: var TTmplParser) = llStreamWrite(p.outp, repeat(')', p.emitPar)) p.emitPar = 0 - if p.info.line > int16(1): llStreamWrite(p.outp, "\n") + if p.info.line > uint16(1): llStreamWrite(p.outp, "\n") if p.pendingExprLine: llStreamWrite(p.outp, spaces(2)) p.pendingExprLine = false proc scanPar(p: var TTmplParser, d: int) = var i = d - while true: + while i < p.x.len: case p.x[i] - of '\0': break of '(': inc(p.par) of ')': dec(p.par) of '[': inc(p.bracket) @@ -63,16 +62,19 @@ const proc parseLine(p: var TTmplParser) = var j = 0 - while p.x[j] == ' ': inc(j) - if p.x[0] == p.nimDirective and p.x[1] == '?': + let len = p.x.len + + while j < len and p.x[j] == ' ': inc(j) + + if len >= 2 and p.x[0] == p.nimDirective and p.x[1] == '?': newLine(p) - elif p.x[j] == p.nimDirective: + elif j < len and p.x[j] == p.nimDirective: newLine(p) inc(j) - while p.x[j] == ' ': inc(j) + while j < len and p.x[j] == ' ': inc(j) let d = j var keyw = "" - while p.x[j] in PatternChars: + while j < len and p.x[j] in PatternChars: add(keyw, p.x[j]) inc(j) @@ -84,7 +86,7 @@ proc parseLine(p: var TTmplParser) = dec(p.indent, 2) else: p.info.col = int16(j) - localError(p.info, errXNotAllowedHere, "end") + localError(p.config, p.info, "'end' does not close a control flow construct") llStreamWrite(p.outp, spaces(p.indent)) llStreamWrite(p.outp, "#end") of "if", "when", "try", "while", "for", "block", "case", "proc", "iterator", @@ -126,10 +128,8 @@ proc parseLine(p: var TTmplParser) = llStreamWrite(p.outp, "(\"") inc(p.emitPar) p.state = psTempl - while true: + while j < len: case p.x[j] - of '\0': - break of '\x01'..'\x1F', '\x80'..'\xFF': llStreamWrite(p.outp, "\\x") llStreamWrite(p.outp, toHex(ord(p.x[j]), 2)) @@ -156,11 +156,8 @@ proc parseLine(p: var TTmplParser) = llStreamWrite(p.outp, '(') inc(j) var curly = 0 - while true: + while j < len: case p.x[j] - of '\0': - localError(p.info, errXExpected, "}") - break of '{': inc(j) inc(curly) @@ -173,6 +170,9 @@ proc parseLine(p: var TTmplParser) = else: llStreamWrite(p.outp, p.x[j]) inc(j) + if curly > 0: + localError(p.config, p.info, "expected closing '}'") + break llStreamWrite(p.outp, ')') llStreamWrite(p.outp, p.conc) llStreamWrite(p.outp, '\"') @@ -181,7 +181,7 @@ proc parseLine(p: var TTmplParser) = llStreamWrite(p.outp, p.conc) llStreamWrite(p.outp, p.toStr) llStreamWrite(p.outp, '(') - while p.x[j] in PatternChars: + while j < len and p.x[j] in PatternChars: llStreamWrite(p.outp, p.x[j]) inc(j) llStreamWrite(p.outp, ')') @@ -193,28 +193,29 @@ proc parseLine(p: var TTmplParser) = inc(j) else: p.info.col = int16(j) - localError(p.info, errInvalidExpression, "$") + localError(p.config, p.info, "invalid expression") else: llStreamWrite(p.outp, p.x[j]) inc(j) llStreamWrite(p.outp, "\\n\"") -proc filterTmpl*(stdin: PLLStream, filename: string, call: PNode): PLLStream = +proc filterTmpl*(stdin: PLLStream, filename: string, call: PNode; conf: ConfigRef): PLLStream = var p: TTmplParser - p.info = newLineInfo(filename, 0, 0) + p.config = conf + p.info = newLineInfo(conf, filename, 0, 0) p.outp = llStreamOpen("") p.inp = stdin - p.subsChar = charArg(call, "subschar", 1, '$') - p.nimDirective = charArg(call, "metachar", 2, '#') - p.emit = strArg(call, "emit", 3, "result.add") - p.conc = strArg(call, "conc", 4, " & ") - p.toStr = strArg(call, "tostring", 5, "$") + p.subsChar = charArg(conf, call, "subschar", 1, '$') + p.nimDirective = charArg(conf, call, "metachar", 2, '#') + p.emit = strArg(conf, call, "emit", 3, "result.add") + p.conc = strArg(conf, call, "conc", 4, " & ") + p.toStr = strArg(conf, call, "tostring", 5, "$") p.x = newStringOfCap(120) # do not process the first line which contains the directive: if llStreamReadLine(p.inp, p.x): - p.info.line = p.info.line + int16(1) + p.info.line = p.info.line + 1'u16 while llStreamReadLine(p.inp, p.x): - p.info.line = p.info.line + int16(1) + p.info.line = p.info.line + 1'u16 parseLine(p) newLine(p) result = p.outp diff --git a/compiler/filters.nim b/compiler/filters.nim index 37df628ed..3ebbad678 100644 --- a/compiler/filters.nim +++ b/compiler/filters.nim @@ -13,43 +13,44 @@ import llstream, os, wordrecg, idents, strutils, ast, astalgo, msgs, options, renderer -proc invalidPragma(n: PNode) = - localError(n.info, errXNotAllowedHere, renderTree(n, {renderNoComments})) +proc invalidPragma(conf: ConfigRef; n: PNode) = + localError(conf, n.info, + "'$1' not allowed here" % renderTree(n, {renderNoComments})) -proc getArg(n: PNode, name: string, pos: int): PNode = +proc getArg(conf: ConfigRef; n: PNode, name: string, pos: int): PNode = result = nil if n.kind in {nkEmpty..nkNilLit}: return for i in countup(1, sonsLen(n) - 1): if n.sons[i].kind == nkExprEqExpr: - if n.sons[i].sons[0].kind != nkIdent: invalidPragma(n) + if n.sons[i].sons[0].kind != nkIdent: invalidPragma(conf, n) if cmpIgnoreStyle(n.sons[i].sons[0].ident.s, name) == 0: return n.sons[i].sons[1] elif i == pos: return n.sons[i] -proc charArg*(n: PNode, name: string, pos: int, default: char): char = - var x = getArg(n, name, pos) +proc charArg*(conf: ConfigRef; n: PNode, name: string, pos: int, default: char): char = + var x = getArg(conf, n, name, pos) if x == nil: result = default elif x.kind == nkCharLit: result = chr(int(x.intVal)) - else: invalidPragma(n) + else: invalidPragma(conf, n) -proc strArg*(n: PNode, name: string, pos: int, default: string): string = - var x = getArg(n, name, pos) +proc strArg*(conf: ConfigRef; n: PNode, name: string, pos: int, default: string): string = + var x = getArg(conf, n, name, pos) if x == nil: result = default elif x.kind in {nkStrLit..nkTripleStrLit}: result = x.strVal - else: invalidPragma(n) + else: invalidPragma(conf, n) -proc boolArg*(n: PNode, name: string, pos: int, default: bool): bool = - var x = getArg(n, name, pos) +proc boolArg*(conf: ConfigRef; n: PNode, name: string, pos: int, default: bool): bool = + var x = getArg(conf, n, name, pos) if x == nil: result = default elif x.kind == nkIdent and cmpIgnoreStyle(x.ident.s, "true") == 0: result = true elif x.kind == nkIdent and cmpIgnoreStyle(x.ident.s, "false") == 0: result = false - else: invalidPragma(n) + else: invalidPragma(conf, n) -proc filterStrip*(stdin: PLLStream, filename: string, call: PNode): PLLStream = - var pattern = strArg(call, "startswith", 1, "") - var leading = boolArg(call, "leading", 2, true) - var trailing = boolArg(call, "trailing", 3, true) +proc filterStrip*(conf: ConfigRef; stdin: PLLStream, filename: string, call: PNode): PLLStream = + var pattern = strArg(conf, call, "startswith", 1, "") + var leading = boolArg(conf, call, "leading", 2, true) + var trailing = boolArg(conf, call, "trailing", 3, true) result = llStreamOpen("") var line = newStringOfCap(80) while llStreamReadLine(stdin, line): @@ -60,10 +61,10 @@ proc filterStrip*(stdin: PLLStream, filename: string, call: PNode): PLLStream = llStreamWriteln(result, line) llStreamClose(stdin) -proc filterReplace*(stdin: PLLStream, filename: string, call: PNode): PLLStream = - var sub = strArg(call, "sub", 1, "") - if len(sub) == 0: invalidPragma(call) - var by = strArg(call, "by", 2, "") +proc filterReplace*(conf: ConfigRef; stdin: PLLStream, filename: string, call: PNode): PLLStream = + var sub = strArg(conf, call, "sub", 1, "") + if len(sub) == 0: invalidPragma(conf, call) + var by = strArg(conf, call, "by", 2, "") result = llStreamOpen("") var line = newStringOfCap(80) while llStreamReadLine(stdin, line): diff --git a/compiler/gorgeimpl.nim b/compiler/gorgeimpl.nim index 80302b4b5..44ad46136 100644 --- a/compiler/gorgeimpl.nim +++ b/compiler/gorgeimpl.nim @@ -9,7 +9,8 @@ ## Module that implements ``gorge`` for the compiler. -import msgs, std / sha1, os, osproc, streams, strutils, options +import msgs, std / sha1, os, osproc, streams, strutils, options, + lineinfos proc readOutput(p: Process): (string, int) = result[0] = "" @@ -21,11 +22,11 @@ proc readOutput(p: Process): (string, int) = result[0].setLen(result[0].len - "\n".len) result[1] = p.waitForExit -proc opGorge*(cmd, input, cache: string, info: TLineInfo): (string, int) = - let workingDir = parentDir(info.toFullPath) +proc opGorge*(cmd, input, cache: string, info: TLineInfo; conf: ConfigRef): (string, int) = + let workingDir = parentDir(toFullPath(conf, info)) if cache.len > 0:# and optForceFullMake notin gGlobalOptions: let h = secureHash(cmd & "\t" & input & "\t" & cache) - let filename = options.toGeneratedFile("gorge_" & $h, "txt") + let filename = options.toGeneratedFile(conf, "gorge_" & $h, "txt") var f: File if open(f, filename): result = (f.readAll, 0) diff --git a/compiler/guards.nim b/compiler/guards.nim index a5e6058c9..99bb51fce 100644 --- a/compiler/guards.nim +++ b/compiler/guards.nim @@ -10,7 +10,7 @@ ## This module implements the 'implies' relation for guards. import ast, astalgo, msgs, magicsys, nimsets, trees, types, renderer, idents, - saturate + saturate, modulegraphs, options, lineinfos const someEq = {mEqI, mEqF64, mEqEnum, mEqCh, mEqB, mEqRef, mEqProc, @@ -83,18 +83,25 @@ proc isLetLocation(m: PNode, isApprox: bool): bool = proc interestingCaseExpr*(m: PNode): bool = isLetLocation(m, true) -let - opLe = createMagic("<=", mLeI) - opLt = createMagic("<", mLtI) - opAnd = createMagic("and", mAnd) - opOr = createMagic("or", mOr) - opIsNil = createMagic("isnil", mIsNil) - opEq = createMagic("==", mEqI) - opAdd = createMagic("+", mAddI) - opSub = createMagic("-", mSubI) - opMul = createMagic("*", mMulI) - opDiv = createMagic("div", mDivI) - opLen = createMagic("len", mLengthSeq) +type + Operators* = object + opNot, opContains, opLe, opLt, opAnd, opOr, opIsNil, opEq: PSym + opAdd, opSub, opMul, opDiv, opLen: PSym + +proc initOperators*(g: ModuleGraph): Operators = + result.opLe = createMagic(g, "<=", mLeI) + result.opLt = createMagic(g, "<", mLtI) + result.opAnd = createMagic(g, "and", mAnd) + result.opOr = createMagic(g, "or", mOr) + result.opIsNil = createMagic(g, "isnil", mIsNil) + result.opEq = createMagic(g, "==", mEqI) + result.opAdd = createMagic(g, "+", mAddI) + result.opSub = createMagic(g, "-", mSubI) + result.opMul = createMagic(g, "*", mMulI) + result.opDiv = createMagic(g, "div", mDivI) + result.opLen = createMagic(g, "len", mLengthSeq) + result.opNot = createMagic(g, "not", mNot) + result.opContains = createMagic(g, "contains", mInSet) proc swapArgs(fact: PNode, newOp: PSym): PNode = result = newNodeI(nkCall, fact.info, 3) @@ -102,16 +109,16 @@ proc swapArgs(fact: PNode, newOp: PSym): PNode = result.sons[1] = fact.sons[2] result.sons[2] = fact.sons[1] -proc neg(n: PNode): PNode = +proc neg(n: PNode; o: Operators): PNode = if n == nil: return nil case n.getMagic of mNot: result = n.sons[1] of someLt: # not (a < b) == a >= b == b <= a - result = swapArgs(n, opLe) + result = swapArgs(n, o.opLe) of someLe: - result = swapArgs(n, opLt) + result = swapArgs(n, o.opLt) of mInSet: if n.sons[1].kind != nkCurly: return nil let t = n.sons[2].typ.skipTypes(abstractInst) @@ -124,8 +131,8 @@ proc neg(n: PNode): PNode = let eAsNode = newIntNode(nkIntLit, e.sym.position) if not inSet(n.sons[1], eAsNode): s.add eAsNode result.sons[1] = s - elif t.kind notin {tyString, tySequence} and lengthOrd(t) < 1000: - result.sons[1] = complement(n.sons[1]) + #elif t.kind notin {tyString, tySequence} and lengthOrd(t) < 1000: + # result.sons[1] = complement(n.sons[1]) else: # not ({2, 3, 4}.contains(x)) x != 2 and x != 3 and x != 4 # XXX todo @@ -133,11 +140,11 @@ proc neg(n: PNode): PNode = of mOr: # not (a or b) --> not a and not b let - a = n.sons[1].neg - b = n.sons[2].neg + a = n.sons[1].neg(o) + b = n.sons[2].neg(o) if a != nil and b != nil: result = newNodeI(nkCall, n.info, 3) - result.sons[0] = newSymNode(opAnd) + result.sons[0] = newSymNode(o.opAnd) result.sons[1] = a result.sons[2] = b elif a != nil: @@ -147,7 +154,7 @@ proc neg(n: PNode): PNode = else: # leave not (a == 4) as it is result = newNodeI(nkCall, n.info, 2) - result.sons[0] = newSymNode(opNot) + result.sons[0] = newSymNode(o.opNot) result.sons[1] = n proc buildCall(op: PSym; a: PNode): PNode = @@ -181,7 +188,7 @@ proc `|div|`(a, b: PNode): PNode = if a.kind in {nkCharLit..nkUInt64Lit}: result.intVal = a.intVal div b.intVal else: result.floatVal = a.floatVal / b.floatVal -proc negate(a, b, res: PNode): PNode = +proc negate(a, b, res: PNode; o: Operators): PNode = if b.kind in {nkCharLit..nkUInt64Lit} and b.intVal != low(BiggestInt): var b = copyNode(b) b.intVal = -b.intVal @@ -189,11 +196,11 @@ proc negate(a, b, res: PNode): PNode = b.intVal = b.intVal |+| a.intVal result = b else: - result = buildCall(opAdd, a, b) + result = buildCall(o.opAdd, a, b) elif b.kind in {nkFloatLit..nkFloat64Lit}: var b = copyNode(b) b.floatVal = -b.floatVal - result = buildCall(opAdd, a, b) + result = buildCall(o.opAdd, a, b) else: result = res @@ -201,35 +208,35 @@ proc zero(): PNode = nkIntLit.newIntNode(0) proc one(): PNode = nkIntLit.newIntNode(1) proc minusOne(): PNode = nkIntLit.newIntNode(-1) -proc lowBound*(x: PNode): PNode = - result = nkIntLit.newIntNode(firstOrd(x.typ)) +proc lowBound*(conf: ConfigRef; x: PNode): PNode = + result = nkIntLit.newIntNode(firstOrd(conf, x.typ)) result.info = x.info -proc highBound*(x: PNode): PNode = +proc highBound*(conf: ConfigRef; x: PNode; o: Operators): PNode = let typ = x.typ.skipTypes(abstractInst) result = if typ.kind == tyArray: - nkIntLit.newIntNode(lastOrd(typ)) + nkIntLit.newIntNode(lastOrd(conf, typ)) elif typ.kind == tySequence and x.kind == nkSym and x.sym.kind == skConst: nkIntLit.newIntNode(x.sym.ast.len-1) else: - opAdd.buildCall(opLen.buildCall(x), minusOne()) + o.opAdd.buildCall(o.opLen.buildCall(x), minusOne()) result.info = x.info -proc reassociation(n: PNode): PNode = +proc reassociation(n: PNode; o: Operators): PNode = result = n # (foo+5)+5 --> foo+10; same for '*' case result.getMagic of someAdd: if result[2].isValue and result[1].getMagic in someAdd and result[1][2].isValue: - result = opAdd.buildCall(result[1][1], result[1][2] |+| result[2]) + result = o.opAdd.buildCall(result[1][1], result[1][2] |+| result[2]) if result[2].intVal == 0: result = result[1] of someMul: if result[2].isValue and result[1].getMagic in someMul and result[1][2].isValue: - result = opMul.buildCall(result[1][1], result[1][2] |*| result[2]) + result = o.opMul.buildCall(result[1][1], result[1][2] |*| result[2]) if result[2].intVal == 1: result = result[1] elif result[2].intVal == 0: @@ -243,12 +250,12 @@ proc pred(n: PNode): PNode = else: result = n -proc canon*(n: PNode): PNode = +proc canon*(n: PNode; o: Operators): PNode = # XXX for now only the new code in 'semparallel' uses this if n.safeLen >= 1: result = shallowCopy(n) for i in 0 ..< n.len: - result.sons[i] = canon(n.sons[i]) + result.sons[i] = canon(n.sons[i], o) elif n.kind == nkSym and n.sym.kind == skLet and n.sym.ast.getMagic in (someEq + someAdd + someMul + someMin + someMax + someHigh + {mUnaryLt} + someSub + someLen + someDiv): @@ -263,24 +270,24 @@ proc canon*(n: PNode): PNode = # (4 + foo) + 2 --> (foo + 4) + 2 of someHigh: # high == len+(-1) - result = opAdd.buildCall(opLen.buildCall(result[1]), minusOne()) + result = o.opAdd.buildCall(o.opLen.buildCall(result[1]), minusOne()) of mUnaryLt: - result = buildCall(opAdd, result[1], minusOne()) + result = buildCall(o.opAdd, result[1], minusOne()) of someSub: # x - 4 --> x + (-4) - result = negate(result[1], result[2], result) + result = negate(result[1], result[2], result, o) of someLen: - result.sons[0] = opLen.newSymNode + result.sons[0] = o.opLen.newSymNode of someLt: # x < y same as x <= y-1: - let y = n[2].canon + let y = n[2].canon(o) let p = pred(y) - let minus = if p != y: p else: opAdd.buildCall(y, minusOne()).canon - result = opLe.buildCall(n[1].canon, minus) + let minus = if p != y: p else: o.opAdd.buildCall(y, minusOne()).canon(o) + result = o.opLe.buildCall(n[1].canon(o), minus) else: discard result = skipConv(result) - result = reassociation(result) + result = reassociation(result, o) # most important rule: (x-4) <= a.len --> x <= a.len+4 case result.getMagic of someLe: @@ -291,10 +298,10 @@ proc canon*(n: PNode): PNode = case x.getMagic of someSub: result = buildCall(result[0].sym, x[1], - reassociation(opAdd.buildCall(y, x[2]))) + reassociation(o.opAdd.buildCall(y, x[2]), o)) of someAdd: # Rule A: - let plus = negate(y, x[2], nil).reassociation + let plus = negate(y, x[2], nil, o).reassociation(o) if plus != nil: result = buildCall(result[0].sym, x[1], plus) else: discard elif y.kind in nkCallKinds and y.len == 3 and y[2].isValue and @@ -303,9 +310,9 @@ proc canon*(n: PNode): PNode = case y.getMagic of someSub: result = buildCall(result[0].sym, y[1], - reassociation(opAdd.buildCall(x, y[2]))) + reassociation(o.opAdd.buildCall(x, y[2]), o)) of someAdd: - let plus = negate(x, y[2], nil).reassociation + let plus = negate(x, y[2], nil, o).reassociation(o) # ensure that Rule A will not trigger afterwards with the # additional 'not isLetLocation' constraint: if plus != nil and not isLetLocation(x, true): @@ -323,15 +330,15 @@ proc canon*(n: PNode): PNode = result.sons[2] = y[1] else: discard -proc `+@`*(a: PNode; b: BiggestInt): PNode = - canon(if b != 0: opAdd.buildCall(a, nkIntLit.newIntNode(b)) else: a) +proc buildAdd*(a: PNode; b: BiggestInt; o: Operators): PNode = + canon(if b != 0: o.opAdd.buildCall(a, nkIntLit.newIntNode(b)) else: a, o) -proc usefulFact(n: PNode): PNode = +proc usefulFact(n: PNode; o: Operators): PNode = case n.getMagic of someEq: if skipConv(n.sons[2]).kind == nkNilLit and ( isLetLocation(n.sons[1], false) or isVar(n.sons[1])): - result = opIsNil.buildCall(n.sons[1]) + result = o.opIsNil.buildCall(n.sons[1]) else: if isLetLocation(n.sons[1], true) or isLetLocation(n.sons[2], true): # XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1' @@ -351,11 +358,11 @@ proc usefulFact(n: PNode): PNode = result = n of mAnd: let - a = usefulFact(n.sons[1]) - b = usefulFact(n.sons[2]) + a = usefulFact(n.sons[1], o) + b = usefulFact(n.sons[2], o) if a != nil and b != nil: result = newNodeI(nkCall, n.info, 3) - result.sons[0] = newSymNode(opAnd) + result.sons[0] = newSymNode(o.opAnd) result.sons[1] = a result.sons[2] = b elif a != nil: @@ -363,9 +370,9 @@ proc usefulFact(n: PNode): PNode = elif b != nil: result = b of mNot: - let a = usefulFact(n.sons[1]) + let a = usefulFact(n.sons[1], o) if a != nil: - result = a.neg + result = a.neg(o) of mOr: # 'or' sucks! (p.isNil or q.isNil) --> hard to do anything # with that knowledge... @@ -374,14 +381,14 @@ proc usefulFact(n: PNode): PNode = # (x == 3) or (y == 2) ---> not ( not (x==3) and not (y == 2)) # not (x != 3 and y != 2) let - a = usefulFact(n.sons[1]).neg - b = usefulFact(n.sons[2]).neg + a = usefulFact(n.sons[1], o).neg(o) + b = usefulFact(n.sons[2], o).neg(o) if a != nil and b != nil: result = newNodeI(nkCall, n.info, 3) - result.sons[0] = newSymNode(opAnd) + result.sons[0] = newSymNode(o.opAnd) result.sons[1] = a result.sons[2] = b - result = result.neg + result = result.neg(o) elif n.kind == nkSym and n.sym.kind == skLet: # consider: # let a = 2 < x @@ -389,32 +396,34 @@ proc usefulFact(n: PNode): PNode = # ... # We make can easily replace 'a' by '2 < x' here: if n.sym.ast != nil: - result = usefulFact(n.sym.ast) + result = usefulFact(n.sym.ast, o) elif n.kind == nkStmtListExpr: - result = usefulFact(n.lastSon) + result = usefulFact(n.lastSon, o) type - TModel* = seq[PNode] # the "knowledge base" + TModel* = object + s*: seq[PNode] # the "knowledge base" + o*: Operators proc addFact*(m: var TModel, nn: PNode) = - let n = usefulFact(nn) - if n != nil: m.add n + let n = usefulFact(nn, m.o) + if n != nil: m.s.add n proc addFactNeg*(m: var TModel, n: PNode) = - let n = n.neg + let n = n.neg(m.o) if n != nil: addFact(m, n) -proc canonOpr(opr: PSym): PSym = - case opr.magic - of someEq: result = opEq - of someLe: result = opLe - of someLt: result = opLt - of someLen: result = opLen - of someAdd: result = opAdd - of someSub: result = opSub - of someMul: result = opMul - of someDiv: result = opDiv - else: result = opr +proc sameOpr(a, b: PSym): bool = + case a.magic + of someEq: result = b.magic in someEq + of someLe: result = b.magic in someLe + of someLt: result = b.magic in someLt + of someLen: result = b.magic in someLen + of someAdd: result = b.magic in someAdd + of someSub: result = b.magic in someSub + of someMul: result = b.magic in someMul + of someDiv: result = b.magic in someDiv + else: result = a == b proc sameTree*(a, b: PNode): bool = result = false @@ -425,7 +434,7 @@ proc sameTree*(a, b: PNode): bool = of nkSym: result = a.sym == b.sym if not result and a.sym.magic != mNone: - result = a.sym.magic == b.sym.magic or canonOpr(a.sym) == canonOpr(b.sym) + result = a.sym.magic == b.sym.magic or sameOpr(a.sym, b.sym) of nkIdent: result = a.ident.id == b.ident.id of nkCharLit..nkInt64Lit: result = a.intVal == b.intVal of nkFloatLit..nkFloat64Lit: result = a.floatVal == b.floatVal @@ -462,8 +471,8 @@ proc invalidateFacts*(m: var TModel, n: PNode) = # The same mechanism could be used for more complex data stored on the heap; # procs that 'write: []' cannot invalidate 'n.kind' for instance. In fact, we # could CSE these expressions then and help C's optimizer. - for i in 0..high(m): - if m[i] != nil and m[i].hasSubTree(n): m[i] = nil + for i in 0..high(m.s): + if m.s[i] != nil and m.s[i].hasSubTree(n): m.s[i] = nil proc valuesUnequal(a, b: PNode): bool = if a.isValue and b.isValue: @@ -486,7 +495,7 @@ proc impliesEq(fact, eq: PNode): TImplication = if sameTree(fact.sons[2], eq.sons[loc]) and isValue(eq.sons[val]): if inSet(fact.sons[1], eq.sons[val]): result = impYes else: result = impNo - of mNot, mOr, mAnd: internalError(eq.info, "impliesEq") + of mNot, mOr, mAnd: assert(false, "impliesEq") else: discard proc leImpliesIn(x, c, aSet: PNode): TImplication = @@ -494,7 +503,7 @@ proc leImpliesIn(x, c, aSet: PNode): TImplication = # fact: x <= 4; question x in {56}? # --> true if every value <= 4 is in the set {56} # - var value = newIntNode(c.kind, firstOrd(x.typ)) + var value = newIntNode(c.kind, firstOrd(nil, x.typ)) # don't iterate too often: if c.intVal - value.intVal < 1000: var i, pos, neg: int @@ -511,7 +520,7 @@ proc geImpliesIn(x, c, aSet: PNode): TImplication = # --> true iff every value >= 4 is in the set {56} # var value = newIntNode(c.kind, c.intVal) - let max = lastOrd(x.typ) + let max = lastOrd(nil, x.typ) # don't iterate too often: if max - value.intVal < 1000: var i, pos, neg: int @@ -523,8 +532,8 @@ proc geImpliesIn(x, c, aSet: PNode): TImplication = elif neg == i: result = impNo proc compareSets(a, b: PNode): TImplication = - if equalSets(a, b): result = impYes - elif intersectSets(a, b).len == 0: result = impNo + if equalSets(nil, a, b): result = impYes + elif intersectSets(nil, a, b).len == 0: result = impNo proc impliesIn(fact, loc, aSet: PNode): TImplication = case fact.sons[0].sym.magic @@ -549,7 +558,7 @@ proc impliesIn(fact, loc, aSet: PNode): TImplication = elif sameTree(fact.sons[2], loc): # 4 < x --> 3 <= x result = geImpliesIn(fact.sons[2], fact.sons[1].pred, aSet) - of mNot, mOr, mAnd: internalError(loc.info, "impliesIn") + of mNot, mOr, mAnd: assert(false, "impliesIn") else: discard proc valueIsNil(n: PNode): TImplication = @@ -567,11 +576,11 @@ proc impliesIsNil(fact, eq: PNode): TImplication = result = valueIsNil(fact.sons[2].skipConv) elif sameTree(fact.sons[2], eq.sons[1]): result = valueIsNil(fact.sons[1].skipConv) - of mNot, mOr, mAnd: internalError(eq.info, "impliesIsNil") + of mNot, mOr, mAnd: assert(false, "impliesIsNil") else: discard proc impliesGe(fact, x, c: PNode): TImplication = - internalAssert isLocation(x) + assert isLocation(x) case fact.sons[0].sym.magic of someEq: if sameTree(fact.sons[1], x): @@ -603,7 +612,7 @@ proc impliesGe(fact, x, c: PNode): TImplication = # fact: 3 <= x; question: x >= 2 ? --> true iff 2 <= 3 if isValue(fact.sons[1]) and isValue(c): if leValue(c, fact.sons[1]): result = impYes - of mNot, mOr, mAnd: internalError(x.info, "impliesGe") + of mNot, mOr, mAnd: assert(false, "impliesGe") else: discard proc impliesLe(fact, x, c: PNode): TImplication = @@ -643,7 +652,7 @@ proc impliesLe(fact, x, c: PNode): TImplication = if isValue(fact.sons[1]) and isValue(c): if leValue(c, fact.sons[1].pred): result = impNo - of mNot, mOr, mAnd: internalError(x.info, "impliesLe") + of mNot, mOr, mAnd: assert(false, "impliesLe") else: discard proc impliesLt(fact, x, c: PNode): TImplication = @@ -707,14 +716,14 @@ proc factImplies(fact, prop: PNode): TImplication = proc doesImply*(facts: TModel, prop: PNode): TImplication = assert prop.kind in nkCallKinds - for f in facts: + for f in facts.s: # facts can be invalidated, in which case they are 'nil': if not f.isNil: result = f.factImplies(prop) if result != impUnknown: return -proc impliesNotNil*(facts: TModel, arg: PNode): TImplication = - result = doesImply(facts, opIsNil.buildCall(arg).neg) +proc impliesNotNil*(m: TModel, arg: PNode): TImplication = + result = doesImply(m, m.o.opIsNil.buildCall(arg).neg(m.o)) proc simpleSlice*(a, b: PNode): BiggestInt = # returns 'c' if a..b matches (i+c)..(i+c), -1 otherwise. (i)..(i) is matched @@ -768,8 +777,10 @@ macro `=~`(x: PNode, pat: untyped): bool = var conds = newTree(nnkBracket) m(x, pat, conds) - when declared(macros.toNimIdent): - result = nestList(toNimIdent"and", conds) + when compiles(nestList(ident"and", conds)): + result = nestList(ident"and", conds) + #elif declared(macros.toNimIdent): + # result = nestList(toNimIdent"and", conds) else: result = nestList(!"and", conds) @@ -788,10 +799,10 @@ proc ple(m: TModel; a, b: PNode): TImplication = # use type information too: x <= 4 iff high(x) <= 4 if b.isValue and a.typ != nil and a.typ.isOrdinalType: - if lastOrd(a.typ) <= b.intVal: return impYes + if lastOrd(nil, a.typ) <= b.intVal: return impYes # 3 <= x iff low(x) <= 3 if a.isValue and b.typ != nil and b.typ.isOrdinalType: - if firstOrd(b.typ) <= a.intVal: return impYes + if firstOrd(nil, b.typ) <= a.intVal: return impYes # x <= x if sameTree(a, b): return impYes @@ -815,20 +826,20 @@ proc ple(m: TModel; a, b: PNode): TImplication = if a.getMagic in someMul and a[2].isValue and a[1].getMagic in someDiv and a[1][2].isValue: # simplify (x div 4) * 2 <= y to x div (c div d) <= y - if ple(m, buildCall(opDiv, a[1][1], `|div|`(a[1][2], a[2])), b) == impYes: + if ple(m, buildCall(m.o.opDiv, a[1][1], `|div|`(a[1][2], a[2])), b) == impYes: return impYes # x*3 + x == x*4. It follows that: # x*3 + y <= x*4 if y <= x and 3 <= 4 if a =~ x*dc + y and b =~ x2*ec: if sameTree(x, x2): - let ec1 = opAdd.buildCall(ec, minusOne()) + let ec1 = m.o.opAdd.buildCall(ec, minusOne()) if x >=? 1 and ec >=? 1 and dc >=? 1 and dc <=? ec1 and y <=? x: return impYes elif a =~ x*dc and b =~ x2*ec + y: #echo "BUG cam ehrer e ", a, " <=? ", b if sameTree(x, x2): - let ec1 = opAdd.buildCall(ec, minusOne()) + let ec1 = m.o.opAdd.buildCall(ec, minusOne()) if x >=? 1 and ec >=? 1 and dc >=? 1 and dc <=? ec1 and y <=? zero(): return impYes @@ -861,9 +872,9 @@ proc ple(m: TModel; a, b: PNode): TImplication = # use the knowledge base: return pleViaModel(m, a, b) - #return doesImply(m, opLe.buildCall(a, b)) + #return doesImply(m, o.opLe.buildCall(a, b)) -type TReplacements = seq[tuple[a,b: PNode]] +type TReplacements = seq[tuple[a, b: PNode]] proc replaceSubTree(n, x, by: PNode): PNode = if sameTree(n, x): @@ -881,11 +892,11 @@ proc applyReplacements(n: PNode; rep: TReplacements): PNode = proc pleViaModelRec(m: var TModel; a, b: PNode): TImplication = # now check for inferrable facts: a <= b and b <= c implies a <= c - for i in 0..m.high: - let fact = m[i] + for i in 0..m.s.high: + let fact = m.s[i] if fact != nil and fact.getMagic in someLe: # mark as used: - m[i] = nil + m.s[i] = nil # i <= len-100 # i <=? len-1 # --> true if (len-100) <= (len-1) @@ -917,7 +928,7 @@ proc pleViaModelRec(m: var TModel; a, b: PNode): TImplication = proc pleViaModel(model: TModel; aa, bb: PNode): TImplication = # compute replacements: var replacements: TReplacements = @[] - for fact in model: + for fact in model.s: if fact != nil and fact.getMagic in someEq: let a = fact[1] let b = fact[2] @@ -927,12 +938,13 @@ proc pleViaModel(model: TModel; aa, bb: PNode): TImplication = var a = aa var b = bb if replacements.len > 0: - m = @[] + m.s = @[] + m.o = model.o # make the other facts consistent: - for fact in model: + for fact in model.s: if fact != nil and fact.getMagic notin someEq: # XXX 'canon' should not be necessary here, but it is - m.add applyReplacements(fact, replacements).canon + m.s.add applyReplacements(fact, replacements).canon(m.o) a = applyReplacements(aa, replacements) b = applyReplacements(bb, replacements) else: @@ -941,31 +953,31 @@ proc pleViaModel(model: TModel; aa, bb: PNode): TImplication = result = pleViaModelRec(m, a, b) proc proveLe*(m: TModel; a, b: PNode): TImplication = - let x = canon(opLe.buildCall(a, b)) + let x = canon(m.o.opLe.buildCall(a, b), m.o) #echo "ROOT ", renderTree(x[1]), " <=? ", renderTree(x[2]) result = ple(m, x[1], x[2]) if result == impUnknown: # try an alternative: a <= b iff not (b < a) iff not (b+1 <= a): - let y = canon(opLe.buildCall(opAdd.buildCall(b, one()), a)) + let y = canon(m.o.opLe.buildCall(m.o.opAdd.buildCall(b, one()), a), m.o) result = ~ple(m, y[1], y[2]) proc addFactLe*(m: var TModel; a, b: PNode) = - m.add canon(opLe.buildCall(a, b)) + m.s.add canon(m.o.opLe.buildCall(a, b), m.o) proc settype(n: PNode): PType = result = newType(tySet, n.typ.owner) addSonSkipIntLit(result, n.typ) -proc buildOf(it, loc: PNode): PNode = +proc buildOf(it, loc: PNode; o: Operators): PNode = var s = newNodeI(nkCurly, it.info, it.len-1) s.typ = settype(loc) for i in 0..it.len-2: s.sons[i] = it.sons[i] result = newNodeI(nkCall, it.info, 3) - result.sons[0] = newSymNode(opContains) + result.sons[0] = newSymNode(o.opContains) result.sons[1] = s result.sons[2] = loc -proc buildElse(n: PNode): PNode = +proc buildElse(n: PNode; o: Operators): PNode = var s = newNodeIT(nkCurly, n.info, settype(n.sons[0])) for i in 1..n.len-2: let branch = n.sons[i] @@ -973,23 +985,23 @@ proc buildElse(n: PNode): PNode = for j in 0..branch.len-2: s.add(branch.sons[j]) result = newNodeI(nkCall, n.info, 3) - result.sons[0] = newSymNode(opContains) + result.sons[0] = newSymNode(o.opContains) result.sons[1] = s result.sons[2] = n.sons[0] proc addDiscriminantFact*(m: var TModel, n: PNode) = var fact = newNodeI(nkCall, n.info, 3) - fact.sons[0] = newSymNode(opEq) + fact.sons[0] = newSymNode(m.o.opEq) fact.sons[1] = n.sons[0] fact.sons[2] = n.sons[1] - m.add fact + m.s.add fact proc addAsgnFact*(m: var TModel, key, value: PNode) = var fact = newNodeI(nkCall, key.info, 3) - fact.sons[0] = newSymNode(opEq) + fact.sons[0] = newSymNode(m.o.opEq) fact.sons[1] = key fact.sons[2] = value - m.add fact + m.s.add fact proc sameSubexprs*(m: TModel; a, b: PNode): bool = # This should be used to check whether two *path expressions* refer to the @@ -1002,7 +1014,7 @@ proc sameSubexprs*(m: TModel; a, b: PNode): bool = # However, nil checking requires exactly the same mechanism! But for now # we simply use sameTree and live with the unsoundness of the analysis. var check = newNodeI(nkCall, a.info, 3) - check.sons[0] = newSymNode(opEq) + check.sons[0] = newSymNode(m.o.opEq) check.sons[1] = a check.sons[2] = b result = m.doesImply(check) == impYes @@ -1010,11 +1022,11 @@ proc sameSubexprs*(m: TModel; a, b: PNode): bool = proc addCaseBranchFacts*(m: var TModel, n: PNode, i: int) = let branch = n.sons[i] if branch.kind == nkOfBranch: - m.add buildOf(branch, n.sons[0]) + m.s.add buildOf(branch, n.sons[0], m.o) else: - m.add n.buildElse.neg + m.s.add n.buildElse(m.o).neg(m.o) -proc buildProperFieldCheck(access, check: PNode): PNode = +proc buildProperFieldCheck(access, check: PNode; o: Operators): PNode = if check.sons[1].kind == nkCurly: result = copyTree(check) if access.kind == nkDotExpr: @@ -1026,10 +1038,10 @@ proc buildProperFieldCheck(access, check: PNode): PNode = else: # it is some 'not' assert check.getMagic == mNot - result = buildProperFieldCheck(access, check.sons[1]).neg + result = buildProperFieldCheck(access, check.sons[1], o).neg(o) -proc checkFieldAccess*(m: TModel, n: PNode) = +proc checkFieldAccess*(m: TModel, n: PNode; conf: ConfigRef) = for i in 1..n.len-1: - let check = buildProperFieldCheck(n.sons[0], n.sons[i]) + let check = buildProperFieldCheck(n.sons[0], n.sons[i], m.o) if check != nil and m.doesImply(check) != impYes: - message(n.info, warnProveField, renderTree(n.sons[0])); break + message(conf, n.info, warnProveField, renderTree(n.sons[0])); break diff --git a/compiler/hlo.nim b/compiler/hlo.nim index 2bffaa173..bbbcb4e56 100644 --- a/compiler/hlo.nim +++ b/compiler/hlo.nim @@ -12,12 +12,12 @@ proc hlo(c: PContext, n: PNode): PNode proc evalPattern(c: PContext, n, orig: PNode): PNode = - internalAssert n.kind == nkCall and n.sons[0].kind == nkSym + internalAssert c.config, n.kind == nkCall and n.sons[0].kind == nkSym # we need to ensure that the resulting AST is semchecked. However, it's # aweful to semcheck before macro invocation, so we don't and treat # templates and macros as immediate in this context. var rule: string - if optHints in gOptions and hintPattern in gNotes: + if optHints in c.config.options and hintPattern in c.config.notes: rule = renderTree(n, {renderNoComments}) let s = n.sons[0].sym case s.kind @@ -27,8 +27,8 @@ proc evalPattern(c: PContext, n, orig: PNode): PNode = result = semTemplateExpr(c, n, s, {efFromHlo}) else: result = semDirectOp(c, n, {}) - if optHints in gOptions and hintPattern in gNotes: - message(orig.info, hintPattern, rule & " --> '" & + if optHints in c.config.options and hintPattern in c.config.notes: + message(c.config, orig.info, hintPattern, rule & " --> '" & renderTree(result, {renderNoComments}) & "'") proc applyPatterns(c: PContext, n: PNode): PNode = @@ -43,9 +43,9 @@ proc applyPatterns(c: PContext, n: PNode): PNode = if not isNil(x): assert x.kind in {nkStmtList, nkCall} # better be safe than sorry, so check evalTemplateCounter too: - inc(evalTemplateCounter) - if evalTemplateCounter > 100: - globalError(n.info, errTemplateInstantiationTooNested) + inc(c.config.evalTemplateCounter) + if c.config.evalTemplateCounter > evalTemplateLimit: + globalError(c.config, n.info, "template instantiation too nested") # deactivate this pattern: c.patterns[i] = nil if x.kind == nkStmtList: @@ -54,7 +54,7 @@ proc applyPatterns(c: PContext, n: PNode): PNode = result = flattenStmts(x) else: result = evalPattern(c, x, result) - dec(evalTemplateCounter) + dec(c.config.evalTemplateCounter) # activate this pattern again: c.patterns[i] = pattern @@ -86,18 +86,18 @@ proc hlo(c: PContext, n: PNode): PNode = else: result = fitNode(c, n.typ, result, n.info) # optimization has been applied so check again: - result = commonOptimizations(c.module, result) + result = commonOptimizations(c.graph, c.module, result) result = hlo(c, result) - result = commonOptimizations(c.module, result) + result = commonOptimizations(c.graph, c.module, result) proc hloBody(c: PContext, n: PNode): PNode = # fast exit: - if c.patterns.len == 0 or optPatterns notin gOptions: return n + if c.patterns.len == 0 or optPatterns notin c.config.options: return n c.hloLoopDetector = 0 result = hlo(c, n) proc hloStmt(c: PContext, n: PNode): PNode = # fast exit: - if c.patterns.len == 0 or optPatterns notin gOptions: return n + if c.patterns.len == 0 or optPatterns notin c.config.options: return n c.hloLoopDetector = 0 result = hlo(c, n) diff --git a/compiler/idents.nim b/compiler/idents.nim index 2cce4710e..58800b73d 100644 --- a/compiler/idents.nim +++ b/compiler/idents.nim @@ -30,14 +30,9 @@ type wordCounter: int idAnon*, idDelegator*, emptyIdent*: PIdent -var - legacy: IdentCache +proc resetIdentCache*() = discard -proc resetIdentCache*() = - for i in low(legacy.buckets)..high(legacy.buckets): - legacy.buckets[i] = nil - -proc cmpIgnoreStyle(a, b: cstring, blen: int): int = +proc cmpIgnoreStyle*(a, b: cstring, blen: int): int = if a[0] != b[0]: return 1 var i = 0 var j = 0 @@ -71,11 +66,9 @@ proc cmpExact(a, b: cstring, blen: int): int = if result == 0: if a[i] != '\0': result = 1 -{.this: self.} - -proc getIdent*(self: IdentCache; identifier: cstring, length: int, h: Hash): PIdent = - var idx = h and high(buckets) - result = buckets[idx] +proc getIdent*(ic: IdentCache; identifier: cstring, length: int, h: Hash): PIdent = + var idx = h and high(ic.buckets) + result = ic.buckets[idx] var last: PIdent = nil var id = 0 while result != nil: @@ -83,8 +76,8 @@ proc getIdent*(self: IdentCache; identifier: cstring, length: int, h: Hash): PId if last != nil: # make access to last looked up identifier faster: last.next = result.next - result.next = buckets[idx] - buckets[idx] = result + result.next = ic.buckets[idx] + ic.buckets[idx] = result return elif cmpIgnoreStyle(cstring(result.s), identifier, length) == 0: assert((id == 0) or (id == result.id)) @@ -95,41 +88,31 @@ proc getIdent*(self: IdentCache; identifier: cstring, length: int, h: Hash): PId result.h = h result.s = newString(length) for i in countup(0, length - 1): result.s[i] = identifier[i] - result.next = buckets[idx] - buckets[idx] = result + result.next = ic.buckets[idx] + ic.buckets[idx] = result if id == 0: - inc(wordCounter) - result.id = -wordCounter + inc(ic.wordCounter) + result.id = -ic.wordCounter else: result.id = id -proc getIdent*(self: IdentCache; identifier: string): PIdent = - result = getIdent(cstring(identifier), len(identifier), +proc getIdent*(ic: IdentCache; identifier: string): PIdent = + result = getIdent(ic, cstring(identifier), len(identifier), hashIgnoreStyle(identifier)) -proc getIdent*(self: IdentCache; identifier: string, h: Hash): PIdent = - result = getIdent(cstring(identifier), len(identifier), h) +proc getIdent*(ic: IdentCache; identifier: string, h: Hash): PIdent = + result = getIdent(ic, cstring(identifier), len(identifier), h) proc newIdentCache*(): IdentCache = - if legacy.isNil: - result = IdentCache() - result.idAnon = result.getIdent":anonymous" - result.wordCounter = 1 - result.idDelegator = result.getIdent":delegator" - result.emptyIdent = result.getIdent("") - # initialize the keywords: - for s in countup(succ(low(specialWords)), high(specialWords)): - result.getIdent(specialWords[s], hashIgnoreStyle(specialWords[s])).id = ord(s) - legacy = result - else: - result = legacy + result = IdentCache() + result.idAnon = result.getIdent":anonymous" + result.wordCounter = 1 + result.idDelegator = result.getIdent":delegator" + result.emptyIdent = result.getIdent("") + # initialize the keywords: + for s in countup(succ(low(specialWords)), high(specialWords)): + result.getIdent(specialWords[s], hashIgnoreStyle(specialWords[s])).id = ord(s) proc whichKeyword*(id: PIdent): TSpecialWord = if id.id < 0: result = wInvalid else: result = TSpecialWord(id.id) - -proc getIdent*(identifier: string): PIdent = - ## for backwards compatibility. - if legacy.isNil: - discard newIdentCache() - legacy.getIdent identifier diff --git a/compiler/idgen.nim b/compiler/idgen.nim index c6b1a4d07..7d103ffd7 100644 --- a/compiler/idgen.nim +++ b/compiler/idgen.nim @@ -36,20 +36,20 @@ proc setId*(id: int) {.inline.} = proc idSynchronizationPoint*(idRange: int) = gFrontEndId = (gFrontEndId div idRange + 1) * idRange + 1 -proc toGid(f: string): string = +proc toGid(conf: ConfigRef; f: string): string = # we used to use ``f.addFileExt("gid")`` (aka ``$project.gid``), but this # will cause strange bugs if multiple projects are in the same folder, so # we simply use a project independent name: - result = options.completeGeneratedFilePath("nim.gid") + result = options.completeGeneratedFilePath(conf, "nim.gid") -proc saveMaxIds*(project: string) = - var f = open(project.toGid, fmWrite) +proc saveMaxIds*(conf: ConfigRef; project: string) = + var f = open(toGid(conf, project), fmWrite) f.writeLine($gFrontEndId) f.close() -proc loadMaxIds*(project: string) = +proc loadMaxIds*(conf: ConfigRef; project: string) = var f: File - if open(f, project.toGid, fmRead): + if open(f, toGid(conf, project), fmRead): var line = newStringOfCap(20) if f.readLine(line): var frontEndId = parseInt(line) diff --git a/compiler/importer.nim b/compiler/importer.nim index 3d7f62464..73d2e6599 100644 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -10,16 +10,29 @@ # This module implements the symbol importing mechanism. import - intsets, strutils, os, ast, astalgo, msgs, options, idents, rodread, lookups, - semdata, passes, renderer, modulepaths, sigmatch + intsets, strutils, os, ast, astalgo, msgs, options, idents, lookups, + semdata, passes, renderer, modulepaths, sigmatch, lineinfos proc evalImport*(c: PContext, n: PNode): PNode proc evalFrom*(c: PContext, n: PNode): PNode +proc readExceptSet*(c: PContext, n: PNode): IntSet = + assert n.kind in {nkImportExceptStmt, nkExportExceptStmt} + result = initIntSet() + for i in 1 ..< n.len: + let ident = lookups.considerQuotedIdent(c, n[i]) + result.incl(ident.id) + proc importPureEnumField*(c: PContext; s: PSym) = - var check = strTableGet(c.importTable.symbols, s.name) + let check = strTableGet(c.importTable.symbols, s.name) if check == nil: - strTableAdd(c.pureEnumFields, s) + let checkB = strTableGet(c.pureEnumFields, s.name) + if checkB == nil: + strTableAdd(c.pureEnumFields, s) + else: + # mark as ambigous: + incl(c.ambiguousSymbols, checkB.id) + incl(c.ambiguousSymbols, s.id) proc rawImportSymbol(c: PContext, s: PSym) = # This does not handle stubs, because otherwise loading on demand would be @@ -40,7 +53,7 @@ proc rawImportSymbol(c: PContext, s: PSym) = for j in countup(0, sonsLen(etyp.n) - 1): var e = etyp.n.sons[j].sym if e.kind != skEnumField: - internalError(s.info, "rawImportSymbol") + internalError(c.config, s.info, "rawImportSymbol") # BUGFIX: because of aliases for enums the symbol may already # have been put into the symbol table # BUGFIX: but only iff they are the same symbols! @@ -62,14 +75,15 @@ proc rawImportSymbol(c: PContext, s: PSym) = if hasPattern(s): addPattern(c, s) proc importSymbol(c: PContext, n: PNode, fromMod: PSym) = - let ident = lookups.considerQuotedIdent(n) + let ident = lookups.considerQuotedIdent(c, n) let s = strTableGet(fromMod.tab, ident) if s == nil: errorUndeclaredIdentifier(c, n.info, ident.s) else: - if s.kind == skStub: loadStub(s) + when false: + if s.kind == skStub: loadStub(s) if s.kind notin ExportableSymKinds: - internalError(n.info, "importSymbol: 2") + internalError(c.config, n.info, "importSymbol: 2") # for an enumeration we have to add all identifiers case s.kind of skProcKinds: @@ -77,7 +91,7 @@ proc importSymbol(c: PContext, n: PNode, fromMod: PSym) = var it: TIdentIter var e = initIdentIter(it, fromMod.tab, s.name) while e != nil: - if e.name.id != s.name.id: internalError(n.info, "importSymbol: 3") + if e.name.id != s.name.id: internalError(c.config, n.info, "importSymbol: 3") rawImportSymbol(c, e) e = nextIdentIter(it, fromMod.tab) else: rawImportSymbol(c, s) @@ -89,7 +103,7 @@ proc importAllSymbolsExcept(c: PContext, fromMod: PSym, exceptSet: IntSet) = if s.kind != skModule: if s.kind != skEnumField: if s.kind notin ExportableSymKinds: - internalError(s.info, "importAllSymbols: " & $s.kind) + internalError(c.config, s.info, "importAllSymbols: " & $s.kind) if exceptSet.isNil or s.name.id notin exceptSet: rawImportSymbol(c, s) s = nextIter(i, fromMod.tab) @@ -110,22 +124,23 @@ proc importForwarded(c: PContext, n: PNode, exceptSet: IntSet) = elif exceptSet.isNil or s.name.id notin exceptSet: rawImportSymbol(c, s) of nkExportExceptStmt: - localError(n.info, errGenerated, "'export except' not implemented") + localError(c.config, n.info, "'export except' not implemented") else: for i in 0..safeLen(n)-1: importForwarded(c, n.sons[i], exceptSet) -proc importModuleAs(n: PNode, realModule: PSym): PSym = +proc importModuleAs(c: PContext; n: PNode, realModule: PSym): PSym = result = realModule if n.kind != nkImportAs: discard elif n.len != 2 or n.sons[1].kind != nkIdent: - localError(n.info, errGenerated, "module alias must be an identifier") + localError(c.config, n.info, "module alias must be an identifier") elif n.sons[1].ident.id != realModule.name.id: # some misguided guy will write 'import abc.foo as foo' ... - result = createModuleAlias(realModule, n.sons[1].ident, realModule.info) + result = createModuleAlias(realModule, n.sons[1].ident, realModule.info, + c.config.options) -proc myImportModule(c: PContext, n: PNode): PSym = - var f = checkModuleName(n) +proc myImportModule(c: PContext, n: PNode; importStmtResult: PNode): PSym = + var f = checkModuleName(c.config, n) if f != InvalidFileIDX: let L = c.graph.importStack.len let recursion = c.graph.importStack.find(f) @@ -135,10 +150,10 @@ proc myImportModule(c: PContext, n: PNode): PSym = var err = "" for i in countup(recursion, L-1): if i > recursion: err.add "\n" - err.add toFullPath(c.graph.importStack[i]) & " imports " & - toFullPath(c.graph.importStack[i+1]) + err.add toFullPath(c.config, c.graph.importStack[i]) & " imports " & + toFullPath(c.config, c.graph.importStack[i+1]) c.recursiveDep = err - result = importModuleAs(n, gImportModule(c.graph, c.module, f, c.cache)) + result = importModuleAs(c, n, c.graph.importModuleCallback(c.graph, c.module, f)) #echo "set back to ", L c.graph.importStack.setLen(L) # we cannot perform this check reliably because of @@ -146,13 +161,26 @@ proc myImportModule(c: PContext, n: PNode): PSym = when true: if result.info.fileIndex == c.module.info.fileIndex and result.info.fileIndex == n.info.fileIndex: - localError(n.info, errGenerated, "A module cannot import itself") + localError(c.config, n.info, "A module cannot import itself") if sfDeprecated in result.flags: - message(n.info, warnDeprecated, result.name.s) - suggestSym(n.info, result, c.graph.usageSym, false) + if result.constraint != nil: + message(c.config, n.info, warnDeprecated, result.constraint.strVal & "; " & result.name.s) + else: + message(c.config, n.info, warnDeprecated, result.name.s) + suggestSym(c.config, n.info, result, c.graph.usageSym, false) + importStmtResult.add newStrNode(toFullPath(c.config, f), n.info) -proc impMod(c: PContext; it: PNode) = - let m = myImportModule(c, it) +proc transformImportAs(c: PContext; n: PNode): PNode = + if n.kind == nkInfix and considerQuotedIdent(c, n[0]).s == "as": + result = newNodeI(nkImportAs, n.info) + result.add n.sons[1] + result.add n.sons[2] + else: + result = n + +proc impMod(c: PContext; it: PNode; importStmtResult: PNode) = + let it = transformImportAs(c, it) + let m = myImportModule(c, it, importStmtResult) if m != nil: var emptySet: IntSet # ``addDecl`` needs to be done before ``importAllSymbols``! @@ -161,26 +189,34 @@ proc impMod(c: PContext; it: PNode) = #importForwarded(c, m.ast, emptySet) proc evalImport(c: PContext, n: PNode): PNode = - result = n + result = newNodeI(nkImportStmt, n.info) for i in countup(0, sonsLen(n) - 1): let it = n.sons[i] if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket: let sep = it[0] let dir = it[1] - let a = newNodeI(nkInfix, it.info) - a.add sep - a.add dir - a.add sep # dummy entry, replaced in the loop + var imp = newNodeI(nkInfix, it.info) + imp.add sep + imp.add dir + imp.add sep # dummy entry, replaced in the loop for x in it[2]: - a.sons[2] = x - impMod(c, a) + # transform `a/b/[c as d]` to `/a/b/c as d` + if x.kind == nkInfix and x.sons[0].ident.s == "as": + let impAs = copyTree(x) + imp.sons[2] = x.sons[1] + impAs.sons[1] = imp + impMod(c, imp, result) + else: + imp.sons[2] = x + impMod(c, imp, result) else: - impMod(c, it) + impMod(c, it, result) proc evalFrom(c: PContext, n: PNode): PNode = - result = n - checkMinSonsLen(n, 2) - var m = myImportModule(c, n.sons[0]) + result = newNodeI(nkImportStmt, n.info) + checkMinSonsLen(n, 2, c.config) + n.sons[0] = transformImportAs(c, n.sons[0]) + var m = myImportModule(c, n.sons[0], result) if m != nil: n.sons[0] = newSymNode(m) addDecl(c, m, n.info) # add symbol to symbol table of module @@ -189,15 +225,12 @@ proc evalFrom(c: PContext, n: PNode): PNode = importSymbol(c, n.sons[i], m) proc evalImportExcept*(c: PContext, n: PNode): PNode = - result = n - checkMinSonsLen(n, 2) - var m = myImportModule(c, n.sons[0]) + result = newNodeI(nkImportStmt, n.info) + checkMinSonsLen(n, 2, c.config) + n.sons[0] = transformImportAs(c, n.sons[0]) + var m = myImportModule(c, n.sons[0], result) if m != nil: n.sons[0] = newSymNode(m) addDecl(c, m, n.info) # add symbol to symbol table of module - var exceptSet = initIntSet() - for i in countup(1, sonsLen(n) - 1): - let ident = lookups.considerQuotedIdent(n.sons[i]) - exceptSet.incl(ident.id) - importAllSymbolsExcept(c, m, exceptSet) + importAllSymbolsExcept(c, m, readExceptSet(c, n)) #importForwarded(c, m.ast, exceptSet) diff --git a/compiler/incremental.nim b/compiler/incremental.nim new file mode 100644 index 000000000..2008d35de --- /dev/null +++ b/compiler/incremental.nim @@ -0,0 +1,196 @@ +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Basic type definitions the module graph needs in order to support +## incremental compilations. + +const nimIncremental* = defined(nimIncremental) + +import options, lineinfos + +when nimIncremental: + import ast, msgs, intsets, btrees, db_sqlite, std / sha1 + from strutils import parseInt + + type + Writer* = object + sstack*: seq[PSym] # a stack of symbols to process + tstack*: seq[PType] # a stack of types to process + tmarks*, smarks*: IntSet + forwardedSyms*: seq[PSym] + + Reader* = object + syms*: BTree[int, PSym] + types*: BTree[int, PType] + + IncrementalCtx* = object + db*: DbConn + w*: Writer + r*: Reader + configChanged*: bool + + proc init*(incr: var IncrementalCtx) = + incr.w.sstack = @[] + incr.w.tstack = @[] + incr.w.tmarks = initIntSet() + incr.w.smarks = initIntSet() + incr.w.forwardedSyms = @[] + incr.r.syms = initBTree[int, PSym]() + incr.r.types = initBTree[int, PType]() + + + proc hashFileCached*(conf: ConfigRef; fileIdx: FileIndex; fullpath: string): string = + result = msgs.getHash(conf, fileIdx) + if result.len == 0: + result = $secureHashFile(fullpath) + msgs.setHash(conf, fileIdx, result) + + proc toDbFileId*(incr: var IncrementalCtx; conf: ConfigRef; fileIdx: FileIndex): int = + if fileIdx == FileIndex(-1): return -1 + let fullpath = toFullPath(conf, fileIdx) + let row = incr.db.getRow(sql"select id, fullhash from filenames where fullpath = ?", + fullpath) + let id = row[0] + let fullhash = hashFileCached(conf, fileIdx, fullpath) + if id.len == 0: + result = int incr.db.insertID(sql"insert into filenames(fullpath, fullhash) values (?, ?)", + fullpath, fullhash) + else: + if row[1] != fullhash: + incr.db.exec(sql"update filenames set fullhash = ? where fullpath = ?", fullhash, fullpath) + result = parseInt(id) + + proc fromDbFileId*(incr: var IncrementalCtx; conf: ConfigRef; dbId: int): FileIndex = + if dbId == -1: return FileIndex(-1) + let fullpath = incr.db.getValue(sql"select fullpath from filenames where id = ?", dbId) + doAssert fullpath.len > 0, "cannot find file name for DB ID " & $dbId + result = fileInfoIdx(conf, fullpath) + + + proc addModuleDep*(incr: var IncrementalCtx; conf: ConfigRef; + module, fileIdx: FileIndex; + isIncludeFile: bool) = + if conf.symbolFiles != v2Sf: return + + let a = toDbFileId(incr, conf, module) + let b = toDbFileId(incr, conf, fileIdx) + + incr.db.exec(sql"insert into deps(module, dependency, isIncludeFile) values (?, ?, ?)", + a, b, ord(isIncludeFile)) + + # --------------- Database model --------------------------------------------- + + proc createDb*(db: DbConn) = + db.exec(sql""" + create table if not exists controlblock( + idgen integer not null + ); + """) + + db.exec(sql""" + create table if not exists config( + config varchar(8000) not null + ); + """) + + db.exec(sql""" + create table if not exists filenames( + id integer primary key, + fullpath varchar(8000) not null, + fullHash varchar(256) not null + ); + """) + db.exec sql"create index if not exists FilenameIx on filenames(fullpath);" + + db.exec(sql""" + create table if not exists modules( + id integer primary key, + nimid integer not null, + fullpath varchar(8000) not null, + interfHash varchar(256) not null, + fullHash varchar(256) not null, + + created timestamp not null default (DATETIME('now')) + );""") + db.exec(sql"""create unique index if not exists SymNameIx on modules(fullpath);""") + + db.exec(sql""" + create table if not exists deps( + id integer primary key, + module integer not null, + dependency integer not null, + isIncludeFile integer not null, + foreign key (module) references filenames(id), + foreign key (dependency) references filenames(id) + );""") + db.exec(sql"""create index if not exists DepsIx on deps(module);""") + + db.exec(sql""" + create table if not exists types( + id integer primary key, + nimid integer not null, + module integer not null, + data blob not null, + foreign key (module) references module(id) + ); + """) + db.exec sql"create index TypeByModuleIdx on types(module);" + db.exec sql"create index TypeByNimIdIdx on types(nimid);" + + db.exec(sql""" + create table if not exists syms( + id integer primary key, + nimid integer not null, + module integer not null, + name varchar(256) not null, + data blob not null, + exported int not null, + foreign key (module) references module(id) + ); + """) + db.exec sql"create index if not exists SymNameIx on syms(name);" + db.exec sql"create index SymByNameAndModuleIdx on syms(name, module);" + db.exec sql"create index SymByModuleIdx on syms(module);" + db.exec sql"create index SymByNimIdIdx on syms(nimid);" + + + db.exec(sql""" + create table if not exists toplevelstmts( + id integer primary key, + position integer not null, + module integer not null, + data blob not null, + foreign key (module) references module(id) + ); + """) + db.exec sql"create index TopLevelStmtByModuleIdx on toplevelstmts(module);" + db.exec sql"create index TopLevelStmtByPositionIdx on toplevelstmts(position);" + + db.exec(sql""" + create table if not exists statics( + id integer primary key, + module integer not null, + data blob not null, + foreign key (module) references module(id) + ); + """) + db.exec sql"create index StaticsByModuleIdx on toplevelstmts(module);" + db.exec sql"insert into controlblock(idgen) values (0)" + + +else: + type + IncrementalCtx* = object + + template init*(incr: IncrementalCtx) = discard + + template addModuleDep*(incr: var IncrementalCtx; conf: ConfigRef; + module, fileIdx: FileIndex; + isIncludeFile: bool) = + discard diff --git a/compiler/installer.ini b/compiler/installer.ini index 8052121bf..79eb7178a 100644 --- a/compiler/installer.ini +++ b/compiler/installer.ini @@ -6,7 +6,7 @@ Name: "Nim" Version: "$version" Platforms: """ windows: i386;amd64 - linux: i386;amd64;powerpc64;arm;sparc;mips;mipsel;mips64;mips64el;powerpc;powerpc64el;arm64 + linux: i386;amd64;powerpc64;arm;sparc;mips;mipsel;mips64;mips64el;powerpc;powerpc64el;arm64;riscv64 macosx: i386;amd64;powerpc64 solaris: i386;amd64;sparc;sparc64 freebsd: i386;amd64 @@ -15,6 +15,7 @@ Platforms: """ dragonfly: i386;amd64 haiku: i386;amd64 android: i386;arm;arm64 + nintendoswitch: arm64 """ Authors: "Andreas Rumpf" diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index dc74fa933..16ed9dc17 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -8,7 +8,6 @@ # # This is the JavaScript code generator. -# Also a PHP code generator. ;-) discard """ The JS code generator contains only 2 tricks: @@ -31,18 +30,18 @@ implements the required case distinction. import ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options, - nversion, nimsets, msgs, std / sha1, bitsets, idents, types, os, - times, ropes, math, passes, ccgutils, wordrecg, renderer, rodread, rodutils, - intsets, cgmeth, lowerings + nversion, nimsets, msgs, std / sha1, bitsets, idents, types, os, tables, + times, ropes, math, passes, ccgutils, wordrecg, renderer, + intsets, cgmeth, lowerings, sighashes, lineinfos, rodutils from modulegraphs import ModuleGraph type - TTarget = enum - targetJS, targetPHP TJSGen = object of TPassContext module: PSym - target: TTarget + graph: ModuleGraph + config: ConfigRef + sigConflicts: CountTable[SigHash] BModule = ref TJSGen TJSTypeKind = enum # necessary JS "types" @@ -73,7 +72,7 @@ type # has been used (i.e. the label should be emitted) isLoop: bool # whether it's a 'block' or 'while' - TGlobals = object + PGlobals = ref object of RootObj typeInfo, constants, code: Rope forwarded: seq[PSym] generatedSyms: IntSet @@ -81,7 +80,6 @@ type classes: seq[(PType, Rope)] unique: int # for temp identifier generation - PGlobals = ref TGlobals PProc = ref TProc TProc = object procDef: PNode @@ -91,24 +89,20 @@ type module: BModule g: PGlobals beforeRetNeeded: bool - target: TTarget # duplicated here for faster dispatching unique: int # for temp identifier generation blocks: seq[TBlock] extraIndent: int up: PProc # up the call chain; required for closure support declaredGlobals: IntSet -template `|`(a, b: untyped): untyped {.dirty.} = - (if p.target == targetJS: a else: b) - -var indent = "\t".rope +template config*(p: PProc): ConfigRef = p.module.config proc indentLine(p: PProc, r: Rope): Rope = result = r var p = p while true: for i in countup(0, p.blocks.len - 1 + p.extraIndent): - prepend(result, indent) + prepend(result, "\t".rope) if p.up == nil or p.up.prc != p.prc.owner: break p = p.up @@ -156,11 +150,8 @@ proc newProc(globals: PGlobals, module: BModule, procDef: PNode, module: module, procDef: procDef, g: globals, - target: module.target, extraIndent: int(procDef != nil)) if procDef != nil: result.prc = procDef.sons[namePos].sym - if result.target == targetPHP: - result.declaredGlobals = initIntSet() proc declareGlobal(p: PProc; id: int; r: Rope) = if p.prc != nil and not p.declaredGlobals.containsOrIncl(id): @@ -204,13 +195,12 @@ proc mapType(typ: PType): TJSTypeKind = else: result = etyNone of tyProc: result = etyProc of tyCString: result = etyString - of tyUnused, tyOptAsRef: internalError("mapType") + of tyUnused, tyOptAsRef: doAssert(false, "mapType") proc mapType(p: PProc; typ: PType): TJSTypeKind = - if p.target == targetPHP: result = etyObject - else: result = mapType(typ) + result = mapType(typ) -proc mangleName(s: PSym; target: TTarget): Rope = +proc mangleName(m: BModule, s: PSym): Rope = proc validJsName(name: string): bool = result = true const reservedWords = ["abstract", "await", "boolean", "break", "byte", @@ -235,7 +225,7 @@ proc mangleName(s: PSym; target: TTarget): Rope = if result == nil: if s.kind == skField and s.name.s.validJsName: result = rope(s.name.s) - elif target == targetJS or s.kind == skTemp: + elif s.kind == skTemp: result = rope(mangle(s.name.s)) else: var x = newStringOfCap(s.name.s.len) @@ -253,9 +243,15 @@ proc mangleName(s: PSym; target: TTarget): Rope = x.add("HEX" & toHex(ord(c), 2)) inc i result = rope(x) - if s.name.s != "this" and s.kind != skField: - add(result, "_") - add(result, rope(s.id)) + # From ES5 on reserved words can be used as object field names + if s.kind != skField: + if optHotCodeReloading in m.config.options: + # When hot reloading is enabled, we must ensure that the names + # of functions and types will be preserved across rebuilds: + add(result, idOrSig(s, m.module.name.s, m.sigConflicts)) + else: + add(result, "_") + add(result, rope(s.id)) s.loc.r = result proc escapeJSString(s: string): string = @@ -276,9 +272,7 @@ proc escapeJSString(s: string): string = result.add("\"") proc makeJSString(s: string, escapeNonAscii = true): Rope = - if s.isNil: - result = "null".rope - elif escapeNonAscii: + if escapeNonAscii: result = strutils.escape(s).rope else: result = escapeJSString(s).rope @@ -292,23 +286,22 @@ proc genConstant(p: PProc, c: PSym) proc useMagic(p: PProc, name: string) = if name.len == 0: return - var s = magicsys.getCompilerProc(name) + var s = magicsys.getCompilerProc(p.module.graph, name) if s != nil: - internalAssert s.kind in {skProc, skFunc, skMethod, skConverter} + internalAssert p.config, s.kind in {skProc, skFunc, skMethod, skConverter} if not p.g.generatedSyms.containsOrIncl(s.id): let code = genProc(p, s) add(p.g.constants, code) else: - # we used to exclude the system module from this check, but for DLL - # generation support this sloppyness leads to hard to detect bugs, so - # we're picky here for the system module too: - if p.prc != nil: globalError(p.prc.info, errSystemNeeds, name) - else: rawMessage(errSystemNeeds, name) + if p.prc != nil: + globalError(p.config, p.prc.info, "system module needs: " & name) + else: + rawMessage(p.config, errGenerated, "system module needs: " & name) proc isSimpleExpr(p: PProc; n: PNode): bool = # calls all the way down --> can stay expression based - if n.kind in nkCallKinds+{nkBracketExpr, nkDotExpr, nkPar} or - (p.target == targetJS and n.kind in {nkObjConstr, nkBracket, nkCurly}): + if n.kind in nkCallKinds+{nkBracketExpr, nkDotExpr, nkPar, nkTupleConstr} or + (n.kind in {nkObjConstr, nkBracket, nkCurly}): for c in n: if not p.isSimpleExpr(c): return false result = true @@ -317,12 +310,9 @@ proc isSimpleExpr(p: PProc; n: PNode): bool = proc getTemp(p: PProc, defineInLocals: bool = true): Rope = inc(p.unique) - if p.target == targetJS: - result = "Tmp$1" % [rope(p.unique)] - if defineInLocals: - add(p.locals, p.indentLine("var $1;$n" % [result])) - else: - result = "$$Tmp$1" % [rope(p.unique)] + result = "Tmp$1" % [rope(p.unique)] + if defineInLocals: + add(p.locals, p.indentLine("var $1;$n" % [result])) proc genAnd(p: PProc, a, b: PNode, r: var TCompRes) = assert r.kind == resNone @@ -388,6 +378,7 @@ const # magic checked op; magic unchecked op; checked op; unchecked op ["", "", "($1 / $2)", "($1 / $2)"], # DivF64 ["", "", "", ""], # ShrI ["", "", "($1 << $2)", "($1 << $2)"], # ShlI + ["", "", "($1 >> $2)", "($1 >> $2)"], # AshrI ["", "", "($1 & $2)", "($1 & $2)"], # BitandI ["", "", "($1 | $2)", "($1 | $2)"], # BitorI ["", "", "($1 ^ $2)", "($1 ^ $2)"], # BitxorI @@ -471,15 +462,9 @@ proc unsignedTrimmerJS(size: BiggestInt): Rope = of 4: rope">>> 0" else: rope"" -proc unsignedTrimmerPHP(size: BiggestInt): Rope = - case size - of 1: rope"& 0xff" - of 2: rope"& 0xffff" - of 4: rope"& 0xffffffff" - else: rope"" template unsignedTrimmer(size: BiggestInt): Rope = - size.unsignedTrimmerJS | size.unsignedTrimmerPHP + size.unsignedTrimmerJS proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string, reassign = false) = @@ -527,46 +512,18 @@ proc arith(p: PProc, n: PNode, r: var TCompRes, op: TMagic) = of mMulU: binaryUintExpr(p, n, r, "*") of mDivU: binaryUintExpr(p, n, r, "/") of mDivI: - if p.target == targetPHP: - var x, y: TCompRes - gen(p, n.sons[1], x) - gen(p, n.sons[2], y) - r.res = "intval($1 / $2)" % [x.rdLoc, y.rdLoc] - else: - arithAux(p, n, r, op) + arithAux(p, n, r, op) of mModI: - if p.target == targetPHP: - var x, y: TCompRes - gen(p, n.sons[1], x) - gen(p, n.sons[2], y) - r.res = "($1 % $2)" % [x.rdLoc, y.rdLoc] - else: - arithAux(p, n, r, op) + arithAux(p, n, r, op) of mShrI: var x, y: TCompRes gen(p, n.sons[1], x) gen(p, n.sons[2], y) let trimmer = unsignedTrimmer(n[1].typ.skipTypes(abstractRange).size) - if p.target == targetPHP: - # XXX prevent multi evaluations - r.res = "(($1 $2) >= 0) ? (($1 $2) >> $3) : ((($1 $2) & 0x7fffffff) >> $3) | (0x40000000 >> ($3 - 1))" % [x.rdLoc, trimmer, y.rdLoc] - else: - r.res = "(($1 $2) >>> $3)" % [x.rdLoc, trimmer, y.rdLoc] + r.res = "(($1 $2) >>> $3)" % [x.rdLoc, trimmer, y.rdLoc] of mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mFloatToStr, mCStrToStr, mStrToStr, mEnumToStr: - if p.target == targetPHP: - if op == mEnumToStr: - var x: TCompRes - gen(p, n.sons[1], x) - r.res = "$#[$#]" % [genEnumInfoPHP(p, n.sons[1].typ), x.rdLoc] - elif op == mCharToStr: - var x: TCompRes - gen(p, n.sons[1], x) - r.res = "chr($#)" % [x.rdLoc] - else: - gen(p, n.sons[1], r) - else: - arithAux(p, n, r, op) + arithAux(p, n, r, op) else: arithAux(p, n, r, op) r.kind = resExpr @@ -579,18 +536,18 @@ proc genLineDir(p: PProc, n: PNode) = let line = toLinenumber(n.info) if optLineDir in p.options: lineF(p, "// line $2 \"$1\"$n", - [rope(toFilename(n.info)), rope(line)]) + [rope(toFilename(p.config, n.info)), rope(line)]) if {optStackTrace, optEndb} * p.options == {optStackTrace, optEndb} and ((p.prc == nil) or sfPure notin p.prc.flags): useMagic(p, "endb") lineF(p, "endb($1);$n", [rope(line)]) elif hasFrameInfo(p): - lineF(p, "F.line = $1;$n" | "$$F['line'] = $1;$n", [rope(line)]) + lineF(p, "F.line = $1;$n", [rope(line)]) proc genWhileStmt(p: PProc, n: PNode) = var cond: TCompRes - internalAssert isEmptyType(n.typ) + internalAssert p.config, isEmptyType(n.typ) genLineDir(p, n) inc(p.unique) var length = len(p.blocks) @@ -598,12 +555,12 @@ proc genWhileStmt(p: PProc, n: PNode) = p.blocks[length].id = -p.unique p.blocks[length].isLoop = true let labl = p.unique.rope - lineF(p, "L$1: while (true) {$n" | "while (true) {$n", [labl]) + lineF(p, "L$1: while (true) {$n", [labl]) p.nested: gen(p, n.sons[0], cond) - lineF(p, "if (!$1) break L$2;$n" | "if (!$1) goto L$2;$n", + lineF(p, "if (!$1) break L$2;$n", [cond.res, labl]) p.nested: genStmt(p, n.sons[1]) - lineF(p, "}$n" | "}L$#:;$n", [labl]) + lineF(p, "}$n", [labl]) setLen(p.blocks, length) proc moveInto(p: PProc, src: var TCompRes, dest: TCompRes) = @@ -647,26 +604,22 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) = var i = 1 var length = sonsLen(n) var catchBranchesExist = length > 1 and n.sons[i].kind == nkExceptBranch - if catchBranchesExist and p.target == targetJS: - add(p.body, "++excHandler;" & tnl) + if catchBranchesExist: + add(p.body, "++excHandler;\L") var tmpFramePtr = rope"F" if optStackTrace notin p.options: tmpFramePtr = p.getTemp(true) - line(p, tmpFramePtr & " = framePtr;" & tnl) + line(p, tmpFramePtr & " = framePtr;\L") lineF(p, "try {$n", []) - if p.target == targetPHP and p.globals == nil: - p.globals = "global $lastJSError; global $prevJSError;".rope var a: TCompRes gen(p, n.sons[0], a) moveInto(p, a, r) var generalCatchBranchExists = false - let dollar = rope(if p.target == targetJS: "" else: "$") - if p.target == targetJS and catchBranchesExist: + let dollar = rope("") + if catchBranchesExist: addf(p.body, "--excHandler;$n} catch (EXC) {$n var prevJSError = lastJSError;$n" & " lastJSError = EXC;$n --excHandler;$n", []) line(p, "framePtr = $1;$n" % [tmpFramePtr]) - elif p.target == targetPHP: - lineF(p, "} catch (Exception $$EXC) {$n $$prevJSError = $$lastJSError;$n $$lastJSError = $$EXC;$n", []) while i < length and n.sons[i].kind == nkExceptBranch: let blen = sonsLen(n.sons[i]) if blen == 1: @@ -681,7 +634,7 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) = useMagic(p, "isObj") for j in countup(0, blen - 2): if n.sons[i].sons[j].kind != nkType: - internalError(n.info, "genTryStmt") + internalError(p.config, n.info, "genTryStmt") if orExpr != nil: add(orExpr, "||") addf(orExpr, "isObj($2lastJSError.m_type, $1)", [genTypeInfo(p, n.sons[i].sons[j].typ), dollar]) @@ -694,36 +647,29 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) = if catchBranchesExist: if not generalCatchBranchExists: useMagic(p, "reraiseException") - line(p, "else {" & tnl) - line(p, indent & "reraiseException();" & tnl) - line(p, "}" & tnl) + line(p, "else {\L") + line(p, "\treraiseException();\L") + line(p, "}\L") addf(p.body, "$1lastJSError = $1prevJSError;$n", [dollar]) - if p.target == targetJS: - line(p, "} finally {" & tnl) - line(p, "framePtr = $1;$n" % [tmpFramePtr]) - if p.target == targetPHP: - # XXX ugly hack for PHP codegen - line(p, "}" & tnl) + line(p, "} finally {\L") + line(p, "framePtr = $1;$n" % [tmpFramePtr]) if i < length and n.sons[i].kind == nkFinally: genStmt(p, n.sons[i].sons[0]) - if p.target == targetPHP: - # XXX ugly hack for PHP codegen - line(p, "if($lastJSError) throw($lastJSError);" & tnl) - if p.target == targetJS: - line(p, "}" & tnl) + line(p, "}\L") proc genRaiseStmt(p: PProc, n: PNode) = - genLineDir(p, n) if n.sons[0].kind != nkEmpty: var a: TCompRes gen(p, n.sons[0], a) let typ = skipTypes(n.sons[0].typ, abstractPtrs) + genLineDir(p, n) useMagic(p, "raiseException") lineF(p, "raiseException($1, $2);$n", [a.rdLoc, makeJSString(typ.sym.name.s)]) else: + genLineDir(p, n) useMagic(p, "reraiseException") - line(p, "reraiseException();" & tnl) + line(p, "reraiseException();\L") proc genCaseJS(p: PProc, n: PNode, r: var TCompRes) = var @@ -731,7 +677,7 @@ proc genCaseJS(p: PProc, n: PNode, r: var TCompRes) = genLineDir(p, n) gen(p, n.sons[0], cond) let stringSwitch = skipTypes(n.sons[0].typ, abstractVar).kind == tyString - if stringSwitch and p.target == targetJS: + if stringSwitch: useMagic(p, "toJSStr") lineF(p, "switch (toJSStr($1)) {$n", [cond.rdLoc]) else: @@ -756,7 +702,7 @@ proc genCaseJS(p: PProc, n: PNode, r: var TCompRes) = case e.kind of nkStrLit..nkTripleStrLit: lineF(p, "case $1:$n", [makeJSString(e.strVal, false)]) - else: internalError(e.info, "jsgen.genCaseStmt: 2") + else: internalError(p.config, e.info, "jsgen.genCaseStmt: 2") else: gen(p, e, cond) lineF(p, "case $1:$n", [cond.rdLoc]) @@ -770,7 +716,7 @@ proc genCaseJS(p: PProc, n: PNode, r: var TCompRes) = gen(p, it.sons[0], stmt) moveInto(p, stmt, r) lineF(p, "break;$n", []) - else: internalError(it.info, "jsgen.genCaseStmt") + else: internalError(p.config, it.info, "jsgen.genCaseStmt") lineF(p, "}$n", []) proc genBlock(p: PProc, n: PNode, r: var TCompRes) = @@ -778,17 +724,17 @@ proc genBlock(p: PProc, n: PNode, r: var TCompRes) = let idx = len(p.blocks) if n.sons[0].kind != nkEmpty: # named block? - if (n.sons[0].kind != nkSym): internalError(n.info, "genBlock") + if (n.sons[0].kind != nkSym): internalError(p.config, n.info, "genBlock") var sym = n.sons[0].sym sym.loc.k = locOther sym.position = idx+1 let labl = p.unique - lineF(p, "L$1: do {$n" | "", [labl.rope]) + lineF(p, "L$1: do {$n", [labl.rope]) setLen(p.blocks, idx + 1) p.blocks[idx].id = - p.unique # negative because it isn't used yet gen(p, n.sons[1], r) setLen(p.blocks, idx) - lineF(p, "} while(false);$n" | "$nL$#:;$n", [labl.rope]) + lineF(p, "} while(false);$n", [labl.rope]) proc genBreakStmt(p: PProc, n: PNode) = var idx: int @@ -804,9 +750,9 @@ proc genBreakStmt(p: PProc, n: PNode) = idx = len(p.blocks) - 1 while idx >= 0 and not p.blocks[idx].isLoop: dec idx if idx < 0 or not p.blocks[idx].isLoop: - internalError(n.info, "no loop to break") + internalError(p.config, n.info, "no loop to break") p.blocks[idx].id = abs(p.blocks[idx].id) # label is used - lineF(p, "break L$1;$n" | "goto L$1;$n", [rope(p.blocks[idx].id)]) + lineF(p, "break L$1;$n", [rope(p.blocks[idx].id)]) proc genAsmOrEmitStmt(p: PProc, n: PNode) = genLineDir(p, n) @@ -819,18 +765,28 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode) = of nkSym: let v = it.sym # for backwards compatibility we don't deref syms here :-( - if v.kind in {skVar, skLet, skTemp, skConst, skResult, skParam, skForVar}: - if p.target == targetPHP: p.body.add "$" - p.body.add mangleName(v, p.target) + if false: + discard else: var r: TCompRes gen(p, it, r) + + if it.typ.kind == tyPointer: + # A fat pointer is disguised as an array + r.res = r.address + r.address = nil + elif r.typ == etyBaseIndex: + # Deference first + r.res = "$1[$2]" % [r.address, r.res] + r.address = nil + r.typ = etyNone + p.body.add(r.rdLoc) else: var r: TCompRes gen(p, it, r) p.body.add(r.rdLoc) - p.body.add tnl + p.body.add "\L" proc genIf(p: PProc, n: PNode, r: var TCompRes) = var cond, stmt: TCompRes @@ -853,7 +809,7 @@ proc genIf(p: PProc, n: PNode, r: var TCompRes) = p.nested: gen(p, it.sons[0], stmt) moveInto(p, stmt, r) lineF(p, "}$n", []) - line(p, repeat('}', toClose) & tnl) + line(p, repeat('}', toClose) & "\L") proc generateHeader(p: PProc, typ: PType): Rope = result = nil @@ -862,25 +818,12 @@ proc generateHeader(p: PProc, typ: PType): Rope = var param = typ.n.sons[i].sym if isCompileTimeOnly(param.typ): continue if result != nil: add(result, ", ") - var name = mangleName(param, p.target) - if p.target == targetJS: - add(result, name) - if mapType(param.typ) == etyBaseIndex: - add(result, ", ") - add(result, name) - add(result, "_Idx") - elif not (i == 1 and param.name.s == "this"): - let k = param.typ.skipTypes({tyGenericInst, tyAlias, tySink}).kind - if k in {tyVar, tyRef, tyPtr, tyLent, tyPointer}: - add(result, "&") - add(result, "$") + var name = mangleName(p.module, param) + add(result, name) + if mapType(param.typ) == etyBaseIndex: + add(result, ", ") add(result, name) - # XXX I think something like this is needed for PHP to really support - # ptr "inside" strings and seq - #if mapType(param.typ) == etyBaseIndex: - # add(result, ", $") - # add(result, name) - # add(result, "_Idx") + add(result, "_Idx") proc countJsParams(typ: PType): int = for i in countup(1, sonsLen(typ.n) - 1): @@ -894,27 +837,16 @@ proc countJsParams(typ: PType): int = const nodeKindsNeedNoCopy = {nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit, - nkFloatLit..nkFloat64Lit, nkCurly, nkPar, nkObjConstr, nkStringToCString, + nkFloatLit..nkFloat64Lit, nkCurly, nkPar, nkTupleConstr, nkObjConstr, nkStringToCString, nkCStringToString, nkCall, nkPrefix, nkPostfix, nkInfix, nkCommand, nkHiddenCallConv, nkCallStrLit} proc needsNoCopy(p: PProc; y: PNode): bool = result = (y.kind in nodeKindsNeedNoCopy) or - (skipTypes(y.typ, abstractInst).kind in {tyRef, tyPtr, tyLent, tyVar}) or - p.target == targetPHP + (skipTypes(y.typ, abstractInst).kind in {tyRef, tyPtr, tyLent, tyVar}) proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = var a, b: TCompRes - - if p.target == targetPHP and x.kind == nkBracketExpr and - x[0].typ.skipTypes(abstractVar).kind in {tyString, tyCString}: - var c: TCompRes - gen(p, x[0], a) - gen(p, x[1], b) - gen(p, y, c) - lineF(p, "$#[$#] = chr($#);$n", [a.rdLoc, b.rdLoc, c.rdLoc]) - return - var xtyp = mapType(p, x.typ) if x.kind == nkHiddenDeref and x.sons[0].kind == nkCall and xtyp != etyObject: @@ -925,6 +857,7 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = else: gen(p, x, a) + genLineDir(p, y) gen(p, y, b) # we don't care if it's an etyBaseIndex (global) of a string, it's @@ -954,18 +887,16 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = elif b.typ == etyBaseIndex: lineF(p, "$# = $#;$n", [a.res, b.rdLoc]) else: - internalError(x.info, "genAsgn") + internalError(p.config, x.info, "genAsgn") else: lineF(p, "$1 = $2; $3 = $4;$n", [a.address, b.address, a.res, b.res]) else: lineF(p, "$1 = $2;$n", [a.res, b.res]) proc genAsgn(p: PProc, n: PNode) = - genLineDir(p, n) - genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=p.target == targetPHP) + genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=false) proc genFastAsgn(p: PProc, n: PNode) = - genLineDir(p, n) # 'shallowCopy' always produced 'noCopyNeeded = true' here but this is wrong # for code like # while j >= pos: @@ -984,20 +915,18 @@ proc genSwap(p: PProc, n: PNode) = if mapType(p, skipTypes(n.sons[1].typ, abstractVar)) == etyBaseIndex: let tmp2 = p.getTemp(false) if a.typ != etyBaseIndex or b.typ != etyBaseIndex: - internalError(n.info, "genSwap") - lineF(p, "var $1 = $2; $2 = $3; $3 = $1;$n" | - "$1 = $2; $2 = $3; $3 = $1;$n", + internalError(p.config, n.info, "genSwap") + lineF(p, "var $1 = $2; $2 = $3; $3 = $1;$n", [tmp, a.address, b.address]) tmp = tmp2 - lineF(p, "var $1 = $2; $2 = $3; $3 = $1;" | - "$1 = $2; $2 = $3; $3 = $1;", + lineF(p, "var $1 = $2; $2 = $3; $3 = $1;", [tmp, a.res, b.res]) -proc getFieldPosition(f: PNode): int = +proc getFieldPosition(p: PProc; f: PNode): int = case f.kind of nkIntLit..nkUInt64Lit: result = int(f.intVal) of nkSym: result = f.sym.position - else: internalError(f.info, "genFieldPosition") + else: internalError(p.config, f.info, "genFieldPosition") proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) = var a: TCompRes @@ -1005,16 +934,13 @@ proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) = let b = if n.kind == nkHiddenAddr: n.sons[0] else: n gen(p, b.sons[0], a) if skipTypes(b.sons[0].typ, abstractVarRange).kind == tyTuple: - if p.target == targetJS: - r.res = makeJSString( "Field" & $getFieldPosition(b.sons[1]) ) - else: - r.res = getFieldPosition(b.sons[1]).rope + r.res = makeJSString("Field" & $getFieldPosition(p, b.sons[1])) else: - if b.sons[1].kind != nkSym: internalError(b.sons[1].info, "genFieldAddr") + if b.sons[1].kind != nkSym: internalError(p.config, b.sons[1].info, "genFieldAddr") var f = b.sons[1].sym - if f.loc.r == nil: f.loc.r = mangleName(f, p.target) + if f.loc.r == nil: f.loc.r = mangleName(p.module, f) r.res = makeJSString($f.loc.r) - internalAssert a.typ != etyBaseIndex + internalAssert p.config, a.typ != etyBaseIndex r.address = a.res r.kind = resExpr @@ -1023,26 +949,20 @@ proc genFieldAccess(p: PProc, n: PNode, r: var TCompRes) = gen(p, n.sons[0], r) let otyp = skipTypes(n.sons[0].typ, abstractVarRange) if otyp.kind == tyTuple: - r.res = ("$1.Field$2" | "$1[$2]") % - [r.res, getFieldPosition(n.sons[1]).rope] + r.res = ("$1.Field$2") % + [r.res, getFieldPosition(p, n.sons[1]).rope] else: - if n.sons[1].kind != nkSym: internalError(n.sons[1].info, "genFieldAccess") + if n.sons[1].kind != nkSym: internalError(p.config, n.sons[1].info, "genFieldAccess") var f = n.sons[1].sym - if f.loc.r == nil: f.loc.r = mangleName(f, p.target) - if p.target == targetJS: - r.res = "$1.$2" % [r.res, f.loc.r] - else: - if {sfImportc, sfExportc} * f.flags != {}: - r.res = "$1->$2" % [r.res, f.loc.r] - else: - r.res = "$1['$2']" % [r.res, f.loc.r] + if f.loc.r == nil: f.loc.r = mangleName(p.module, f) + r.res = "$1.$2" % [r.res, f.loc.r] r.kind = resExpr proc genAddr(p: PProc, n: PNode, r: var TCompRes) proc genCheckedFieldAddr(p: PProc, n: PNode, r: var TCompRes) = let m = if n.kind == nkHiddenAddr: n.sons[0] else: n - internalAssert m.kind == nkCheckedFieldExpr + internalAssert p.config, m.kind == nkCheckedFieldExpr genAddr(p, m, r) # XXX proc genCheckedFieldAccess(p: PProc, n: PNode, r: var TCompRes) = @@ -1056,20 +976,14 @@ proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) = let m = if n.kind == nkHiddenAddr: n.sons[0] else: n gen(p, m.sons[0], a) gen(p, m.sons[1], b) - internalAssert a.typ != etyBaseIndex and b.typ != etyBaseIndex + internalAssert p.config, a.typ != etyBaseIndex and b.typ != etyBaseIndex r.address = a.res var typ = skipTypes(m.sons[0].typ, abstractPtrs) - if typ.kind == tyArray: first = firstOrd(typ.sons[0]) + if typ.kind == tyArray: first = firstOrd(p.config, typ.sons[0]) else: first = 0 if optBoundsCheck in p.options: useMagic(p, "chckIndx") - if p.target == targetPHP: - if typ.kind != tyString: - r.res = "chckIndx($1, $2, count($3)-1)-$2" % [b.res, rope(first), a.res] - else: - r.res = "chckIndx($1, $2, strlen($3))-$2" % [b.res, rope(first), a.res] - else: - r.res = "chckIndx($1, $2, $3.length+$2-1)-$2" % [b.res, rope(first), a.res] + r.res = "chckIndx($1, $2, $3.length+$2-1)-$2" % [b.res, rope(first), a.res] elif first != 0: r.res = "($1)-$2" % [b.res, rope(first)] else: @@ -1083,26 +997,12 @@ proc genArrayAccess(p: PProc, n: PNode, r: var TCompRes) = of tyArray, tyOpenArray, tySequence, tyString, tyCString, tyVarargs: genArrayAddr(p, n, r) of tyTuple: - if p.target == targetPHP: - genFieldAccess(p, n, r) - return genFieldAddr(p, n, r) - else: internalError(n.info, "expr(nkBracketExpr, " & $ty.kind & ')') + else: internalError(p.config, n.info, "expr(nkBracketExpr, " & $ty.kind & ')') r.typ = etyNone - if r.res == nil: internalError(n.info, "genArrayAccess") - if p.target == targetPHP: - if n.sons[0].kind in nkCallKinds+{nkStrLit..nkTripleStrLit}: - useMagic(p, "nimAt") - if ty.kind in {tyString, tyCString}: - # XXX this needs to be more like substr($1,$2) - r.res = "ord(nimAt($1, $2))" % [r.address, r.res] - else: - r.res = "nimAt($1, $2)" % [r.address, r.res] - elif ty.kind in {tyString, tyCString}: - # XXX this needs to be more like substr($1,$2) - r.res = "ord(@$1[$2])" % [r.address, r.res] - else: - r.res = "$1[$2]" % [r.address, r.res] + if r.res == nil: internalError(p.config, n.info, "genArrayAccess") + if ty.kind == tyCString: + r.res = "$1.charCodeAt($2)" % [r.address, r.res] else: r.res = "$1[$2]" % [r.address, r.res] r.address = nil @@ -1114,13 +1014,13 @@ template isIndirect(x: PSym): bool = #(mapType(v.typ) != etyObject) and {sfImportc, sfVolatile, sfExportc} * v.flags == {} and v.kind notin {skProc, skFunc, skConverter, skMethod, skIterator, - skConst, skTemp, skLet} and p.target == targetJS) + skConst, skTemp, skLet}) proc genAddr(p: PProc, n: PNode, r: var TCompRes) = case n.sons[0].kind of nkSym: let s = n.sons[0].sym - if s.loc.r == nil: internalError(n.info, "genAddr: 3") + if s.loc.r == nil: internalError(p.config, n.info, "genAddr: 3") case s.kind of skVar, skLet, skResult: r.kind = resExpr @@ -1130,8 +1030,6 @@ proc genAddr(p: PProc, n: PNode, r: var TCompRes) = r.typ = etyNone if isIndirect(s): r.res = s.loc.r & "[0]" - elif p.target == targetPHP: - r.res = "&" & s.loc.r else: r.res = s.loc.r r.address = nil @@ -1144,8 +1042,8 @@ proc genAddr(p: PProc, n: PNode, r: var TCompRes) = else: # 'var openArray' for instance produces an 'addr' but this is harmless: gen(p, n.sons[0], r) - #internalError(n.info, "genAddr: 4 " & renderTree(n)) - else: internalError(n.info, "genAddr: 2") + #internalError(p.config, n.info, "genAddr: 4 " & renderTree(n)) + else: internalError(p.config, n.info, "genAddr: 2") of nkCheckedFieldExpr: genCheckedFieldAddr(p, n, r) of nkDotExpr: @@ -1164,23 +1062,15 @@ proc genAddr(p: PProc, n: PNode, r: var TCompRes) = genArrayAddr(p, n.sons[0], r) of tyTuple: genFieldAddr(p, n.sons[0], r) - else: internalError(n.sons[0].info, "expr(nkBracketExpr, " & $kindOfIndexedExpr & ')') + else: internalError(p.config, n.sons[0].info, "expr(nkBracketExpr, " & $kindOfIndexedExpr & ')') of nkObjDownConv: gen(p, n.sons[0], r) of nkHiddenDeref: gen(p, n.sons[0].sons[0], r) - else: internalError(n.sons[0].info, "genAddr: " & $n.sons[0].kind) + else: internalError(p.config, n.sons[0].info, "genAddr: " & $n.sons[0].kind) proc thisParam(p: PProc; typ: PType): PType = - if p.target == targetPHP: - # XXX Might be very useful for the JS backend too? - let typ = skipTypes(typ, abstractInst) - assert(typ.kind == tyProc) - if 1 < sonsLen(typ.n): - assert(typ.n.sons[1].kind == nkSym) - let param = typ.n.sons[1].sym - if param.name.s == "this": - result = param.typ.skipTypes(abstractVar) + discard proc attachProc(p: PProc; content: Rope; s: PSym) = let otyp = thisParam(p, s.typ) @@ -1211,40 +1101,28 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) = case s.kind of skVar, skLet, skParam, skTemp, skResult, skForVar: if s.loc.r == nil: - internalError(n.info, "symbol has no generated name: " & s.name.s) - if p.target == targetJS: - let k = mapType(p, s.typ) - if k == etyBaseIndex: - r.typ = etyBaseIndex - if {sfAddrTaken, sfGlobal} * s.flags != {}: - r.address = "$1[0]" % [s.loc.r] - r.res = "$1[1]" % [s.loc.r] - else: - r.address = s.loc.r - r.res = s.loc.r & "_Idx" - elif isIndirect(s): - r.res = "$1[0]" % [s.loc.r] + internalError(p.config, n.info, "symbol has no generated name: " & s.name.s) + let k = mapType(p, s.typ) + if k == etyBaseIndex: + r.typ = etyBaseIndex + if {sfAddrTaken, sfGlobal} * s.flags != {}: + r.address = "$1[0]" % [s.loc.r] + r.res = "$1[1]" % [s.loc.r] else: - r.res = s.loc.r + r.address = s.loc.r + r.res = s.loc.r & "_Idx" + elif isIndirect(s): + r.res = "$1[0]" % [s.loc.r] else: - r.res = "$" & s.loc.r - if sfGlobal in s.flags: - p.declareGlobal(s.id, r.res) + r.res = s.loc.r of skConst: genConstant(p, s) if s.loc.r == nil: - internalError(n.info, "symbol has no generated name: " & s.name.s) - if p.target == targetJS: - r.res = s.loc.r - else: - r.res = "$" & s.loc.r - p.declareGlobal(s.id, r.res) + internalError(p.config, n.info, "symbol has no generated name: " & s.name.s) + r.res = s.loc.r of skProc, skFunc, skConverter, skMethod: - discard mangleName(s, p.target) - if p.target == targetPHP and r.kind != resCallee: - r.res = makeJsString($s.loc.r) - else: - r.res = s.loc.r + discard mangleName(p.module, s) + r.res = s.loc.r if lfNoDecl in s.loc.flags or s.magic != mNone or {sfImportc, sfInfixCall} * s.flags != {}: discard @@ -1257,7 +1135,7 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) = genProcForSymIfNeeded(p, s) else: if s.loc.r == nil: - internalError(n.info, "symbol has no generated name: " & s.name.s) + internalError(p.config, n.info, "symbol has no generated name: " & s.name.s) r.res = s.loc.r r.kind = resVal @@ -1278,7 +1156,7 @@ proc genDeref(p: PProc, n: PNode, r: var TCompRes) = elif t == etyBaseIndex: r.res = "$1[0]" % [a.res] else: - internalError(n.info, "genDeref") + internalError(p.config, n.info, "genDeref") proc genArgNoParam(p: PProc, n: PNode, r: var TCompRes) = var a: TCompRes @@ -1338,14 +1216,14 @@ proc genArgs(p: PProc, n: PNode, r: var TCompRes; start=1) = # XXX look into this: let jsp = countJsParams(typ) if emitted != jsp and tfVarargs notin typ.flags: - localError(n.info, "wrong number of parameters emitted; expected: " & $jsp & + localError(p.config, n.info, "wrong number of parameters emitted; expected: " & $jsp & " but got: " & $emitted) r.kind = resExpr proc genOtherArg(p: PProc; n: PNode; i: int; typ: PType; generated: var int; r: var TCompRes) = if i >= n.len: - globalError(n.info, "wrong importcpp pattern; expected parameter at position " & $i & + globalError(p.config, n.info, "wrong importcpp pattern; expected parameter at position " & $i & " but got only: " & $(n.len-1)) let it = n[i] var paramType: PNode = nil @@ -1396,10 +1274,10 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType; proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) = # don't call '$' here for efficiency: let f = n[0].sym - if f.loc.r == nil: f.loc.r = mangleName(f, p.target) + if f.loc.r == nil: f.loc.r = mangleName(p.module, f) if sfInfixCall in f.flags: let pat = n.sons[0].sym.loc.r.data - internalAssert pat != nil + internalAssert p.config, pat.len > 0 if pat.contains({'#', '(', '@'}): var typ = skipTypes(n.sons[0].typ, abstractInst) assert(typ.kind == tyProc) @@ -1409,14 +1287,12 @@ proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) = gen(p, n.sons[1], r) if r.typ == etyBaseIndex: if r.address == nil: - globalError(n.info, "cannot invoke with infix syntax") + globalError(p.config, n.info, "cannot invoke with infix syntax") r.res = "$1[$2]" % [r.address, r.res] r.address = nil r.typ = etyNone - add(r.res, "." | "->") + add(r.res, ".") var op: TCompRes - if p.target == targetPHP: - op.kind = resCallee gen(p, n.sons[0], op) add(r.res, op.res) genArgs(p, n, r, 2) @@ -1425,28 +1301,21 @@ proc genCall(p: PProc, n: PNode, r: var TCompRes) = if n.sons[0].kind == nkSym and thisParam(p, n.sons[0].typ) != nil: genInfixCall(p, n, r) return - if p.target == targetPHP: - r.kind = resCallee gen(p, n.sons[0], r) genArgs(p, n, r) proc genEcho(p: PProc, n: PNode, r: var TCompRes) = let n = n[1].skipConv - internalAssert n.kind == nkBracket - if p.target == targetJS: - useMagic(p, "toJSStr") # Used in rawEcho - useMagic(p, "rawEcho") - elif n.len == 0: - r.kind = resExpr - add(r.res, """print("\n")""") - return - add(r.res, "rawEcho(" | "print(") + internalAssert p.config, n.kind == nkBracket + useMagic(p, "toJSStr") # Used in rawEcho + useMagic(p, "rawEcho") + add(r.res, "rawEcho(") for i in countup(0, sonsLen(n) - 1): let it = n.sons[i] if it.typ.isCompileTimeOnly: continue - if i > 0: add(r.res, ", " | ".") + if i > 0: add(r.res, ", ") genArgNoParam(p, it, r) - add(r.res, ")" | """."\n")""") + add(r.res, ")") r.kind = resExpr proc putToSeq(s: string, indirect: bool): Rope = @@ -1466,18 +1335,15 @@ proc createRecordVarAux(p: PProc, rec: PNode, excludedFieldIDs: IntSet, output: of nkSym: if rec.sym.id notin excludedFieldIDs: if output.len > 0: output.add(", ") - if p.target == targetJS: - output.addf("$#: ", [mangleName(rec.sym, p.target)]) - else: - output.addf("'$#' => ", [mangleName(rec.sym, p.target)]) + output.addf("$#: ", [mangleName(p.module, rec.sym)]) output.add(createVar(p, rec.sym.typ, false)) - else: internalError(rec.info, "createRecordVarAux") + else: internalError(p.config, rec.info, "createRecordVarAux") proc createObjInitList(p: PProc, typ: PType, excludedFieldIDs: IntSet, output: var Rope) = var t = typ if objHasTypeField(t): if output.len > 0: output.add(", ") - addf(output, "m_type: $1" | "'m_type' => $#", [genTypeInfo(p, t)]) + addf(output, "m_type: $1", [genTypeInfo(p, t)]) while t != nil: t = t.skipTypes(skipPtrs) createRecordVarAux(p, t.n, excludedFieldIDs, output) @@ -1494,7 +1360,7 @@ proc arrayTypeForElemType(typ: PType): string = of tyUint8: "Uint8Array" of tyFloat32: "Float32Array" of tyFloat64, tyFloat: "Float64Array" - else: nil + else: "" proc createVar(p: PProc, typ: PType, indirect: bool): Rope = var t = skipTypes(typ, abstractInst) @@ -1506,49 +1372,42 @@ proc createVar(p: PProc, typ: PType, indirect: bool): Rope = of tyRange, tyGenericInst, tyAlias, tySink: result = createVar(p, lastSon(typ), indirect) of tySet: - result = putToSeq("{}" | "array()", indirect) + result = putToSeq("{}", indirect) of tyBool: result = putToSeq("false", indirect) of tyArray: - let length = int(lengthOrd(t)) + let length = int(lengthOrd(p.config, t)) let e = elemType(t) let jsTyp = arrayTypeForElemType(e) - if not jsTyp.isNil and p.target == targetJS: + if jsTyp.len > 0: result = "new $1($2)" % [rope(jsTyp), rope(length)] elif length > 32: useMagic(p, "arrayConstr") # XXX: arrayConstr depends on nimCopy. This line shouldn't be necessary. - if p.target == targetJS: useMagic(p, "nimCopy") + useMagic(p, "nimCopy") result = "arrayConstr($1, $2, $3)" % [rope(length), createVar(p, e, false), genTypeInfo(p, e)] else: - result = rope("[" | "array(") + result = rope("[") var i = 0 while i < length: if i > 0: add(result, ", ") add(result, createVar(p, e, false)) inc(i) - add(result, "]" | ")") + add(result, "]") if indirect: result = "[$1]" % [result] of tyTuple: - if p.target == targetJS: - result = rope("{") - for i in 0..<t.sonsLen: - if i > 0: add(result, ", ") - addf(result, "Field$1: $2", [i.rope, - createVar(p, t.sons[i], false)]) - add(result, "}") - if indirect: result = "[$1]" % [result] - else: - result = rope("array(") - for i in 0..<t.sonsLen: - if i > 0: add(result, ", ") - add(result, createVar(p, t.sons[i], false)) - add(result, ")") + result = rope("{") + for i in 0..<t.sonsLen: + if i > 0: add(result, ", ") + addf(result, "Field$1: $2", [i.rope, + createVar(p, t.sons[i], false)]) + add(result, "}") + if indirect: result = "[$1]" % [result] of tyObject: var initList: Rope createObjInitList(p, t, initIntSet(), initList) - result = ("{$1}" | "array($#)") % [initList] + result = ("{$1}") % [initList] if indirect: result = "[$1]" % [result] of tyVar, tyPtr, tyLent, tyRef: if mapType(p, t) == etyBaseIndex: @@ -1561,10 +1420,10 @@ proc createVar(p: PProc, typ: PType, indirect: bool): Rope = if t.n != nil: result = createVar(p, lastSon t, indirect) else: - internalError("createVar: " & $t.kind) + internalError(p.config, "createVar: " & $t.kind) result = nil else: - internalError("createVar: " & $t.kind) + internalError(p.config, "createVar: " & $t.kind) result = nil template returnType: untyped = @@ -1575,18 +1434,25 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) = a: TCompRes s: Rope varCode: string + varName = mangleName(p.module, v) + useReloadingGuard = sfGlobal in v.flags and optHotCodeReloading in p.config.options + if v.constraint.isNil: - varCode = "var $2" + if useReloadingGuard: + lineF(p, "var $1;$n", varName) + lineF(p, "if ($1 === undefined) {$n", varName) + varCode = $varName + else: + varCode = "var $2" else: varCode = v.constraint.strVal + if n.kind == nkEmpty: - let mname = mangleName(v, p.target) - lineF(p, varCode & " = $3;$n" | "$$$2 = $3;$n", - [returnType, mname, createVar(p, v.typ, isIndirect(v))]) + lineF(p, varCode & " = $3;$n", + [returnType, varName, createVar(p, v.typ, isIndirect(v))]) if v.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and mapType(p, v.typ) == etyBaseIndex: - lineF(p, "var $1_Idx = 0;$n", [ mname ]) + lineF(p, "var $1_Idx = 0;$n", [varName]) else: - discard mangleName(v, p.target) gen(p, n, a) case mapType(p, v.typ) of etyObject, etySeq: @@ -1617,14 +1483,17 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) = if isIndirect(v): lineF(p, varCode & " = [$3];$n", [returnType, v.loc.r, s]) else: - lineF(p, varCode & " = $3;$n" | "$$$2 = $3;$n", [returnType, v.loc.r, s]) + lineF(p, varCode & " = $3;$n", [returnType, v.loc.r, s]) + + if useReloadingGuard: + lineF(p, "}$n") proc genVarStmt(p: PProc, n: PNode) = for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] if a.kind != nkCommentStmt: if a.kind == nkVarTuple: - let unpacked = lowerTupleUnpacking(a, p.prc) + let unpacked = lowerTupleUnpacking(p.module.graph, a, p.prc) genStmt(p, unpacked) else: assert(a.kind == nkIdentDefs) @@ -1647,25 +1516,21 @@ proc genNew(p: PProc, n: PNode) = var a: TCompRes gen(p, n.sons[1], a) var t = skipTypes(n.sons[1].typ, abstractVar).sons[0] - if p.target == targetJS: - lineF(p, "$1 = $2;$n", [a.res, createVar(p, t, false)]) - else: - lineF(p, "$3 = $2; $1 = &$3;$n", [a.res, createVar(p, t, false), getTemp(p)]) + lineF(p, "$1 = $2;$n", [a.res, createVar(p, t, false)]) proc genNewSeq(p: PProc, n: PNode) = var x, y: TCompRes gen(p, n.sons[1], x) gen(p, n.sons[2], y) let t = skipTypes(n.sons[1].typ, abstractVar).sons[0] - lineF(p, "$1 = new Array($2); for (var i=0;i<$2;++i) {$1[i]=$3;}" | - "$1 = array(); for ($$i=0;$$i<$2;++$$i) {$1[]=$3;}", [ + lineF(p, "$1 = new Array($2); for (var i=0;i<$2;++i) {$1[i]=$3;}", [ x.rdLoc, y.rdLoc, createVar(p, t, false)]) proc genOrd(p: PProc, n: PNode, r: var TCompRes) = case skipTypes(n.sons[1].typ, abstractVar).kind of tyEnum, tyInt..tyUInt64, tyChar: gen(p, n.sons[1], r) of tyBool: unaryExpr(p, n, r, "", "($1 ? 1:0)") - else: internalError(n.info, "genOrd") + else: internalError(p.config, n.info, "genOrd") proc genConStrStr(p: PProc, n: PNode, r: var TCompRes) = var a: TCompRes @@ -1675,36 +1540,21 @@ proc genConStrStr(p: PProc, n: PNode, r: var TCompRes) = if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyChar: r.res.add("[$1].concat(" % [a.res]) else: - r.res.add("($1.slice(0,-1)).concat(" % [a.res]) + r.res.add("($1).concat(" % [a.res]) for i in countup(2, sonsLen(n) - 2): gen(p, n.sons[i], a) if skipTypes(n.sons[i].typ, abstractVarRange).kind == tyChar: r.res.add("[$1]," % [a.res]) else: - r.res.add("$1.slice(0,-1)," % [a.res]) + r.res.add("$1," % [a.res]) gen(p, n.sons[sonsLen(n) - 1], a) if skipTypes(n.sons[sonsLen(n) - 1].typ, abstractVarRange).kind == tyChar: - r.res.add("[$1, 0])" % [a.res]) + r.res.add("[$1])" % [a.res]) else: r.res.add("$1)" % [a.res]) -proc genConStrStrPHP(p: PProc, n: PNode, r: var TCompRes) = - var a: TCompRes - gen(p, n.sons[1], a) - r.kind = resExpr - if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyChar: - r.res.add("chr($1)" % [a.res]) - else: - r.res.add(a.res) - for i in countup(2, sonsLen(n) - 1): - gen(p, n.sons[i], a) - if skipTypes(n.sons[i].typ, abstractVarRange).kind == tyChar: - r.res.add(".chr($1)" % [a.res]) - else: - r.res.add(".$1" % [a.res]) - proc genToArray(p: PProc; n: PNode; r: var TCompRes) = # we map mArray to PHP's array constructor, a mild hack: var a, b: TCompRes @@ -1714,15 +1564,15 @@ proc genToArray(p: PProc; n: PNode; r: var TCompRes) = if x.kind == nkBracket: for i in countup(0, x.len - 1): let it = x[i] - if it.kind == nkPar and it.len == 2: + if it.kind in {nkPar, nkTupleConstr} and it.len == 2: if i > 0: r.res.add(", ") gen(p, it[0], a) gen(p, it[1], b) r.res.add("$# => $#" % [a.rdLoc, b.rdLoc]) else: - localError(it.info, "'toArray' needs tuple constructors") + localError(p.config, it.info, "'toArray' needs tuple constructors") else: - localError(x.info, "'toArray' needs an array literal") + localError(p.config, x.info, "'toArray' needs an array literal") r.res.add(")") proc genReprAux(p: PProc, n: PNode, r: var TCompRes, magic: string, typ: Rope = nil) = @@ -1748,9 +1598,6 @@ proc genReprAux(p: PProc, n: PNode, r: var TCompRes, magic: string, typ: Rope = add(r.res, ")") proc genRepr(p: PProc, n: PNode, r: var TCompRes) = - if p.target == targetPHP: - localError(n.info, "'repr' not available for PHP backend") - return let t = skipTypes(n.sons[1].typ, abstractVarRange) case t.kind: of tyInt..tyInt64, tyUInt..tyUInt64: @@ -1768,7 +1615,7 @@ proc genRepr(p: PProc, n: PNode, r: var TCompRes) = of tySet: genReprAux(p, n, r, "reprSet", genTypeInfo(p, t)) of tyEmpty, tyVoid: - localError(n.info, "'repr' doesn't support 'void' type") + localError(p.config, n.info, "'repr' doesn't support 'void' type") of tyPointer: genReprAux(p, n, r, "reprPointer") of tyOpenArray, tyVarargs: @@ -1810,82 +1657,52 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = if not (optOverflowCheck in p.options): unaryExpr(p, n, r, "", "$1 - 1") else: unaryExpr(p, n, r, "subInt", "subInt($1, 1)") of mAppendStrCh: - if p.target == targetJS: - binaryExpr(p, n, r, "addChar", - "if ($1 != null) { addChar($1, $2); } else { $1 = [$2, 0]; }") - else: - binaryExpr(p, n, r, "", "$1 .= chr($2)") + binaryExpr(p, n, r, "addChar", + "if ($1 != null) { addChar($1, $2); } else { $1 = [$2]; }") of mAppendStrStr: - if p.target == targetJS: - if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyCString: - binaryExpr(p, n, r, "", "if ($1 != null) { $1 += $2; } else { $1 = $2; }") - else: - binaryExpr(p, n, r, "", - "if ($1 != null) { $1 = ($1.slice(0, -1)).concat($2); } else { $1 = $2;}") - # XXX: make a copy of $2, because of Javascript's sucking semantics + if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyCString: + binaryExpr(p, n, r, "", "if ($1 != null) { $1 += $2; } else { $1 = $2; }") else: - binaryExpr(p, n, r, "", "$1 .= $2;") + binaryExpr(p, n, r, "", + "if ($1 != null) { $1 = ($1).concat($2); } else { $1 = $2;}") + # XXX: make a copy of $2, because of Javascript's sucking semantics of mAppendSeqElem: - if p.target == targetJS: - var x, y: TCompRes - gen(p, n.sons[1], x) - gen(p, n.sons[2], y) - if needsNoCopy(p, n[2]): - r.res = "if ($1 != null) { $1.push($2); } else { $1 = [$2]; }" % [x.rdLoc, y.rdLoc] - else: - useMagic(p, "nimCopy") - let c = getTemp(p, defineInLocals=false) - lineF(p, "var $1 = nimCopy(null, $2, $3);$n", - [c, y.rdLoc, genTypeInfo(p, n[2].typ)]) - r.res = "if ($1 != null) { $1.push($2); } else { $1 = [$2]; }" % [x.rdLoc, c] - r.kind = resExpr + var x, y: TCompRes + gen(p, n.sons[1], x) + gen(p, n.sons[2], y) + if needsNoCopy(p, n[2]): + r.res = "if ($1 != null) { $1.push($2); } else { $1 = [$2]; }" % [x.rdLoc, y.rdLoc] else: - binaryExpr(p, n, r, "", "$1[] = $2") + useMagic(p, "nimCopy") + let c = getTemp(p, defineInLocals=false) + lineF(p, "var $1 = nimCopy(null, $2, $3);$n", + [c, y.rdLoc, genTypeInfo(p, n[2].typ)]) + r.res = "if ($1 != null) { $1.push($2); } else { $1 = [$2]; }" % [x.rdLoc, c] + r.kind = resExpr of mConStrStr: - if p.target == targetJS: - genConStrStr(p, n, r) - else: - genConStrStrPHP(p, n, r) + genConStrStr(p, n, r) of mEqStr: - if p.target == targetJS: - binaryExpr(p, n, r, "eqStrings", "eqStrings($1, $2)") - else: - binaryExpr(p, n, r, "", "($1 == $2)") + binaryExpr(p, n, r, "eqStrings", "eqStrings($1, $2)") of mLeStr: - if p.target == targetJS: - binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) <= 0)") - else: - binaryExpr(p, n, r, "", "($1 <= $2)") + binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) <= 0)") of mLtStr: - if p.target == targetJS: - binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) < 0)") - else: - binaryExpr(p, n, r, "", "($1 < $2)") + binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) < 0)") of mIsNil: unaryExpr(p, n, r, "", "($1 === null)") of mEnumToStr: genRepr(p, n, r) of mNew, mNewFinalize: genNew(p, n) - of mSizeOf: r.res = rope(getSize(n.sons[1].typ)) + of mSizeOf: r.res = rope(getSize(p.config, n.sons[1].typ)) of mChr, mArrToSeq: gen(p, n.sons[1], r) # nothing to do of mOrd: genOrd(p, n, r) of mLengthStr: - if p.target == targetJS and n.sons[1].typ.skipTypes(abstractInst).kind == tyCString: - unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)") - else: - unaryExpr(p, n, r, "", "($1 != null ? $1.length-1 : 0)" | - "strlen($1)") - of mXLenStr: unaryExpr(p, n, r, "", "$1.length-1" | "strlen($1)") + unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)") + of mXLenStr: + unaryExpr(p, n, r, "", "$1.length") of mLengthSeq, mLengthOpenArray, mLengthArray: - unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)" | - "count($1)") + unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)") of mXLenSeq: - unaryExpr(p, n, r, "", "$1.length" | "count($1)") + unaryExpr(p, n, r, "", "$1.length") of mHigh: - if skipTypes(n.sons[1].typ, abstractVar).kind == tyString: - unaryExpr(p, n, r, "", "($1 != null ? ($1.length-2) : -1)" | - "(strlen($1)-1)") - else: - unaryExpr(p, n, r, "", "($1 != null ? ($1.length-1) : -1)" | - "(count($1)-1)") + unaryExpr(p, n, r, "", "($1 != null ? ($1.length-1) : -1)") of mInc: if n[1].typ.skipTypes(abstractRange).kind in tyUInt .. tyUInt64: binaryUintExpr(p, n, r, "+", true) @@ -1899,7 +1716,7 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 -= $2") else: binaryExpr(p, n, r, "subInt", "$1 = subInt($1, $2)") of mSetLengthStr: - binaryExpr(p, n, r, "", "$1.length = $2+1; $1[$1.length-1] = 0" | "$1 = substr($1, 0, $2)") + binaryExpr(p, n, r, "", "$1.length = $2") of mSetLengthSeq: var x, y: TCompRes gen(p, n.sons[1], x) @@ -1916,50 +1733,21 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = of mPlusSet: binaryExpr(p, n, r, "SetPlus", "SetPlus($1, $2)") of mMinusSet: binaryExpr(p, n, r, "SetMinus", "SetMinus($1, $2)") of mIncl: binaryExpr(p, n, r, "", "$1[$2] = true") - of mExcl: binaryExpr(p, n, r, "", "delete $1[$2]" | "unset $1[$2]") + of mExcl: binaryExpr(p, n, r, "", "delete $1[$2]") of mInSet: - if p.target == targetJS: - binaryExpr(p, n, r, "", "($1[$2] != undefined)") - else: - let s = n.sons[1] - if s.kind == nkCurly: - var a, b, x: TCompRes - gen(p, n.sons[2], x) - r.res = rope("(") - r.kind = resExpr - for i in countup(0, sonsLen(s) - 1): - if i > 0: add(r.res, " || ") - var it = s.sons[i] - if it.kind == nkRange: - gen(p, it.sons[0], a) - gen(p, it.sons[1], b) - addf(r.res, "($1 >= $2 && $1 <= $3)", [x.res, a.res, b.res,]) - else: - gen(p, it, a) - addf(r.res, "($1 == $2)", [x.res, a.res]) - add(r.res, ")") - else: - binaryExpr(p, n, r, "", "isset($1[$2])") + binaryExpr(p, n, r, "", "($1[$2] != undefined)") of mNewSeq: genNewSeq(p, n) - of mNewSeqOfCap: unaryExpr(p, n, r, "", "[]" | "array()") + of mNewSeqOfCap: unaryExpr(p, n, r, "", "[]") of mOf: genOf(p, n, r) of mReset: genReset(p, n) of mEcho: genEcho(p, n, r) of mNLen..mNError, mSlurp, mStaticExec: - localError(n.info, errXMustBeCompileTime, n.sons[0].sym.name.s) + localError(p.config, n.info, errXMustBeCompileTime % n.sons[0].sym.name.s) of mCopyStr: - binaryExpr(p, n, r, "", "($1.slice($2))" | "substr($1, $2)") - of mCopyStrLast: - if p.target == targetJS: - ternaryExpr(p, n, r, "", "($1.slice($2, ($3)+1).concat(0))") - else: - ternaryExpr(p, n, r, "nimSubstr", "nimSubstr($#, $#, $#)") + binaryExpr(p, n, r, "", "($1.slice($2))") of mNewString: unaryExpr(p, n, r, "mnewString", "mnewString($1)") of mNewStringOfCap: - if p.target == targetJS: - unaryExpr(p, n, r, "mnewString", "mnewString(0)") - else: - unaryExpr(p, n, r, "", "''") + unaryExpr(p, n, r, "mnewString", "mnewString(0)") of mDotDot: genProcForSymIfNeeded(p, n.sons[0].sym) genCall(p, n, r) @@ -1967,11 +1755,10 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = useMagic(p, "nimParseBiggestFloat") genCall(p, n, r) of mArray: - if p.target == targetPHP: genToArray(p, n, r) - else: genCall(p, n, r) + genCall(p, n, r) else: genCall(p, n, r) - #else internalError(e.info, 'genMagic: ' + magicToStr[op]); + #else internalError(p.config, e.info, 'genMagic: ' + magicToStr[op]); proc genSetConstr(p: PProc, n: PNode, r: var TCompRes) = var @@ -1985,13 +1772,13 @@ proc genSetConstr(p: PProc, n: PNode, r: var TCompRes) = if it.kind == nkRange: gen(p, it.sons[0], a) gen(p, it.sons[1], b) - addf(r.res, "[$1, $2]" | "array($#,$#)", [a.res, b.res]) + addf(r.res, "[$1, $2]", [a.res, b.res]) else: gen(p, it, a) add(r.res, a.res) add(r.res, ")") # emit better code for constant sets: - if p.target == targetJS and isDeepConstExpr(n): + if isDeepConstExpr(n): inc(p.g.unique) let tmp = rope("ConstSet") & rope(p.g.unique) addf(p.g.constants, "var $1 = $2;$n", [tmp, r.res]) @@ -1999,25 +1786,25 @@ proc genSetConstr(p: PProc, n: PNode, r: var TCompRes) = proc genArrayConstr(p: PProc, n: PNode, r: var TCompRes) = var a: TCompRes - r.res = rope("[" | "array(") + r.res = rope("[") r.kind = resExpr for i in countup(0, sonsLen(n) - 1): if i > 0: add(r.res, ", ") gen(p, n.sons[i], a) add(r.res, a.res) - add(r.res, "]" | ")") + add(r.res, "]") proc genTupleConstr(p: PProc, n: PNode, r: var TCompRes) = var a: TCompRes - r.res = rope("{" | "array(") + r.res = rope("{") r.kind = resExpr for i in countup(0, sonsLen(n) - 1): if i > 0: add(r.res, ", ") var it = n.sons[i] if it.kind == nkExprColonExpr: it = it.sons[1] gen(p, it, a) - addf(r.res, "Field$#: $#" | "$2", [i.rope, a.res]) - r.res.add("}" | ")") + addf(r.res, "Field$#: $#", [i.rope, a.res]) + r.res.add("}") proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) = var a: TCompRes @@ -2027,11 +1814,11 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) = for i in countup(1, sonsLen(n) - 1): if i > 1: add(initList, ", ") var it = n.sons[i] - internalAssert it.kind == nkExprColonExpr + internalAssert p.config, it.kind == nkExprColonExpr let val = it.sons[1] gen(p, val, a) var f = it.sons[0].sym - if f.loc.r == nil: f.loc.r = mangleName(f, p.target) + if f.loc.r == nil: f.loc.r = mangleName(p.module, f) fieldIDs.incl(f.id) let typ = val.typ.skipTypes(abstractInst) @@ -2041,10 +1828,10 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) = else: useMagic(p, "nimCopy") a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] - addf(initList, "$#: $#" | "'$#' => $#" , [f.loc.r, a.res]) + addf(initList, "$#: $#", [f.loc.r, a.res]) let t = skipTypes(n.typ, abstractInst + skipPtrs) createObjInitList(p, t, fieldIDs, initList) - r.res = ("{$1}" | "array($#)") % [initList] + r.res = ("{$1}") % [initList] proc genConv(p: PProc, n: PNode, r: var TCompRes) = var dest = skipTypes(n.typ, abstractVarRange) @@ -2083,7 +1870,7 @@ proc convStrToCStr(p: PProc, n: PNode, r: var TCompRes) = gen(p, n.sons[0].sons[0], r) else: gen(p, n.sons[0], r) - if r.res == nil: internalError(n.info, "convStrToCStr") + if r.res == nil: internalError(p.config, n.info, "convStrToCStr") useMagic(p, "toJSStr") r.res = "toJSStr($1)" % [r.res] r.kind = resExpr @@ -2095,48 +1882,44 @@ proc convCStrToStr(p: PProc, n: PNode, r: var TCompRes) = gen(p, n.sons[0].sons[0], r) else: gen(p, n.sons[0], r) - if r.res == nil: internalError(n.info, "convCStrToStr") + if r.res == nil: internalError(p.config, n.info, "convCStrToStr") useMagic(p, "cstrToNimstr") r.res = "cstrToNimstr($1)" % [r.res] r.kind = resExpr proc genReturnStmt(p: PProc, n: PNode) = - if p.procDef == nil: internalError(n.info, "genReturnStmt") + if p.procDef == nil: internalError(p.config, n.info, "genReturnStmt") p.beforeRetNeeded = true if n.sons[0].kind != nkEmpty: genStmt(p, n.sons[0]) else: genLineDir(p, n) - lineF(p, "break BeforeRet;$n" | "goto BeforeRet;$n", []) + lineF(p, "break BeforeRet;$n", []) proc frameCreate(p: PProc; procname, filename: Rope): Rope = let frameFmt = - "var F={procname:$1,prev:framePtr,filename:$2,line:0};$n" | - "global $$framePtr; $$F=array('procname'=>$#,'prev'=>$$framePtr,'filename'=>$#,'line'=>0);$n" + "var F={procname:$1,prev:framePtr,filename:$2,line:0};$n" result = p.indentLine(frameFmt % [procname, filename]) - result.add p.indentLine(ropes.`%`("framePtr = F;$n" | "$$framePtr = &$$F;$n", [])) + result.add p.indentLine(ropes.`%`("framePtr = F;$n", [])) proc frameDestroy(p: PProc): Rope = - result = p.indentLine rope(("framePtr = F.prev;" | "$framePtr = $F['prev'];") & tnl) + result = p.indentLine rope(("framePtr = F.prev;") & "\L") proc genProcBody(p: PProc, prc: PSym): Rope = if hasFrameInfo(p): result = frameCreate(p, makeJSString(prc.owner.name.s & '.' & prc.name.s), - makeJSString(toFilename(prc.info))) + makeJSString(toFilename(p.config, prc.info))) else: result = nil if p.beforeRetNeeded: - if p.target == targetJS: - result.add p.indentLine(~"BeforeRet: do {$n") - result.add p.body - result.add p.indentLine(~"} while (false);$n") - else: - addF(result, "$# BeforeRet:;$n", [p.body]) + result.add p.indentLine(~"BeforeRet: do {$n") + result.add p.body + result.add p.indentLine(~"} while (false);$n") else: add(result, p.body) - if prc.typ.callConv == ccSysCall and p.target == targetJS: + if prc.typ.callConv == ccSysCall: result = ("try {$n$1} catch (e) {$n" & " alert(\"Unhandled exception:\\n\" + e.message + \"\\n\"$n}") % [result] if hasFrameInfo(p): @@ -2146,7 +1929,7 @@ proc optionaLine(p: Rope): Rope = if p == nil: return nil else: - return p & tnl + return p & "\L" proc genProc(oldProc: PProc, prc: PSym): Rope = var @@ -2158,13 +1941,13 @@ proc genProc(oldProc: PProc, prc: PSym): Rope = p.up = oldProc var returnStmt: Rope = nil var resultAsgn: Rope = nil - let name = mangleName(prc, p.target) + var name = mangleName(p.module, prc) let header = generateHeader(p, prc.typ) if prc.typ.sons[0] != nil and sfPure notin prc.flags: resultSym = prc.ast.sons[resultPos].sym - let mname = mangleName(resultSym, p.target) + let mname = mangleName(p.module, resultSym) let resVar = createVar(p, resultSym.typ, isIndirect(resultSym)) - resultAsgn = p.indentLine(("var $# = $#;$n" | "$$$# = $#;$n") % [mname, resVar]) + resultAsgn = p.indentLine(("var $# = $#;$n") % [mname, resVar]) if resultSym.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and mapType(p, resultSym.typ) == etyBaseIndex: resultAsgn.add p.indentLine("var $#_Idx = 0;$n" % [mname]) @@ -2188,6 +1971,18 @@ proc genProc(oldProc: PProc, prc: PSym): Rope = optionaLine(genProcBody(p, prc)), optionaLine(p.indentLine(returnStmt))] else: + result = ~"\L" + + if optHotCodeReloading in p.config.options: + # Here, we introduce thunks that create the equivalent of a jump table + # for all global functions, because references to them may be stored + # in JavaScript variables. The added indirection ensures that such + # references will end up calling the reloaded code. + var thunkName = name + name = name & "IMLP" + result.add("function $#() { return $#.apply(this, arguments); }$n" % + [thunkName, name]) + def = "function $#($#) {$n$#$#$#$#$#" % [ name, header, @@ -2198,7 +1993,6 @@ proc genProc(oldProc: PProc, prc: PSym): Rope = optionaLine(p.indentLine(returnStmt))] dec p.extraIndent - result = ~tnl result.add p.indentLine(def) result.add p.indentLine(~"}$n") @@ -2238,8 +2032,7 @@ proc genCast(p: PProc, n: PNode, r: var TCompRes) = elif fromUint: if src.size == 4 and dest.size == 4: # XXX prevent multi evaluations - r.res = "($1|0)" % [r.res] | - "($1>(float)2147483647?(int)$1-4294967296:$1)" % [r.res] + r.res = "($1|0)" % [r.res] else: let trimmer = unsignedTrimmer(dest.size) let minuend = case dest.size @@ -2275,10 +2068,12 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = r.res = rope"null" r.kind = resExpr of nkStrLit..nkTripleStrLit: - if skipTypes(n.typ, abstractVarRange).kind == tyString and - p.target == targetJS: - useMagic(p, "makeNimstrLit") - r.res = "makeNimstrLit($1)" % [makeJSString(n.strVal)] + if skipTypes(n.typ, abstractVarRange).kind == tyString: + if n.strVal.len != 0: + useMagic(p, "makeNimstrLit") + r.res = "makeNimstrLit($1)" % [makeJSString(n.strVal)] + else: + r.res = rope"[]" else: r.res = makeJSString(n.strVal, false) r.kind = resExpr @@ -2309,14 +2104,11 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = of nkClosure: gen(p, n[0], r) of nkCurly: genSetConstr(p, n, r) of nkBracket: genArrayConstr(p, n, r) - of nkPar: genTupleConstr(p, n, r) + of nkPar, nkTupleConstr: genTupleConstr(p, n, r) of nkObjConstr: genObjConstr(p, n, r) of nkHiddenStdConv, nkHiddenSubConv, nkConv: genConv(p, n, r) of nkAddr, nkHiddenAddr: - if p.target == targetJS: - genAddr(p, n, r) - else: - gen(p, n.sons[0], r) + genAddr(p, n, r) of nkDerefExpr, nkHiddenDeref: genDeref(p, n, r) of nkBracketExpr: genArrayAccess(p, n, r) of nkDotExpr: genFieldAccess(p, n, r) @@ -2332,7 +2124,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = of nkEmpty: discard of nkLambdaKinds: let s = n.sons[namePos].sym - discard mangleName(s, p.target) + discard mangleName(p.module, s) r.res = s.loc.r if lfNoDecl in s.loc.flags or s.magic != mNone: discard elif not p.g.generatedSyms.containsOrIncl(s.id): @@ -2355,7 +2147,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = of nkVarSection, nkLetSection: genVarStmt(p, n) of nkConstSection: discard of nkForStmt, nkParForStmt: - internalError(n.info, "for statement not eliminated") + internalError(p.config, n.info, "for statement not eliminated") of nkCaseStmt: genCaseJS(p, n, r) of nkReturnStmt: genReturnStmt(p, n) of nkBreakStmt: genBreakStmt(p, n) @@ -2370,7 +2162,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = of nkRaiseStmt: genRaiseStmt(p, n) of nkTypeSection, nkCommentStmt, nkIteratorDef, nkIncludeStmt, nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt, - nkFromStmt, nkTemplateDef, nkMacroDef: discard + nkFromStmt, nkTemplateDef, nkMacroDef, nkStaticStmt: discard of nkPragma: genPragma(p, n) of nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef: var s = n.sons[namePos].sym @@ -2378,60 +2170,53 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = genSym(p, n.sons[namePos], r) r.res = nil of nkGotoState, nkState: - internalError(n.info, "first class iterators not implemented") + internalError(p.config, n.info, "first class iterators not implemented") of nkPragmaBlock: gen(p, n.lastSon, r) of nkComesFrom: discard "XXX to implement for better stack traces" - else: internalError(n.info, "gen: unknown node type: " & $n.kind) + else: internalError(p.config, n.info, "gen: unknown node type: " & $n.kind) -var globals: PGlobals - -proc newModule(module: PSym): BModule = +proc newModule(g: ModuleGraph; module: PSym): BModule = new(result) result.module = module - if globals == nil: - globals = newGlobals() - -proc genHeader(target: TTarget): Rope = - if target == targetJS: - result = ( - "/* Generated by the Nim Compiler v$1 */$n" & - "/* (c) " & copyrightYear & " Andreas Rumpf */$n$n" & - "var framePtr = null;$n" & - "var excHandler = 0;$n" & - "var lastJSError = null;$n" & - "if (typeof Int8Array === 'undefined') Int8Array = Array;$n" & - "if (typeof Int16Array === 'undefined') Int16Array = Array;$n" & - "if (typeof Int32Array === 'undefined') Int32Array = Array;$n" & - "if (typeof Uint8Array === 'undefined') Uint8Array = Array;$n" & - "if (typeof Uint16Array === 'undefined') Uint16Array = Array;$n" & - "if (typeof Uint32Array === 'undefined') Uint32Array = Array;$n" & - "if (typeof Float32Array === 'undefined') Float32Array = Array;$n" & - "if (typeof Float64Array === 'undefined') Float64Array = Array;$n") % - [rope(VersionAsString)] - else: - result = ("<?php$n" & - "/* Generated by the Nim Compiler v$1 */$n" & - "/* (c) " & copyrightYear & " Andreas Rumpf */$n$n" & - "$$framePtr = null;$n" & - "$$excHandler = 0;$n" & - "$$lastJSError = null;$n") % - [rope(VersionAsString)] + result.sigConflicts = initCountTable[SigHash]() + if g.backend == nil: + g.backend = newGlobals() + result.graph = g + result.config = g.config + +proc genHeader(): Rope = + result = ( + "/* Generated by the Nim Compiler v$1 */$n" & + "/* (c) " & copyrightYear & " Andreas Rumpf */$n$n" & + "var framePtr = null;$n" & + "var excHandler = 0;$n" & + "var lastJSError = null;$n" & + "if (typeof Int8Array === 'undefined') Int8Array = Array;$n" & + "if (typeof Int16Array === 'undefined') Int16Array = Array;$n" & + "if (typeof Int32Array === 'undefined') Int32Array = Array;$n" & + "if (typeof Uint8Array === 'undefined') Uint8Array = Array;$n" & + "if (typeof Uint16Array === 'undefined') Uint16Array = Array;$n" & + "if (typeof Uint32Array === 'undefined') Uint32Array = Array;$n" & + "if (typeof Float32Array === 'undefined') Float32Array = Array;$n" & + "if (typeof Float64Array === 'undefined') Float64Array = Array;$n") % + [rope(VersionAsString)] proc genModule(p: PProc, n: PNode) = if optStackTrace in p.options: add(p.body, frameCreate(p, makeJSString("module " & p.module.module.name.s), - makeJSString(toFilename(p.module.module.info)))) + makeJSString(toFilename(p.config, p.module.module.info)))) genStmt(p, n) if optStackTrace in p.options: add(p.body, frameDestroy(p)) proc myProcess(b: PPassContext, n: PNode): PNode = - if passes.skipCodegen(n): return n result = n - var m = BModule(b) - if m.module == nil: internalError(n.info, "myProcess") + let m = BModule(b) + if passes.skipCodegen(m.config, n): return n + if m.module == nil: internalError(m.config, n.info, "myProcess") + let globals = PGlobals(m.graph.backend) var p = newProc(globals, m, nil, m.module.options) p.unique = globals.unique genModule(p, n) @@ -2439,6 +2224,7 @@ proc myProcess(b: PPassContext, n: PNode): PNode = add(p.g.code, p.body) proc wholeCode(graph: ModuleGraph; m: BModule): Rope = + let globals = PGlobals(graph.backend) for prc in globals.forwarded: if not globals.generatedSyms.containsOrIncl(prc.id): var p = newProc(globals, m, nil, m.module.options) @@ -2458,11 +2244,11 @@ proc getClassName(t: PType): Rope = if s.isNil or sfAnon in s.flags: s = skipTypes(t, abstractPtrs).sym if s.isNil or sfAnon in s.flags: - internalError("cannot retrieve class name") + doAssert(false, "cannot retrieve class name") if s.loc.r != nil: result = s.loc.r else: result = rope(s.name.s) -proc genClass(obj: PType; content: Rope; ext: string) = +proc genClass(conf: ConfigRef; obj: PType; content: Rope; ext: string) = let cls = getClassName(obj) let t = skipTypes(obj, abstractPtrs) let extends = if t.kind == tyObject and t.sons[0] != nil: @@ -2475,36 +2261,31 @@ proc genClass(obj: PType; content: Rope; ext: string) = "class $#$# {$n$#$n}$n") % [rope(VersionAsString), cls, extends, content] - let outfile = changeFileExt(completeCFilePath($cls), ext) + let outfile = changeFileExt(completeCFilePath(conf, $cls), ext) discard writeRopeIfNotEqual(result, outfile) proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = - if passes.skipCodegen(n): return n result = myProcess(b, n) var m = BModule(b) + if passes.skipCodegen(m.config, n): return n if sfMainModule in m.module.flags: - let ext = if m.target == targetJS: "js" else: "php" - let f = if globals.classes.len == 0: m.module.filename + let globals = PGlobals(graph.backend) + let ext = "js" + let f = if globals.classes.len == 0: toFilename(m.config, FileIndex m.module.position) else: "nimsystem" let code = wholeCode(graph, m) let outfile = - if options.outFile.len > 0: - if options.outFile.isAbsolute: options.outFile - else: getCurrentDir() / options.outFile + if m.config.outFile.len > 0: + if m.config.outFile.isAbsolute: m.config.outFile + else: getCurrentDir() / m.config.outFile else: - changeFileExt(completeCFilePath(f), ext) - discard writeRopeIfNotEqual(genHeader(m.target) & code, outfile) + changeFileExt(completeCFilePath(m.config, f), ext) + discard writeRopeIfNotEqual(genHeader() & code, outfile) for obj, content in items(globals.classes): - genClass(obj, content, ext) + genClass(m.config, obj, content, ext) -proc myOpenCached(graph: ModuleGraph; s: PSym, rd: PRodReader): PPassContext = - internalError("symbol files are not possible with the JS code generator") - result = nil - -proc myOpen(graph: ModuleGraph; s: PSym; cache: IdentCache): PPassContext = - var r = newModule(s) - r.target = if gCmd == cmdCompileToPHP: targetPHP else: targetJS - result = r +proc myOpen(graph: ModuleGraph; s: PSym): PPassContext = + result = newModule(graph, s) -const JSgenPass* = makePass(myOpen, myOpenCached, myProcess, myClose) +const JSgenPass* = makePass(myOpen, myProcess, myClose) diff --git a/compiler/jstypes.nim b/compiler/jstypes.nim index 3768acf27..d86b09a03 100644 --- a/compiler/jstypes.nim +++ b/compiler/jstypes.nim @@ -25,7 +25,7 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope = else: s = nil for i in countup(0, length - 1): - if i > 0: add(s, ", " & tnl) + if i > 0: add(s, ", \L") add(s, genObjectFields(p, typ, n.sons[i])) result = ("{kind: 2, len: $1, offset: 0, " & "typ: null, name: null, sons: [$2]}") % [rope(length), s] @@ -34,11 +34,11 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope = s = genTypeInfo(p, field.typ) result = ("{kind: 1, offset: \"$1\", len: 0, " & "typ: $2, name: $3, sons: null}") % - [mangleName(field, p.target), s, + [mangleName(p.module, field), s, makeJSString(field.name.s)] of nkRecCase: length = sonsLen(n) - if (n.sons[0].kind != nkSym): internalError(n.info, "genObjectFields") + if (n.sons[0].kind != nkSym): internalError(p.config, n.info, "genObjectFields") field = n.sons[0].sym s = genTypeInfo(p, field.typ) for i in countup(1, length - 1): @@ -47,7 +47,7 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope = case b.kind of nkOfBranch: if sonsLen(b) < 2: - internalError(b.info, "genObjectFields; nkOfBranch broken") + internalError(p.config, b.info, "genObjectFields; nkOfBranch broken") for j in countup(0, sonsLen(b) - 2): if u != nil: add(u, ", ") if b.sons[j].kind == nkRange: @@ -56,16 +56,16 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope = else: add(u, rope(getOrdValue(b.sons[j]))) of nkElse: - u = rope(lengthOrd(field.typ)) - else: internalError(n.info, "genObjectFields(nkRecCase)") - if result != nil: add(result, ", " & tnl) + u = rope(lengthOrd(p.config, field.typ)) + else: internalError(p.config, n.info, "genObjectFields(nkRecCase)") + if result != nil: add(result, ", \L") addf(result, "[setConstr($1), $2]", [u, genObjectFields(p, typ, lastSon(b))]) result = ("{kind: 3, offset: \"$1\", len: $3, " & "typ: $2, name: $4, sons: [$5]}") % [ - mangleName(field, p.target), s, - rope(lengthOrd(field.typ)), makeJSString(field.name.s), result] - else: internalError(n.info, "genObjectFields") + mangleName(p.module, field), s, + rope(lengthOrd(p.config, field.typ)), makeJSString(field.name.s), result] + else: internalError(p.config, n.info, "genObjectFields") proc objHasTypeField(t: PType): bool {.inline.} = tfInheritable in t.flags or t.sons[0] != nil @@ -85,7 +85,7 @@ proc genObjectInfo(p: PProc, typ: PType, name: Rope) = proc genTupleFields(p: PProc, typ: PType): Rope = var s: Rope = nil for i in 0 ..< typ.len: - if i > 0: add(s, ", " & tnl) + if i > 0: add(s, ", \L") s.addf("{kind: 1, offset: \"Field$1\", len: 0, " & "typ: $2, name: \"Field$1\", sons: null}", [i.rope, genTypeInfo(p, typ.sons[i])]) @@ -104,9 +104,9 @@ proc genEnumInfo(p: PProc, typ: PType, name: Rope) = let length = sonsLen(typ.n) var s: Rope = nil for i in countup(0, length - 1): - if (typ.n.sons[i].kind != nkSym): internalError(typ.n.info, "genEnumInfo") + if (typ.n.sons[i].kind != nkSym): internalError(p.config, typ.n.info, "genEnumInfo") let field = typ.n.sons[i].sym - if i > 0: add(s, ", " & tnl) + if i > 0: add(s, ", \L") let extName = if field.ast == nil: field.name.s else: field.ast.strVal addf(s, "\"$1\": {kind: 1, offset: $1, typ: $2, name: $3, len: 0, sons: null}", [rope(field.position), name, makeJSString(extName)]) @@ -121,26 +121,7 @@ proc genEnumInfo(p: PProc, typ: PType, name: Rope) = addf(p.g.typeInfo, "$1.base = $2;$n", [name, genTypeInfo(p, typ.sons[0])]) -proc genEnumInfoPHP(p: PProc; t: PType): Rope = - let t = t.skipTypes({tyGenericInst, tyDistinct, tyAlias, tySink}) - result = "$$NTI$1" % [rope(t.id)] - p.declareGlobal(t.id, result) - if containsOrIncl(p.g.typeInfoGenerated, t.id): return - - let length = sonsLen(t.n) - var s: Rope = nil - for i in countup(0, length - 1): - if (t.n.sons[i].kind != nkSym): internalError(t.n.info, "genEnumInfo") - let field = t.n.sons[i].sym - if i > 0: add(s, ", " & tnl) - let extName = if field.ast == nil: field.name.s else: field.ast.strVal - addf(s, "$# => $#$n", - [rope(field.position), makeJSString(extName)]) - prepend(p.g.typeInfo, "$$$# = $#;$n" % [result, s]) - proc genTypeInfo(p: PProc, typ: PType): Rope = - if p.target == targetPHP: - return makeJSString(typeToString(typ, preferModuleInfo)) let t = typ.skipTypes({tyGenericInst, tyDistinct, tyAlias, tySink}) result = "NTI$1" % [rope(t.id)] if containsOrIncl(p.g.typeInfoGenerated, t.id): return @@ -171,5 +152,5 @@ proc genTypeInfo(p: PProc, typ: PType): Rope = of tyTuple: genTupleInfo(p, t, result) of tyStatic: if t.n != nil: result = genTypeInfo(p, lastSon t) - else: internalError("genTypeInfo(" & $t.kind & ')') - else: internalError("genTypeInfo(" & $t.kind & ')') + else: internalError(p.config, "genTypeInfo(" & $t.kind & ')') + else: internalError(p.config, "genTypeInfo(" & $t.kind & ')') diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index fca5ef52e..d8c0461ce 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -7,11 +7,11 @@ # distribution, for details about the copyright. # -# This include file implements lambda lifting for the transformator. +# This file implements lambda lifting for the transformator. import - intsets, strutils, options, ast, astalgo, trees, treetab, msgs, os, - idents, renderer, types, magicsys, rodread, lowerings, tables + intsets, strutils, options, ast, astalgo, trees, treetab, msgs, + idents, renderer, types, magicsys, lowerings, tables, modulegraphs, lineinfos discard """ The basic approach is that captured vars need to be put on the heap and @@ -125,32 +125,32 @@ proc newCall(a: PSym, b: PNode): PNode = result.add newSymNode(a) result.add b -proc createStateType(iter: PSym): PType = +proc createClosureIterStateType*(g: ModuleGraph; iter: PSym): PType = var n = newNodeI(nkRange, iter.info) addSon(n, newIntNode(nkIntLit, -1)) addSon(n, newIntNode(nkIntLit, 0)) result = newType(tyRange, iter) result.n = n - var intType = nilOrSysInt() + var intType = nilOrSysInt(g) if intType.isNil: intType = newType(tyInt, iter) rawAddSon(result, intType) -proc createStateField(iter: PSym): PSym = - result = newSym(skField, getIdent(":state"), iter, iter.info) - result.typ = createStateType(iter) +proc createStateField(g: ModuleGraph; iter: PSym): PSym = + result = newSym(skField, getIdent(g.cache, ":state"), iter, iter.info) + result.typ = createClosureIterStateType(g, iter) -proc createEnvObj(owner: PSym; info: TLineInfo): PType = +proc createEnvObj(g: ModuleGraph; owner: PSym; info: TLineInfo): PType = # YYY meh, just add the state field for every closure for now, it's too # hard to figure out if it comes from a closure iterator: - result = createObj(owner, info, final=false) - rawAddField(result, createStateField(owner)) + result = createObj(g, owner, info, final=false) + rawAddField(result, createStateField(g, owner)) -proc getIterResult(iter: PSym): PSym = +proc getClosureIterResult*(g: ModuleGraph; iter: PSym): PSym = if resultPos < iter.ast.len: result = iter.ast.sons[resultPos].sym else: # XXX a bit hacky: - result = newSym(skResult, getIdent":result", iter, iter.info) + result = newSym(skResult, getIdent(g.cache, ":result"), iter, iter.info, {}) result.typ = iter.typ.sons[0] incl(result.flags, sfUsed) iter.ast.add newSymNode(result) @@ -166,7 +166,7 @@ proc addHiddenParam(routine: PSym, param: PSym) = assert sfFromGeneric in param.flags #echo "produced environment: ", param.id, " for ", routine.id -proc getHiddenParam(routine: PSym): PSym = +proc getHiddenParam(g: ModuleGraph; routine: PSym): PSym = let params = routine.ast.sons[paramsPos] let hidden = lastSon(params) if hidden.kind == nkSym and hidden.sym.kind == skParam and hidden.sym.name.s == paramName: @@ -174,7 +174,7 @@ proc getHiddenParam(routine: PSym): PSym = assert sfFromGeneric in result.flags else: # writeStackTrace() - localError(routine.info, "internal error: could not find env param for " & routine.name.s) + localError(g.config, routine.info, "internal error: could not find env param for " & routine.name.s) result = routine proc getEnvParam*(routine: PSym): PSym = @@ -186,7 +186,8 @@ proc getEnvParam*(routine: PSym): PSym = proc interestingVar(s: PSym): bool {.inline.} = result = s.kind in {skVar, skLet, skTemp, skForVar, skParam, skResult} and - sfGlobal notin s.flags + sfGlobal notin s.flags and + s.typ.kind notin {tyStatic, tyTypeDesc} proc illegalCapture(s: PSym): bool {.inline.} = result = skipTypes(s.typ, abstractInst).kind in @@ -207,43 +208,43 @@ proc newAsgnStmt(le, ri: PNode, info: TLineInfo): PNode = result.sons[0] = le result.sons[1] = ri -proc makeClosure*(prc: PSym; env: PNode; info: TLineInfo): PNode = +proc makeClosure*(g: ModuleGraph; prc: PSym; env: PNode; info: TLineInfo): PNode = result = newNodeIT(nkClosure, info, prc.typ) result.add(newSymNode(prc)) if env == nil: - result.add(newNodeIT(nkNilLit, info, getSysType(tyNil))) + result.add(newNodeIT(nkNilLit, info, getSysType(g, info, tyNil))) else: if env.skipConv.kind == nkClosure: - localError(info, "internal error: taking closure of closure") + localError(g.config, info, "internal error: taking closure of closure") result.add(env) proc interestingIterVar(s: PSym): bool {.inline.} = # XXX optimization: Only lift the variable if it lives across # yield/return boundaries! This can potentially speed up # closure iterators quite a bit. - result = s.kind in {skVar, skLet, skTemp, skForVar} and sfGlobal notin s.flags + result = s.kind in {skResult, skVar, skLet, skTemp, skForVar} and sfGlobal notin s.flags template isIterator*(owner: PSym): bool = owner.kind == skIterator and owner.typ.callConv == ccClosure -proc liftingHarmful(owner: PSym): bool {.inline.} = +proc liftingHarmful(conf: ConfigRef; owner: PSym): bool {.inline.} = ## lambda lifting can be harmful for JS-like code generators. let isCompileTime = sfCompileTime in owner.flags or owner.kind == skMacro - result = gCmd in {cmdCompileToPHP, cmdCompileToJS} and not isCompileTime + result = conf.cmd == cmdCompileToJS and not isCompileTime -proc liftIterSym*(n: PNode; owner: PSym): PNode = +proc liftIterSym*(g: ModuleGraph; n: PNode; owner: PSym): PNode = # transforms (iter) to (let env = newClosure[iter](); (iter, env)) - if liftingHarmful(owner): return n + if liftingHarmful(g.config, owner): return n let iter = n.sym assert iter.isIterator result = newNodeIT(nkStmtListExpr, n.info, n.typ) - let hp = getHiddenParam(iter) + let hp = getHiddenParam(g, iter) var env: PNode if owner.isIterator: - let it = getHiddenParam(owner) - addUniqueField(it.typ.sons[0], hp) + let it = getHiddenParam(g, owner) + addUniqueField(it.typ.sons[0], hp, g.cache) env = indirectAccess(newSymNode(it), hp, hp.info) else: let e = newSym(skLet, iter.name, owner, n.info) @@ -254,13 +255,13 @@ proc liftIterSym*(n: PNode; owner: PSym): PNode = addVar(v, env) result.add(v) # add 'new' statement: - result.add newCall(getSysSym"internalNew", env) - result.add makeClosure(iter, env, n.info) + result.add newCall(getSysSym(g, n.info, "internalNew"), env) + result.add makeClosure(g, iter, env, n.info) -proc freshVarForClosureIter*(s, owner: PSym): PNode = - let envParam = getHiddenParam(owner) +proc freshVarForClosureIter*(g: ModuleGraph; s, owner: PSym): PNode = + let envParam = getHiddenParam(g, owner) let obj = envParam.typ.lastSon - addField(obj, s) + addField(obj, s, g.cache) var access = newSymNode(envParam) assert obj.kind == tyObject @@ -268,15 +269,19 @@ proc freshVarForClosureIter*(s, owner: PSym): PNode = if field != nil: result = rawIndirectAccess(access, field, s.info) else: - localError(s.info, "internal error: cannot generate fresh variable") + localError(g.config, s.info, "internal error: cannot generate fresh variable") result = access # ------------------ new stuff ------------------------------------------- -proc markAsClosure(owner: PSym; n: PNode) = +proc markAsClosure(g: ModuleGraph; owner: PSym; n: PNode) = let s = n.sym - if illegalCapture(s) or owner.typ.callConv notin {ccClosure, ccDefault}: - localError(n.info, errIllegalCaptureX, s.name.s) + if illegalCapture(s): + localError(g.config, n.info, "illegal capture '$1' of type <$2> which is declared here: $3" % + [s.name.s, typeToString(s.typ), g.config$s.info]) + elif owner.typ.callConv notin {ccClosure, ccDefault}: + localError(g.config, n.info, "illegal capture '$1' because '$2' has the calling convention: <$3>" % + [s.name.s, owner.name.s, CallingConvToStr[owner.typ.callConv]]) incl(owner.typ.flags, tfCapturesEnv) owner.typ.callConv = ccClosure @@ -285,12 +290,14 @@ type processed, capturedVars: IntSet ownerToType: Table[int, PType] somethingToDo: bool + graph: ModuleGraph -proc initDetectionPass(fn: PSym): DetectionPass = +proc initDetectionPass(g: ModuleGraph; fn: PSym): DetectionPass = result.processed = initIntSet() result.capturedVars = initIntSet() result.ownerToType = initTable[int, PType]() result.processed.incl(fn.id) + result.graph = g discard """ proc outer = @@ -307,7 +314,7 @@ proc getEnvTypeForOwner(c: var DetectionPass; owner: PSym; result = c.ownerToType.getOrDefault(owner.id) if result.isNil: result = newType(tyRef, owner) - let obj = createEnvObj(owner, info) + let obj = createEnvObj(c.graph, owner, info) rawAddSon(result, obj) c.ownerToType[owner.id] = result @@ -316,13 +323,13 @@ proc createUpField(c: var DetectionPass; dest, dep: PSym; info: TLineInfo) = let obj = refObj.lastSon let fieldType = c.getEnvTypeForOwner(dep, info) #getHiddenParam(dep).typ if refObj == fieldType: - localError(dep.info, "internal error: invalid up reference computed") + localError(c.graph.config, dep.info, "internal error: invalid up reference computed") - let upIdent = getIdent(upName) + let upIdent = getIdent(c.graph.cache, upName) let upField = lookupInRecord(obj.n, upIdent) if upField != nil: if upField.typ != fieldType: - localError(dep.info, "internal error: up references do not agree") + localError(c.graph.config, dep.info, "internal error: up references do not agree") else: let result = newSym(skField, upIdent, obj.owner, obj.owner.info) result.typ = fieldType @@ -359,12 +366,12 @@ proc addClosureParam(c: var DetectionPass; fn: PSym; info: TLineInfo) = let owner = if fn.kind == skIterator: fn else: fn.skipGenericOwner let t = c.getEnvTypeForOwner(owner, info) if cp == nil: - cp = newSym(skParam, getIdent(paramName), fn, fn.info) + cp = newSym(skParam, getIdent(c.graph.cache, paramName), fn, fn.info) incl(cp.flags, sfFromGeneric) cp.typ = t addHiddenParam(fn, cp) elif cp.typ != t and fn.kind != skIterator: - localError(fn.info, "internal error: inconsistent environment type") + localError(c.graph.config, fn.info, "internal error: inconsistent environment type") #echo "adding closure to ", fn.name.s proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) = @@ -390,9 +397,13 @@ proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) = addClosureParam(c, owner, n.info) if interestingIterVar(s): if not c.capturedVars.containsOrIncl(s.id): - let obj = getHiddenParam(owner).typ.lastSon + let obj = getHiddenParam(c.graph, owner).typ.lastSon #let obj = c.getEnvTypeForOwner(s.owner).lastSon - addField(obj, s) + + if s.name.id == getIdent(c.graph.cache, ":state").id: + obj.n[0].sym.id = -s.id + else: + addField(obj, s, c.graph.cache) # but always return because the rest of the proc is only relevant when # ow != owner: return @@ -410,14 +421,14 @@ proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) = """ # mark 'owner' as taking a closure: c.somethingToDo = true - markAsClosure(owner, n) + markAsClosure(c.graph, owner, n) addClosureParam(c, owner, n.info) #echo "capturing ", n.info # variable 's' is actually captured: if interestingVar(s) and not c.capturedVars.containsOrIncl(s.id): let obj = c.getEnvTypeForOwner(ow, n.info).lastSon #getHiddenParam(owner).typ.lastSon - addField(obj, s) + addField(obj, s, c.graph.cache) # create required upFields: var w = owner.skipGenericOwner if isInnerProc(w) or owner.isIterator: @@ -435,7 +446,7 @@ proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) = """ let up = w.skipGenericOwner #echo "up for ", w.name.s, " up ", up.name.s - markAsClosure(w, n) + markAsClosure(c.graph, w, n) addClosureParam(c, w, n.info) # , ow createUpField(c, w, up, n.info) w = up @@ -447,6 +458,10 @@ proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) = of nkLambdaKinds, nkIteratorDef, nkFuncDef: if n.typ != nil: detectCapturedVars(n[namePos], owner, c) + of nkReturnStmt: + if n[0].kind in {nkAsgn, nkFastAsgn}: + detectCapturedVars(n[0].sons[1], owner, c) + else: assert n[0].kind == nkEmpty else: for i in 0..<n.len: detectCapturedVars(n[i], owner, c) @@ -462,10 +477,10 @@ proc initLiftingPass(fn: PSym): LiftingPass = result.processed.incl(fn.id) result.envVars = initTable[int, PNode]() -proc accessViaEnvParam(n: PNode; owner: PSym): PNode = +proc accessViaEnvParam(g: ModuleGraph; n: PNode; owner: PSym): PNode = let s = n.sym # Type based expression construction for simplicity: - let envParam = getHiddenParam(owner) + let envParam = getHiddenParam(g, owner) if not envParam.isNil: var access = newSymNode(envParam) while true: @@ -474,14 +489,14 @@ proc accessViaEnvParam(n: PNode; owner: PSym): PNode = let field = getFieldFromObj(obj, s) if field != nil: return rawIndirectAccess(access, field, n.info) - let upField = lookupInRecord(obj.n, getIdent(upName)) + let upField = lookupInRecord(obj.n, getIdent(g.cache, upName)) if upField == nil: break access = rawIndirectAccess(access, upField, n.info) - localError(n.info, "internal error: environment misses: " & s.name.s) + localError(g.config, n.info, "internal error: environment misses: " & s.name.s) result = n -proc newEnvVar(owner: PSym; typ: PType): PNode = - var v = newSym(skVar, getIdent(envName), owner, owner.info) +proc newEnvVar(cache: IdentCache; owner: PSym; typ: PType): PNode = + var v = newSym(skVar, getIdent(cache, envName), owner, owner.info) incl(v.flags, sfShadowed) v.typ = typ result = newSymNode(v) @@ -496,22 +511,22 @@ proc newEnvVar(owner: PSym; typ: PType): PNode = proc setupEnvVar(owner: PSym; d: DetectionPass; c: var LiftingPass): PNode = if owner.isIterator: - return getHiddenParam(owner).newSymNode + return getHiddenParam(d.graph, owner).newSymNode result = c.envvars.getOrDefault(owner.id) if result.isNil: let envVarType = d.ownerToType.getOrDefault(owner.id) if envVarType.isNil: - localError owner.info, "internal error: could not determine closure type" - result = newEnvVar(owner, envVarType) + localError d.graph.config, owner.info, "internal error: could not determine closure type" + result = newEnvVar(d.graph.cache, owner, envVarType) c.envVars[owner.id] = result -proc getUpViaParam(owner: PSym): PNode = - let p = getHiddenParam(owner) +proc getUpViaParam(g: ModuleGraph; owner: PSym): PNode = + let p = getHiddenParam(g, owner) result = p.newSymNode if owner.isIterator: - let upField = lookupInRecord(p.typ.lastSon.n, getIdent(upName)) + let upField = lookupInRecord(p.typ.lastSon.n, getIdent(g.cache, upName)) if upField == nil: - localError(owner.info, "could not find up reference for closure iter") + localError(g.config, owner.info, "could not find up reference for closure iter") else: result = rawIndirectAccess(result, upField, p.info) @@ -521,7 +536,7 @@ proc rawClosureCreation(owner: PSym; var env: PNode if owner.isIterator: - env = getHiddenParam(owner).newSymNode + env = getHiddenParam(d.graph, owner).newSymNode else: env = setupEnvVar(owner, d, c) if env.kind == nkSym: @@ -529,7 +544,7 @@ proc rawClosureCreation(owner: PSym; addVar(v, env) result.add(v) # add 'new' statement: - result.add(newCall(getSysSym"internalNew", env)) + result.add(newCall(getSysSym(d.graph, env.info, "internalNew"), env)) # add assignment statements for captured parameters: for i in 1..<owner.typ.n.len: let local = owner.typ.n[i].sym @@ -538,9 +553,9 @@ proc rawClosureCreation(owner: PSym; # add ``env.param = param`` result.add(newAsgnStmt(fieldAccess, newSymNode(local), env.info)) - let upField = lookupInRecord(env.typ.lastSon.n, getIdent(upName)) + let upField = lookupInRecord(env.typ.lastSon.n, getIdent(d.graph.cache, upName)) if upField != nil: - let up = getUpViaParam(owner) + let up = getUpViaParam(d.graph, owner) if up != nil and upField.typ == up.typ: result.add(newAsgnStmt(rawIndirectAccess(env, upField, env.info), up, env.info)) @@ -548,36 +563,36 @@ proc rawClosureCreation(owner: PSym; # result.add(newAsgnStmt(rawIndirectAccess(env, upField, env.info), # oldenv, env.info)) else: - localError(env.info, "internal error: cannot create up reference") + localError(d.graph.config, env.info, "internal error: cannot create up reference") proc closureCreationForIter(iter: PNode; d: DetectionPass; c: var LiftingPass): PNode = result = newNodeIT(nkStmtListExpr, iter.info, iter.sym.typ) let owner = iter.sym.skipGenericOwner - var v = newSym(skVar, getIdent(envName), owner, iter.info) + var v = newSym(skVar, getIdent(d.graph.cache, envName), owner, iter.info) incl(v.flags, sfShadowed) - v.typ = getHiddenParam(iter.sym).typ + v.typ = getHiddenParam(d.graph, iter.sym).typ var vnode: PNode if owner.isIterator: - let it = getHiddenParam(owner) - addUniqueField(it.typ.sons[0], v) + let it = getHiddenParam(d.graph, owner) + addUniqueField(it.typ.sons[0], v, d.graph.cache) vnode = indirectAccess(newSymNode(it), v, v.info) else: vnode = v.newSymNode var vs = newNodeI(nkVarSection, iter.info) addVar(vs, vnode) result.add(vs) - result.add(newCall(getSysSym"internalNew", vnode)) + result.add(newCall(getSysSym(d.graph, iter.info, "internalNew"), vnode)) - let upField = lookupInRecord(v.typ.lastSon.n, getIdent(upName)) + let upField = lookupInRecord(v.typ.lastSon.n, getIdent(d.graph.cache, upName)) if upField != nil: let u = setupEnvVar(owner, d, c) if u.typ == upField.typ: result.add(newAsgnStmt(rawIndirectAccess(vnode, upField, iter.info), u, iter.info)) else: - localError(iter.info, "internal error: cannot create up reference for iter") - result.add makeClosure(iter.sym, vnode, iter.info) + localError(d.graph.config, iter.info, "internal error: cannot create up reference for iter") + result.add makeClosure(d.graph, iter.sym, vnode, iter.info) proc accessViaEnvVar(n: PNode; owner: PSym; d: DetectionPass; c: var LiftingPass): PNode = @@ -587,112 +602,39 @@ proc accessViaEnvVar(n: PNode; owner: PSym; d: DetectionPass; if field != nil: result = rawIndirectAccess(access, field, n.info) else: - localError(n.info, "internal error: not part of closure object type") + localError(d.graph.config, n.info, "internal error: not part of closure object type") result = n -proc getStateField(owner: PSym): PSym = - getHiddenParam(owner).typ.sons[0].n.sons[0].sym +proc getStateField*(g: ModuleGraph; owner: PSym): PSym = + getHiddenParam(g, owner).typ.sons[0].n.sons[0].sym proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass; c: var LiftingPass): PNode -proc transformYield(n: PNode; owner: PSym; d: DetectionPass; - c: var LiftingPass): PNode = - if c.inContainer > 0: - localError(n.info, "invalid control flow: 'yield' within a constructor") - let state = getStateField(owner) - assert state != nil - assert state.typ != nil - assert state.typ.n != nil - inc state.typ.n.sons[1].intVal - let stateNo = state.typ.n.sons[1].intVal - - var stateAsgnStmt = newNodeI(nkAsgn, n.info) - stateAsgnStmt.add(rawIndirectAccess(newSymNode(getEnvParam(owner)), - state, n.info)) - stateAsgnStmt.add(newIntTypeNode(nkIntLit, stateNo, getSysType(tyInt))) - - var retStmt = newNodeI(nkReturnStmt, n.info) - if n.sons[0].kind != nkEmpty: - var a = newNodeI(nkAsgn, n.sons[0].info) - var retVal = liftCapturedVars(n.sons[0], owner, d, c) - addSon(a, newSymNode(getIterResult(owner))) - addSon(a, retVal) - retStmt.add(a) - else: - retStmt.add(emptyNode) - - var stateLabelStmt = newNodeI(nkState, n.info) - stateLabelStmt.add(newIntTypeNode(nkIntLit, stateNo, getSysType(tyInt))) - - result = newNodeI(nkStmtList, n.info) - result.add(stateAsgnStmt) - result.add(retStmt) - result.add(stateLabelStmt) - -proc transformReturn(n: PNode; owner: PSym; d: DetectionPass; - c: var LiftingPass): PNode = - let state = getStateField(owner) - result = newNodeI(nkStmtList, n.info) - var stateAsgnStmt = newNodeI(nkAsgn, n.info) - stateAsgnStmt.add(rawIndirectAccess(newSymNode(getEnvParam(owner)), - state, n.info)) - stateAsgnStmt.add(newIntTypeNode(nkIntLit, -1, getSysType(tyInt))) - result.add(stateAsgnStmt) - result.add(n) - -proc wrapIterBody(n: PNode; owner: PSym): PNode = - if not owner.isIterator: return n - when false: - # unfortunately control flow is still convoluted and we can end up - # multiple times here for the very same iterator. We shield against this - # with some rather primitive check for now: - if n.kind == nkStmtList and n.len > 0: - if n.sons[0].kind == nkGotoState: return n - if n.len > 1 and n[1].kind == nkStmtList and n[1].len > 0 and - n[1][0].kind == nkGotoState: - return n - let info = n.info - result = newNodeI(nkStmtList, info) - var gs = newNodeI(nkGotoState, info) - gs.add(rawIndirectAccess(newSymNode(owner.getHiddenParam), getStateField(owner), info)) - result.add(gs) - var state0 = newNodeI(nkState, info) - state0.add(newIntNode(nkIntLit, 0)) - result.add(state0) - - result.add(n) - - var stateAsgnStmt = newNodeI(nkAsgn, info) - stateAsgnStmt.add(rawIndirectAccess(newSymNode(owner.getHiddenParam), - getStateField(owner), info)) - stateAsgnStmt.add(newIntTypeNode(nkIntLit, -1, getSysType(tyInt))) - result.add(stateAsgnStmt) - proc symToClosure(n: PNode; owner: PSym; d: DetectionPass; c: var LiftingPass): PNode = let s = n.sym if s == owner: # recursive calls go through (lambda, hiddenParam): - let available = getHiddenParam(owner) - result = makeClosure(s, available.newSymNode, n.info) + let available = getHiddenParam(d.graph, owner) + result = makeClosure(d.graph, s, available.newSymNode, n.info) elif s.isIterator: result = closureCreationForIter(n, d, c) elif s.skipGenericOwner == owner: # direct dependency, so use the outer's env variable: - result = makeClosure(s, setupEnvVar(owner, d, c), n.info) + result = makeClosure(d.graph, s, setupEnvVar(owner, d, c), n.info) else: - let available = getHiddenParam(owner) - let wanted = getHiddenParam(s).typ + let available = getHiddenParam(d.graph, owner) + let wanted = getHiddenParam(d.graph, s).typ # ugh: call through some other inner proc; var access = newSymNode(available) while true: if access.typ == wanted: - return makeClosure(s, access, n.info) + return makeClosure(d.graph, s, access, n.info) let obj = access.typ.sons[0] - let upField = lookupInRecord(obj.n, getIdent(upName)) + let upField = lookupInRecord(obj.n, getIdent(d.graph.cache, upName)) if upField == nil: - localError(n.info, "internal error: no environment found") + localError(d.graph.config, n.info, "internal error: no environment found") return n access = rawIndirectAccess(access, upField, n.info) @@ -708,7 +650,7 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass; # echo renderTree(s.getBody, {renderIds}) let oldInContainer = c.inContainer c.inContainer = 0 - let body = wrapIterBody(liftCapturedVars(s.getBody, s, d, c), s) + var body = liftCapturedVars(s.getBody, s, d, c) if c.envvars.getOrDefault(s.id).isNil: s.ast.sons[bodyPos] = body else: @@ -718,9 +660,9 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass; result = symToClosure(n, owner, d, c) elif s.id in d.capturedVars: if s.owner != owner: - result = accessViaEnvParam(n, owner) + result = accessViaEnvParam(d.graph, n, owner) elif owner.isIterator and interestingIterVar(s): - result = accessViaEnvParam(n, owner) + result = accessViaEnvParam(d.graph, n, owner) else: result = accessViaEnvVar(n, owner, d, c) of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, nkComesFrom, @@ -749,13 +691,16 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass; if n.len == 2: n.sons[1] = liftCapturedVars(n[1], owner, d, c) if n[1].kind == nkClosure: result = n[1] + of nkReturnStmt: + if n[0].kind in {nkAsgn, nkFastAsgn}: + # we have a `result = result` expression produced by the closure + # transform, let's not touch the LHS in order to make the lifting pass + # correct when `result` is lifted + n[0].sons[1] = liftCapturedVars(n[0].sons[1], owner, d, c) + else: assert n[0].kind == nkEmpty else: if owner.isIterator: - if n.kind == nkYieldStmt: - return transformYield(n, owner, d, c) - elif n.kind == nkReturnStmt: - return transformReturn(n, owner, d, c) - elif nfLL in n.flags: + if nfLL in n.flags: # special case 'when nimVm' due to bug #3636: n.sons[1] = liftCapturedVars(n[1], owner, d, c) return @@ -786,8 +731,8 @@ proc semCaptureSym*(s, owner: PSym) = # since the analysis is not entirely correct, we don't set 'tfCapturesEnv' # here -proc liftIterToProc*(fn: PSym; body: PNode; ptrType: PType): PNode = - var d = initDetectionPass(fn) +proc liftIterToProc*(g: ModuleGraph; fn: PSym; body: PNode; ptrType: PType): PNode = + var d = initDetectionPass(g, fn) var c = initLiftingPass(fn) # pretend 'fn' is a closure iterator for the analysis: let oldKind = fn.kind @@ -796,11 +741,11 @@ proc liftIterToProc*(fn: PSym; body: PNode; ptrType: PType): PNode = fn.typ.callConv = ccClosure d.ownerToType[fn.id] = ptrType detectCapturedVars(body, fn, d) - result = wrapIterBody(liftCapturedVars(body, fn, d, c), fn) + result = liftCapturedVars(body, fn, d, c) fn.kind = oldKind fn.typ.callConv = oldCC -proc liftLambdas*(fn: PSym, body: PNode; tooEarly: var bool): PNode = +proc liftLambdas*(g: ModuleGraph; fn: PSym, body: PNode; tooEarly: var bool): PNode = # XXX gCmd == cmdCompileToJS does not suffice! The compiletime stuff needs # the transformation even when compiling to JS ... @@ -808,23 +753,24 @@ proc liftLambdas*(fn: PSym, body: PNode; tooEarly: var bool): PNode = let isCompileTime = sfCompileTime in fn.flags or fn.kind == skMacro if body.kind == nkEmpty or ( - gCmd in {cmdCompileToPHP, cmdCompileToJS} and not isCompileTime) or + g.config.cmd == cmdCompileToJS and not isCompileTime) or fn.skipGenericOwner.kind != skModule: + # ignore forward declaration: result = body tooEarly = true else: - var d = initDetectionPass(fn) + var d = initDetectionPass(g, fn) detectCapturedVars(body, fn, d) if not d.somethingToDo and fn.isIterator: addClosureParam(d, fn, body.info) d.somethingToDo = true if d.somethingToDo: var c = initLiftingPass(fn) - var newBody = liftCapturedVars(body, fn, d, c) + result = liftCapturedVars(body, fn, d, c) + # echo renderTree(result, {renderIds}) if c.envvars.getOrDefault(fn.id) != nil: - newBody = newTree(nkStmtList, rawClosureCreation(fn, d, c), newBody) - result = wrapIterBody(newBody, fn) + result = newTree(nkStmtList, rawClosureCreation(fn, d, c), result) else: result = body #if fn.name.s == "get2": @@ -832,15 +778,12 @@ proc liftLambdas*(fn: PSym, body: PNode; tooEarly: var bool): PNode = # echo renderTree(result, {renderIds}) proc liftLambdasForTopLevel*(module: PSym, body: PNode): PNode = - if body.kind == nkEmpty or gCmd == cmdCompileToJS: - result = body - else: - # XXX implement it properly - result = body + # XXX implement it properly + result = body # ------------------- iterator transformation -------------------------------- -proc liftForLoop*(body: PNode; owner: PSym): PNode = +proc liftForLoop*(g: ModuleGraph; body: PNode; owner: PSym): PNode = # problem ahead: the iterator could be invoked indirectly, but then # we don't know what environment to create here: # @@ -865,13 +808,14 @@ proc liftForLoop*(body: PNode; owner: PSym): PNode = cl = createClosure() while true: let i = foo(cl) - nkBreakState(cl.state) + if (nkBreakState(cl.state)): + break ... """ - if liftingHarmful(owner): return body + if liftingHarmful(g.config, owner): return body var L = body.len if not (body.kind == nkForStmt and body[L-2].kind in nkCallKinds): - localError(body.info, "ignored invalid for loop") + localError(g.config, body.info, "ignored invalid for loop") return body var call = body[L-2] @@ -884,7 +828,7 @@ proc liftForLoop*(body: PNode; owner: PSym): PNode = # createClosure() let iter = op.sym - let hp = getHiddenParam(iter) + let hp = getHiddenParam(g, iter) env = newSym(skLet, iter.name, owner, body.info) env.typ = hp.typ env.flags = hp.flags @@ -893,7 +837,7 @@ proc liftForLoop*(body: PNode; owner: PSym): PNode = addVar(v, newSymNode(env)) result.add(v) # add 'new' statement: - result.add(newCall(getSysSym"internalNew", env.newSymNode)) + result.add(newCall(getSysSym(g, env.info, "internalNew"), env.newSymNode)) elif op.kind == nkStmtListExpr: let closure = op.lastSon if closure.kind == nkClosure: @@ -903,7 +847,7 @@ proc liftForLoop*(body: PNode; owner: PSym): PNode = var loopBody = newNodeI(nkStmtList, body.info, 3) var whileLoop = newNodeI(nkWhileStmt, body.info, 2) - whileLoop.sons[0] = newIntTypeNode(nkIntLit, 1, getSysType(tyBool)) + whileLoop.sons[0] = newIntTypeNode(nkIntLit, 1, getSysType(g, body.info, tyBool)) whileLoop.sons[1] = loopBody result.add whileLoop @@ -916,14 +860,25 @@ proc liftForLoop*(body: PNode; owner: PSym): PNode = body[i].sym.kind = skLet addSon(vpart, body[i]) - addSon(vpart, ast.emptyNode) # no explicit type + addSon(vpart, newNodeI(nkEmpty, body.info)) # no explicit type if not env.isNil: - call.sons[0] = makeClosure(call.sons[0].sym, env.newSymNode, body.info) + call.sons[0] = makeClosure(g, call.sons[0].sym, env.newSymNode, body.info) addSon(vpart, call) addSon(v2, vpart) loopBody.sons[0] = v2 var bs = newNodeI(nkBreakState, body.info) bs.addSon(call.sons[0]) - loopBody.sons[1] = bs + + let ibs = newNodeI(nkIfStmt, body.info) + let elifBranch = newNodeI(nkElifBranch, body.info) + elifBranch.add(bs) + + let br = newNodeI(nkBreakStmt, body.info) + br.add(g.emptyNode) + + elifBranch.add(br) + ibs.add(elifBranch) + + loopBody.sons[1] = ibs loopBody.sons[2] = body[L-1] diff --git a/compiler/layouter.nim b/compiler/layouter.nim new file mode 100644 index 000000000..36ad08696 --- /dev/null +++ b/compiler/layouter.nim @@ -0,0 +1,274 @@ +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Layouter for nimpretty. + +import idents, lexer, lineinfos, llstream, options, msgs, strutils +from os import changeFileExt + +const + MaxLineLen = 80 + LineCommentColumn = 30 + +type + SplitKind = enum + splitComma, splitParLe, splitAnd, splitOr, splitIn, splitBinary + + SemicolonKind = enum + detectSemicolonKind, useSemicolon, dontTouch + + Emitter* = object + config: ConfigRef + fid: FileIndex + lastTok: TTokType + inquote: bool + semicolons: SemicolonKind + col, lastLineNumber, lineSpan, indentLevel, indWidth: int + nested: int + doIndentMore*: int + content: string + indentStack: seq[int] + fixedUntil: int # marks where we must not go in the content + altSplitPos: array[SplitKind, int] # alternative split positions + +proc openEmitter*(em: var Emitter, cache: IdentCache; + config: ConfigRef, fileIdx: FileIndex) = + let fullPath = config.toFullPath(fileIdx) + em.indWidth = getIndentWidth(fileIdx, llStreamOpen(fullPath, fmRead), + cache, config) + if em.indWidth == 0: em.indWidth = 2 + em.config = config + em.fid = fileIdx + em.lastTok = tkInvalid + em.inquote = false + em.col = 0 + em.content = newStringOfCap(16_000) + em.indentStack = newSeqOfCap[int](30) + em.indentStack.add 0 + +proc closeEmitter*(em: var Emitter) = + var f = llStreamOpen(em.config.outFile, fmWrite) + if f == nil: + rawMessage(em.config, errGenerated, "cannot open file: " & em.config.outFile) + f.llStreamWrite em.content + llStreamClose(f) + +proc countNewlines(s: string): int = + result = 0 + for i in 0..<s.len: + if s[i] == '\L': inc result + +proc calcCol(em: var Emitter; s: string) = + var i = s.len-1 + em.col = 0 + while i >= 0 and s[i] != '\L': + dec i + inc em.col + +template wr(x) = + em.content.add x + inc em.col, x.len + +template goodCol(col): bool = col in 40..MaxLineLen + +const + openPars = {tkParLe, tkParDotLe, + tkBracketLe, tkBracketLeColon, tkCurlyDotLe, + tkCurlyLe} + splitters = openPars + {tkComma, tkSemicolon} + oprSet = {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs, + tkIsnot, tkNot, tkOf, tkAs, tkDotDot, tkAnd, tkOr, tkXor} + +template rememberSplit(kind) = + if goodCol(em.col): + em.altSplitPos[kind] = em.content.len + +template moreIndent(em): int = + (if em.doIndentMore > 0: em.indWidth*2 else: em.indWidth) + +proc softLinebreak(em: var Emitter, lit: string) = + # XXX Use an algorithm that is outlined here: + # https://llvm.org/devmtg/2013-04/jasper-slides.pdf + # +2 because we blindly assume a comma or ' &' might follow + if not em.inquote and em.col+lit.len+2 >= MaxLineLen: + if em.lastTok in splitters: + while em.content.len > 0 and em.content[em.content.high] == ' ': + setLen(em.content, em.content.len-1) + wr("\L") + em.col = 0 + for i in 1..em.indentLevel+moreIndent(em): wr(" ") + else: + # search backwards for a good split position: + for a in em.altSplitPos: + if a > em.fixedUntil: + var spaces = 0 + while a+spaces < em.content.len and em.content[a+spaces] == ' ': + inc spaces + if spaces > 0: delete(em.content, a, a+spaces-1) + let ws = "\L" & repeat(' ',em.indentLevel+moreIndent(em)) + em.col = em.content.len - a + em.content.insert(ws, a) + break + +proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = + + template endsInWhite(em): bool = + em.content.len == 0 or em.content[em.content.high] in {' ', '\L'} + template endsInAlpha(em): bool = + em.content.len > 0 and em.content[em.content.high] in SymChars+{'_'} + + proc emitComment(em: var Emitter; tok: TToken) = + let lit = strip fileSection(em.config, em.fid, tok.commentOffsetA, tok.commentOffsetB) + em.lineSpan = countNewlines(lit) + if em.lineSpan > 0: calcCol(em, lit) + if not endsInWhite(em): + wr(" ") + if em.lineSpan == 0 and max(em.col, LineCommentColumn) + lit.len <= MaxLineLen: + for i in 1 .. LineCommentColumn - em.col: wr(" ") + wr lit + + var preventComment = false + if tok.tokType == tkComment and tok.line == em.lastLineNumber and tok.indent >= 0: + # we have an inline comment so handle it before the indentation token: + emitComment(em, tok) + preventComment = true + em.fixedUntil = em.content.high + + elif tok.indent >= 0: + if em.lastTok in (splitters + oprSet): + em.indentLevel = tok.indent + else: + if tok.indent > em.indentStack[^1]: + em.indentStack.add tok.indent + else: + # dedent? + while em.indentStack.len > 1 and em.indentStack[^1] > tok.indent: + discard em.indentStack.pop() + em.indentLevel = em.indentStack.high * em.indWidth + #[ we only correct the indentation if it is not in an expression context, + so that code like + + const splitters = {tkComma, tkSemicolon, tkParLe, tkParDotLe, + tkBracketLe, tkBracketLeColon, tkCurlyDotLe, + tkCurlyLe} + + is not touched. + ]# + # remove trailing whitespace: + while em.content.len > 0 and em.content[em.content.high] == ' ': + setLen(em.content, em.content.len-1) + wr("\L") + for i in 2..tok.line - em.lastLineNumber: wr("\L") + em.col = 0 + for i in 1..em.indentLevel: + wr(" ") + em.fixedUntil = em.content.high + + case tok.tokType + of tokKeywordLow..tokKeywordHigh: + if endsInAlpha(em): + wr(" ") + elif not em.inquote and not endsInWhite(em) and + em.lastTok notin openPars: + #and tok.tokType in oprSet + wr(" ") + + if not em.inquote: + wr(TokTypeToStr[tok.tokType]) + + case tok.tokType + of tkAnd: rememberSplit(splitAnd) + of tkOr: rememberSplit(splitOr) + of tkIn, tkNotin: + rememberSplit(splitIn) + wr(" ") + else: discard + else: + # keywords in backticks are not normalized: + wr(tok.ident.s) + + of tkColon: + wr(TokTypeToStr[tok.tokType]) + wr(" ") + of tkSemicolon, tkComma: + wr(TokTypeToStr[tok.tokType]) + rememberSplit(splitComma) + wr(" ") + of tkParDotLe, tkParLe, tkBracketDotLe, tkBracketLe, + tkCurlyLe, tkCurlyDotLe, tkBracketLeColon: + if tok.strongSpaceA > 0 and not em.endsInWhite: + wr(" ") + wr(TokTypeToStr[tok.tokType]) + rememberSplit(splitParLe) + of tkParRi, + tkBracketRi, tkCurlyRi, + tkBracketDotRi, + tkCurlyDotRi, + tkParDotRi, + tkColonColon, tkDot: + wr(TokTypeToStr[tok.tokType]) + of tkEquals: + if not em.inquote and not em.endsInWhite: wr(" ") + wr(TokTypeToStr[tok.tokType]) + if not em.inquote: wr(" ") + of tkOpr, tkDotDot: + if tok.strongSpaceA == 0 and tok.strongSpaceB == 0: + # if not surrounded by whitespace, don't produce any whitespace either: + wr(tok.ident.s) + else: + if not em.endsInWhite: wr(" ") + wr(tok.ident.s) + template isUnary(tok): bool = + tok.strongSpaceB == 0 and tok.strongSpaceA > 0 + + if not isUnary(tok): + wr(" ") + rememberSplit(splitBinary) + of tkAccent: + if not em.inquote and endsInAlpha(em): wr(" ") + wr(TokTypeToStr[tok.tokType]) + em.inquote = not em.inquote + of tkComment: + if not preventComment: + emitComment(em, tok) + of tkIntLit..tkStrLit, tkRStrLit, tkTripleStrLit, tkGStrLit, tkGTripleStrLit, tkCharLit: + let lit = fileSection(em.config, em.fid, tok.offsetA, tok.offsetB) + softLinebreak(em, lit) + if endsInAlpha(em) and tok.tokType notin {tkGStrLit, tkGTripleStrLit}: wr(" ") + em.lineSpan = countNewlines(lit) + if em.lineSpan > 0: calcCol(em, lit) + wr lit + of tkEof: discard + else: + let lit = if tok.ident != nil: tok.ident.s else: tok.literal + softLinebreak(em, lit) + if endsInAlpha(em): wr(" ") + wr lit + + em.lastTok = tok.tokType + em.lastLineNumber = tok.line + em.lineSpan + em.lineSpan = 0 + +proc starWasExportMarker*(em: var Emitter) = + if em.content.endsWith(" * "): + setLen(em.content, em.content.len-3) + em.content.add("*") + dec em.col, 2 + +proc commaWasSemicolon*(em: var Emitter) = + if em.semicolons == detectSemicolonKind: + em.semicolons = if em.content.endsWith(", "): dontTouch else: useSemicolon + if em.semicolons == useSemicolon and em.content.endsWith(", "): + setLen(em.content, em.content.len-2) + em.content.add("; ") + +proc curlyRiWasPragma*(em: var Emitter) = + if em.content.endsWith("}"): + setLen(em.content, em.content.len-1) + em.content.add(".}") diff --git a/compiler/lexer.nim b/compiler/lexer.nim index afa7642dc..278fa1e54 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -17,7 +17,7 @@ import hashes, options, msgs, strutils, platform, idents, nimlexbase, llstream, - wordrecg + wordrecg, lineinfos const MaxLineLength* = 80 # lines longer than this lead to a warning @@ -25,7 +25,7 @@ const SymChars*: set[char] = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF'} SymStartChars*: set[char] = {'a'..'z', 'A'..'Z', '\x80'..'\xFF'} OpChars*: set[char] = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^', '.', - '|', '=', '%', '&', '$', '@', '~', ':', '\x80'..'\xFF'} + '|', '=', '%', '&', '$', '@', '~', ':'} # don't forget to update the 'highlite' module if these charsets should change @@ -60,7 +60,7 @@ type tkCurlyDotLe, tkCurlyDotRi, # {. and .} tkParDotLe, tkParDotRi, # (. and .) tkComma, tkSemiColon, - tkColon, tkColonColon, tkEquals, tkDot, tkDotDot, + tkColon, tkColonColon, tkEquals, tkDot, tkDotDot, tkBracketLeColon, tkOpr, tkComment, tkAccent, tkSpaces, tkInfixOpr, tkPrefixOpr, tkPostfixOpr @@ -98,7 +98,7 @@ const "tkTripleStrLit", "tkGStrLit", "tkGTripleStrLit", "tkCharLit", "(", ")", "[", "]", "{", "}", "[.", ".]", "{.", ".}", "(.", ".)", ",", ";", - ":", "::", "=", ".", "..", + ":", "::", "=", ".", "..", "[:", "tkOpr", "tkComment", "`", "tkSpaces", "tkInfixOpr", "tkPrefixOpr", "tkPostfixOpr"] @@ -131,9 +131,9 @@ type # like 0b01 or r"\L" are unaffected commentOffsetA*, commentOffsetB*: int - TErrorHandler* = proc (info: TLineInfo; msg: TMsgKind; arg: string) + TErrorHandler* = proc (conf: ConfigRef; info: TLineInfo; msg: TMsgKind; arg: string) TLexer* = object of TBaseLexer - fileIdx*: int32 + fileIdx*: FileIndex indentAhead*: int # if > 0 an indendation has already been read # this is needed because scanning comments # needs so much look-ahead @@ -144,13 +144,12 @@ type cache*: IdentCache when defined(nimsuggest): previousToken: TLineInfo + config*: ConfigRef when defined(nimpretty): var gIndentationWidth*: int -var gLinesCompiled*: int # all lines that have been compiled - proc getLineInfo*(L: TLexer, tok: TToken): TLineInfo {.inline.} = result = newLineInfo(L.fileIdx, tok.line, tok.col) when defined(nimpretty): @@ -165,13 +164,12 @@ proc isKeyword*(kind: TTokType): bool = template ones(n): untyped = ((1 shl n)-1) # for utf-8 conversion proc isNimIdentifier*(s: string): bool = - if s[0] in SymStartChars: + let sLen = s.len + if sLen > 0 and s[0] in SymStartChars: var i = 1 - var sLen = s.len while i < sLen: - if s[i] == '_': - inc(i) - if s[i] notin SymChars: return + if s[i] == '_': inc(i) + if i < sLen and s[i] notin SymChars: return inc(i) result = true @@ -192,8 +190,8 @@ proc prettyTok*(tok: TToken): string = if isKeyword(tok.tokType): result = "keyword " & tok.ident.s else: result = tokToStr(tok) -proc printTok*(tok: TToken) = - msgWriteln($tok.line & ":" & $tok.col & "\t" & +proc printTok*(conf: ConfigRef; tok: TToken) = + msgWriteln(conf, $tok.line & ":" & $tok.col & "\t" & TokTypeToStr[tok.tokType] & " " & tokToStr(tok)) proc initToken*(L: var TToken) = @@ -222,8 +220,8 @@ proc fillToken(L: var TToken) = L.commentOffsetA = 0 L.commentOffsetB = 0 -proc openLexer*(lex: var TLexer, fileIdx: int32, inputstream: PLLStream; - cache: IdentCache) = +proc openLexer*(lex: var TLexer, fileIdx: FileIndex, inputstream: PLLStream; + cache: IdentCache; config: ConfigRef) = openBaseLexer(lex, inputstream) lex.fileIdx = fileidx lex.indentAhead = - 1 @@ -232,13 +230,15 @@ proc openLexer*(lex: var TLexer, fileIdx: int32, inputstream: PLLStream; lex.cache = cache when defined(nimsuggest): lex.previousToken.fileIndex = fileIdx + lex.config = config proc openLexer*(lex: var TLexer, filename: string, inputstream: PLLStream; - cache: IdentCache) = - openLexer(lex, filename.fileInfoIdx, inputstream, cache) + cache: IdentCache; config: ConfigRef) = + openLexer(lex, fileInfoIdx(config, filename), inputstream, cache, config) proc closeLexer*(lex: var TLexer) = - inc(gLinesCompiled, lex.lineNumber) + if lex.config != nil: + inc(lex.config.linesCompiled, lex.lineNumber) closeBaseLexer(lex) proc getLineInfo(L: TLexer): TLineInfo = @@ -246,9 +246,9 @@ proc getLineInfo(L: TLexer): TLineInfo = proc dispMessage(L: TLexer; info: TLineInfo; msg: TMsgKind; arg: string) = if L.errorHandler.isNil: - msgs.message(info, msg, arg) + msgs.message(L.config, info, msg, arg) else: - L.errorHandler(info, msg, arg) + L.errorHandler(L.config, info, msg, arg) proc lexMessage*(L: TLexer, msg: TMsgKind, arg = "") = L.dispMessage(getLineInfo(L), msg, arg) @@ -273,10 +273,10 @@ template tokenBegin(tok, pos) {.dirty.} = template tokenEnd(tok, pos) {.dirty.} = when defined(nimsuggest): let colB = getColNumber(L, pos)+1 - if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and - L.lineNumber == gTrackPos.line and gIdeCmd in {ideSug, ideCon}: + if L.fileIdx == L.config.m.trackPos.fileIndex and L.config.m.trackPos.col in colA..colB and + L.lineNumber == L.config.m.trackPos.line.int and L.config.ideCmd in {ideSug, ideCon}: L.cursor = CursorPosition.InToken - gTrackPos.col = colA.int16 + L.config.m.trackPos.col = colA.int16 colA = 0 when defined(nimpretty): tok.offsetB = L.offsetBase + pos @@ -284,10 +284,10 @@ template tokenEnd(tok, pos) {.dirty.} = template tokenEndIgnore(tok, pos) = when defined(nimsuggest): let colB = getColNumber(L, pos) - if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and - L.lineNumber == gTrackPos.line and gIdeCmd in {ideSug, ideCon}: - gTrackPos.fileIndex = trackPosInvalidFileIdx - gTrackPos.line = -1 + if L.fileIdx == L.config.m.trackPos.fileIndex and L.config.m.trackPos.col in colA..colB and + L.lineNumber == L.config.m.trackPos.line.int and L.config.ideCmd in {ideSug, ideCon}: + L.config.m.trackPos.fileIndex = trackPosInvalidFileIdx + L.config.m.trackPos.line = 0'u16 colA = 0 when defined(nimpretty): tok.offsetB = L.offsetBase + pos @@ -298,11 +298,11 @@ template tokenEndPrevious(tok, pos) = # to the token that came before that, but only if we haven't detected # the cursor in a string literal or comment: let colB = getColNumber(L, pos) - if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and - L.lineNumber == gTrackPos.line and gIdeCmd in {ideSug, ideCon}: + if L.fileIdx == L.config.m.trackPos.fileIndex and L.config.m.trackPos.col in colA..colB and + L.lineNumber == L.config.m.trackPos.line.int and L.config.ideCmd in {ideSug, ideCon}: L.cursor = CursorPosition.BeforeToken - gTrackPos = L.previousToken - gTrackPosAttached = true + L.config.m.trackPos = L.previousToken + L.config.m.trackPosAttached = true colA = 0 when defined(nimpretty): tok.offsetB = L.offsetBase + pos @@ -311,12 +311,12 @@ template tokenEndPrevious(tok, pos) = # We need to parse the largest uint literal without overflow checks proc unsafeParseUInt(s: string, b: var BiggestInt, start = 0): int = var i = start - if s[i] in {'0'..'9'}: + if i < s.len and s[i] in {'0'..'9'}: b = 0 - while s[i] in {'0'..'9'}: + while i < s.len and s[i] in {'0'..'9'}: b = b * 10 + (ord(s[i]) - ord('0')) inc(i) - while s[i] == '_': inc(i) # underscores are allowed and ignored + while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored result = i - start {.pop.} # overflowChecks @@ -330,18 +330,22 @@ template eatChar(L: var TLexer, t: var TToken) = inc(L.bufpos) proc getNumber(L: var TLexer, result: var TToken) = - proc matchUnderscoreChars(L: var TLexer, tok: var TToken, chars: set[char]) = + proc matchUnderscoreChars(L: var TLexer, tok: var TToken, chars: set[char]): Natural = var pos = L.bufpos # use registers for pos, buf var buf = L.buf + result = 0 while true: if buf[pos] in chars: add(tok.literal, buf[pos]) inc(pos) + inc(result) else: break if buf[pos] == '_': if buf[pos+1] notin chars: - lexMessage(L, errInvalidToken, "_") + lexMessage(L, errGenerated, + "only single underscores may occur in a token and token may not " & + "end with an underscore: e.g. '1__1' and '1_' are invalid") break add(tok.literal, '_') inc(pos) @@ -355,9 +359,8 @@ proc getNumber(L: var TLexer, result: var TToken) = inc(pos) L.bufpos = pos - proc lexMessageLitNum(L: var TLexer, msg: TMsgKind, startpos: int) = + proc lexMessageLitNum(L: var TLexer, msg: string, startpos: int, msgKind = errGenerated) = # Used to get slightly human friendlier err messages. - # Note: the erroneous 'O' char in the character set is intentional const literalishChars = {'A'..'F', 'a'..'f', '0'..'9', 'X', 'x', 'o', 'O', 'c', 'C', 'b', 'B', '_', '.', '\'', 'd', 'i', 'u'} var msgPos = L.bufpos @@ -376,14 +379,16 @@ proc getNumber(L: var TLexer, result: var TToken) = add(t.literal, L.buf[L.bufpos]) matchChars(L, t, {'0'..'9'}) L.bufpos = msgPos - lexMessage(L, msg, t.literal) + lexMessage(L, msgKind, msg % t.literal) var startpos, endpos: int xi: BiggestInt isBase10 = true + numDigits = 0 const - baseCodeChars = {'X', 'x', 'o', 'c', 'C', 'b', 'B'} + # 'c', 'C' is deprecated + baseCodeChars = {'X', 'x', 'o', 'b', 'B', 'c', 'C'} literalishChars = baseCodeChars + {'A'..'F', 'a'..'f', '0'..'9', '_', '\''} floatTypes = {tkFloatLit, tkFloat32Lit, tkFloat64Lit, tkFloat128Lit} result.tokType = tkIntLit # int literal until we know better @@ -393,35 +398,47 @@ proc getNumber(L: var TLexer, result: var TToken) = tokenBegin(result, startPos) # First stage: find out base, make verifications, build token literal string - if L.buf[L.bufpos] == '0' and L.buf[L.bufpos + 1] in baseCodeChars + {'O'}: + # {'c', 'C'} is added for deprecation reasons to provide a clear error message + if L.buf[L.bufpos] == '0' and L.buf[L.bufpos + 1] in baseCodeChars + {'c', 'C', 'O'}: isBase10 = false eatChar(L, result, '0') case L.buf[L.bufpos] + of 'c', 'C': + lexMessageLitNum(L, + "$1 will soon be invalid for oct literals; Use '0o' " & + "for octals. 'c', 'C' prefix", + startpos, + warnDeprecated) + eatChar(L, result, 'c') + numDigits = matchUnderscoreChars(L, result, {'0'..'7'}) of 'O': - lexMessageLitNum(L, errInvalidNumberOctalCode, startpos) + lexMessageLitNum(L, "$1 is an invalid int literal; For octal literals " & + "use the '0o' prefix.", startpos) of 'x', 'X': eatChar(L, result, 'x') - matchUnderscoreChars(L, result, {'0'..'9', 'a'..'f', 'A'..'F'}) - of 'o', 'c', 'C': - eatChar(L, result, 'c') - matchUnderscoreChars(L, result, {'0'..'7'}) + numDigits = matchUnderscoreChars(L, result, {'0'..'9', 'a'..'f', 'A'..'F'}) + of 'o': + eatChar(L, result, 'o') + numDigits = matchUnderscoreChars(L, result, {'0'..'7'}) of 'b', 'B': eatChar(L, result, 'b') - matchUnderscoreChars(L, result, {'0'..'1'}) + numDigits = matchUnderscoreChars(L, result, {'0'..'1'}) else: - internalError(getLineInfo(L), "getNumber") + internalError(L.config, getLineInfo(L), "getNumber") + if numDigits == 0: + lexMessageLitNum(L, "invalid number: '$1'", startpos) else: - matchUnderscoreChars(L, result, {'0'..'9'}) + discard matchUnderscoreChars(L, result, {'0'..'9'}) if (L.buf[L.bufpos] == '.') and (L.buf[L.bufpos + 1] in {'0'..'9'}): result.tokType = tkFloatLit eatChar(L, result, '.') - matchUnderscoreChars(L, result, {'0'..'9'}) + discard matchUnderscoreChars(L, result, {'0'..'9'}) if L.buf[L.bufpos] in {'e', 'E'}: result.tokType = tkFloatLit eatChar(L, result, 'e') if L.buf[L.bufpos] in {'+', '-'}: eatChar(L, result) - matchUnderscoreChars(L, result, {'0'..'9'}) + discard matchUnderscoreChars(L, result, {'0'..'9'}) endpos = L.bufpos # Second stage, find out if there's a datatype suffix and handle it @@ -464,7 +481,7 @@ proc getNumber(L: var TLexer, result: var TToken) = result.tokType = tkInt8Lit inc(postPos) else: - lexMessageLitNum(L, errInvalidNumber, startpos) + lexMessageLitNum(L, "invalid number: '$1'", startpos) of 'u', 'U': inc(postPos) if (L.buf[postPos] == '6') and (L.buf[postPos + 1] == '4'): @@ -482,12 +499,12 @@ proc getNumber(L: var TLexer, result: var TToken) = else: result.tokType = tkUIntLit else: - lexMessageLitNum(L, errInvalidNumber, startpos) + lexMessageLitNum(L, "invalid number: '$1'", startpos) # Is there still a literalish char awaiting? Then it's an error! if L.buf[postPos] in literalishChars or (L.buf[postPos] == '.' and L.buf[postPos + 1] in {'0'..'9'}): - lexMessageLitNum(L, errInvalidNumber, startpos) + lexMessageLitNum(L, "invalid number: '$1'", startpos) # Third stage, extract actual number L.bufpos = startpos # restore position @@ -504,6 +521,7 @@ proc getNumber(L: var TLexer, result: var TToken) = if L.buf[pos] != '_': xi = `shl`(xi, 1) or (ord(L.buf[pos]) - ord('0')) inc(pos) + # 'c', 'C' is deprecated of 'o', 'c', 'C': result.base = base8 while pos < endpos: @@ -528,7 +546,7 @@ proc getNumber(L: var TLexer, result: var TToken) = else: break else: - internalError(getLineInfo(L), "getNumber") + internalError(L.config, getLineInfo(L), "getNumber") case result.tokType of tkIntLit, tkInt64Lit: result.iNumber = xi @@ -545,7 +563,7 @@ proc getNumber(L: var TLexer, result: var TToken) = # XXX: Test this on big endian machine! of tkFloat64Lit, tkFloatLit: result.fNumber = (cast[PFloat64](addr(xi)))[] - else: internalError(getLineInfo(L), "getNumber") + else: internalError(L.config, getLineInfo(L), "getNumber") # Bounds checks. Non decimal literals are allowed to overflow the range of # the datatype as long as their pattern don't overflow _bitwise_, hence @@ -561,7 +579,7 @@ proc getNumber(L: var TLexer, result: var TToken) = if outOfRange: #echo "out of range num: ", result.iNumber, " vs ", xi - lexMessageLitNum(L, errNumberOutOfRange, startpos) + lexMessageLitNum(L, "number out of range: '$1'", startpos) else: case result.tokType @@ -577,19 +595,20 @@ proc getNumber(L: var TLexer, result: var TToken) = result.iNumber = parseBiggestInt(result.literal) # Explicit bounds checks - let outOfRange = case result.tokType: - of tkInt8Lit: (result.iNumber < int8.low or result.iNumber > int8.high) - of tkUInt8Lit: (result.iNumber < BiggestInt(uint8.low) or - result.iNumber > BiggestInt(uint8.high)) - of tkInt16Lit: (result.iNumber < int16.low or result.iNumber > int16.high) - of tkUInt16Lit: (result.iNumber < BiggestInt(uint16.low) or - result.iNumber > BiggestInt(uint16.high)) - of tkInt32Lit: (result.iNumber < int32.low or result.iNumber > int32.high) - of tkUInt32Lit: (result.iNumber < BiggestInt(uint32.low) or - result.iNumber > BiggestInt(uint32.high)) - else: false - - if outOfRange: lexMessageLitNum(L, errNumberOutOfRange, startpos) + let outOfRange = + case result.tokType + of tkInt8Lit: (result.iNumber < int8.low or result.iNumber > int8.high) + of tkUInt8Lit: (result.iNumber < BiggestInt(uint8.low) or + result.iNumber > BiggestInt(uint8.high)) + of tkInt16Lit: (result.iNumber < int16.low or result.iNumber > int16.high) + of tkUInt16Lit: (result.iNumber < BiggestInt(uint16.low) or + result.iNumber > BiggestInt(uint16.high)) + of tkInt32Lit: (result.iNumber < int32.low or result.iNumber > int32.high) + of tkUInt32Lit: (result.iNumber < BiggestInt(uint32.low) or + result.iNumber > BiggestInt(uint32.high)) + else: false + + if outOfRange: lexMessageLitNum(L, "number out of range: '$1'", startpos) # Promote int literal to int64? Not always necessary, but more consistent if result.tokType == tkIntLit: @@ -597,9 +616,9 @@ proc getNumber(L: var TLexer, result: var TToken) = result.tokType = tkInt64Lit except ValueError: - lexMessageLitNum(L, errInvalidNumber, startpos) + lexMessageLitNum(L, "invalid number: '$1'", startpos) except OverflowError, RangeError: - lexMessageLitNum(L, errNumberOutOfRange, startpos) + lexMessageLitNum(L, "number out of range: '$1'", startpos) tokenEnd(result, postPos-1) L.bufpos = postPos @@ -625,16 +644,17 @@ proc getEscapedChar(L: var TLexer, tok: var TToken) = inc(L.bufpos) # skip '\' case L.buf[L.bufpos] of 'n', 'N': - if gOldNewlines: - if tok.tokType == tkCharLit: lexMessage(L, errNnotAllowedInCharacter) - add(tok.literal, tnl) + if L.config.oldNewlines: + if tok.tokType == tkCharLit: + lexMessage(L, errGenerated, "\\n not allowed in character literal") + add(tok.literal, L.config.target.tnl) else: add(tok.literal, '\L') inc(L.bufpos) of 'p', 'P': if tok.tokType == tkCharLit: lexMessage(L, errGenerated, "\\p not allowed in character literal") - add(tok.literal, tnl) + add(tok.literal, L.config.target.tnl) inc(L.bufpos) of 'r', 'R', 'c', 'C': add(tok.literal, CR) @@ -695,8 +715,8 @@ proc getEscapedChar(L: var TLexer, tok: var TToken) = var xi = 0 handleDecChars(L, xi) if (xi <= 255): add(tok.literal, chr(xi)) - else: lexMessage(L, errInvalidCharacterConstant) - else: lexMessage(L, errInvalidCharacterConstant) + else: lexMessage(L, errGenerated, "invalid character constant") + else: lexMessage(L, errGenerated, "invalid character constant") proc newString(s: cstring, len: int): string = ## XXX, how come there is no support for this? @@ -711,11 +731,6 @@ proc handleCRLF(L: var TLexer, pos: int): int = if col > MaxLineLength: lexMessagePos(L, hintLineTooLong, pos) - if optEmbedOrigSrc in gGlobalOptions: - let lineStart = cast[ByteAddress](L.buf) + L.lineStart - let line = newString(cast[cstring](lineStart), col) - addSourceLine(L.fileIdx, line) - case L.buf[pos] of CR: registerLine() @@ -760,7 +775,7 @@ proc getString(L: var TLexer, tok: var TToken, rawMode: bool) = tokenEndIgnore(tok, pos) var line2 = L.lineNumber L.lineNumber = line - lexMessagePos(L, errClosingTripleQuoteExpected, L.lineStart) + lexMessagePos(L, errGenerated, L.lineStart, "closing \"\"\" expected, but end of file reached") L.lineNumber = line2 L.bufpos = pos break @@ -783,7 +798,7 @@ proc getString(L: var TLexer, tok: var TToken, rawMode: bool) = break elif c in {CR, LF, nimlexbase.EndOfFile}: tokenEndIgnore(tok, pos) - lexMessage(L, errClosingQuoteExpected) + lexMessage(L, errGenerated, "closing \" expected") break elif (c == '\\') and not rawMode: L.bufpos = pos @@ -799,12 +814,13 @@ proc getCharacter(L: var TLexer, tok: var TToken) = inc(L.bufpos) # skip ' var c = L.buf[L.bufpos] case c - of '\0'..pred(' '), '\'': lexMessage(L, errInvalidCharacterConstant) + of '\0'..pred(' '), '\'': lexMessage(L, errGenerated, "invalid character literal") of '\\': getEscapedChar(L, tok) else: tok.literal = $c inc(L.bufpos) - if L.buf[L.bufpos] != '\'': lexMessage(L, errMissingFinalQuote) + if L.buf[L.bufpos] != '\'': + lexMessage(L, errGenerated, "missing closing ' for character literal") tokenEndIgnore(tok, L.bufpos) inc(L.bufpos) # skip ' @@ -825,7 +841,7 @@ proc getSymbol(L: var TLexer, tok: var TToken) = inc(pos) of '_': if buf[pos+1] notin SymChars: - lexMessage(L, errInvalidToken, "_") + lexMessage(L, errGenerated, "invalid token: trailing underscore") break inc(pos) else: break @@ -868,7 +884,43 @@ proc getOperator(L: var TLexer, tok: var TToken) = if buf[pos] in {CR, LF, nimlexbase.EndOfFile}: tok.strongSpaceB = -1 -proc newlineFollows*(L: var TLexer): bool = +proc getPrecedence*(tok: TToken, strongSpaces: bool): int = + ## Calculates the precedence of the given token. + template considerStrongSpaces(x): untyped = + x + (if strongSpaces: 100 - tok.strongSpaceA.int*10 else: 0) + + case tok.tokType + of tkOpr: + let L = tok.ident.s.len + let relevantChar = tok.ident.s[0] + + # arrow like? + if L > 1 and tok.ident.s[L-1] == '>' and + tok.ident.s[L-2] in {'-', '~', '='}: return considerStrongSpaces(1) + + template considerAsgn(value: untyped) = + result = if tok.ident.s[L-1] == '=': 1 else: value + + case relevantChar + of '$', '^': considerAsgn(10) + of '*', '%', '/', '\\': considerAsgn(9) + of '~': result = 8 + of '+', '-', '|': considerAsgn(8) + of '&': considerAsgn(7) + of '=', '<', '>', '!': result = 5 + of '.': considerAsgn(6) + of '?': result = 2 + else: considerAsgn(2) + of tkDiv, tkMod, tkShl, tkShr: result = 9 + of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5 + of tkDotDot: result = 6 + of tkAnd: result = 4 + of tkOr, tkXor, tkPtr, tkRef: result = 3 + else: return -10 + result = considerStrongSpaces(result) + + +proc newlineFollows*(L: TLexer): bool = var pos = L.bufpos var buf = L.buf while true: @@ -950,6 +1002,8 @@ proc skipMultiLineComment(L: var TLexer; tok: var TToken; start: int; if isDoc or defined(nimpretty): tok.literal.add buf[pos] inc(pos) L.bufpos = pos + when defined(nimpretty): + tok.commentOffsetB = L.offsetBase + pos - 1 proc scanComment(L: var TLexer, tok: var TToken) = var pos = L.bufpos @@ -958,6 +1012,9 @@ proc scanComment(L: var TLexer, tok: var TToken) = # iNumber contains the number of '\n' in the token tok.iNumber = 0 assert buf[pos+1] == '#' + when defined(nimpretty): + tok.commentOffsetA = L.offsetBase + pos - 1 + if buf[pos+2] == '[': skipMultiLineComment(L, tok, pos+3, true) return @@ -997,22 +1054,31 @@ proc scanComment(L: var TLexer, tok: var TToken) = tokenEndIgnore(tok, pos) break L.bufpos = pos + when defined(nimpretty): + tok.commentOffsetB = L.offsetBase + pos - 1 proc skip(L: var TLexer, tok: var TToken) = var pos = L.bufpos var buf = L.buf tokenBegin(tok, pos) tok.strongSpaceA = 0 + when defined(nimpretty): + var hasComment = false + tok.commentOffsetA = L.offsetBase + pos + tok.commentOffsetB = tok.commentOffsetA while true: case buf[pos] of ' ': inc(pos) inc(tok.strongSpaceA) of '\t': - if not L.allowTabs: lexMessagePos(L, errTabulatorsAreNotAllowed, pos) + if not L.allowTabs: lexMessagePos(L, errGenerated, pos, "tabulators are not allowed") inc(pos) of CR, LF: tokenEndPrevious(tok, pos) + when defined(nimpretty): + # we are not yet in a comment, so update the comment token's line information: + if not hasComment: inc tok.line pos = handleCRLF(L, pos) buf = L.buf var indent = 0 @@ -1021,6 +1087,7 @@ proc skip(L: var TLexer, tok: var TToken) = inc(pos) inc(indent) elif buf[pos] == '#' and buf[pos+1] == '[': + when defined(nimpretty): hasComment = true skipMultiLineComment(L, tok, pos+2, false) pos = L.bufpos buf = L.buf @@ -1034,14 +1101,11 @@ proc skip(L: var TLexer, tok: var TToken) = of '#': # do not skip documentation comment: if buf[pos+1] == '#': break - when defined(nimpretty): - tok.commentOffsetA = L.offsetBase + pos + when defined(nimpretty): hasComment = true if buf[pos+1] == '[': skipMultiLineComment(L, tok, pos+2, false) pos = L.bufpos buf = L.buf - when defined(nimpretty): - tok.commentOffsetB = L.offsetBase + pos else: tokenBegin(tok, pos) while buf[pos] notin {CR, LF, nimlexbase.EndOfFile}: inc(pos) @@ -1053,6 +1117,9 @@ proc skip(L: var TLexer, tok: var TToken) = tokenEndPrevious(tok, pos-1) L.bufpos = pos when defined(nimpretty): + if hasComment: + tok.commentOffsetB = L.offsetBase + pos - 1 + tok.tokType = tkComment if gIndentationWidth <= 0: gIndentationWidth = tok.indent @@ -1061,7 +1128,7 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = when defined(nimsuggest): # we attach the cursor to the last *strong* token if tok.tokType notin weakTokens: - L.previousToken.line = tok.line.int16 + L.previousToken.line = tok.line.uint16 L.previousToken.col = tok.col.int16 when defined(nimsuggest): @@ -1074,6 +1141,10 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = else: tok.indent = -1 skip(L, tok) + when defined(nimpretty): + if tok.tokType == tkComment: + L.indentAhead = L.currLineIndent + return var c = L.buf[L.bufpos] tok.line = L.lineNumber tok.col = getColNumber(L, L.bufpos) @@ -1108,9 +1179,9 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = else: tok.tokType = tkParLe when defined(nimsuggest): - if L.fileIdx == gTrackPos.fileIndex and tok.col < gTrackPos.col and - tok.line == gTrackPos.line and gIdeCmd == ideCon: - gTrackPos.col = tok.col.int16 + if L.fileIdx == L.config.m.trackPos.fileIndex and tok.col < L.config.m.trackPos.col and + tok.line == L.config.m.trackPos.line.int and L.config.ideCmd == ideCon: + L.config.m.trackPos.col = tok.col.int16 of ')': tok.tokType = tkParRi inc(L.bufpos) @@ -1119,6 +1190,9 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = if L.buf[L.bufpos] == '.' and L.buf[L.bufpos+1] != '.': tok.tokType = tkBracketDotLe inc(L.bufpos) + elif L.buf[L.bufpos] == ':': + tok.tokType = tkBracketLeColon + inc(L.bufpos) else: tok.tokType = tkBracketLe of ']': @@ -1126,11 +1200,11 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = inc(L.bufpos) of '.': when defined(nimsuggest): - if L.fileIdx == gTrackPos.fileIndex and tok.col+1 == gTrackPos.col and - tok.line == gTrackPos.line and gIdeCmd == ideSug: + if L.fileIdx == L.config.m.trackPos.fileIndex and tok.col+1 == L.config.m.trackPos.col and + tok.line == L.config.m.trackPos.line.int and L.config.ideCmd == ideSug: tok.tokType = tkDot L.cursor = CursorPosition.InToken - gTrackPos.col = tok.col.int16 + L.config.m.trackPos.col = tok.col.int16 inc(L.bufpos) atTokenEnd() return @@ -1169,9 +1243,9 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = else: tok.literal = $c tok.tokType = tkInvalid - lexMessage(L, errInvalidToken, c & " (\\" & $(ord(c)) & ')') + lexMessage(L, errGenerated, "invalid token: " & c & " (\\" & $(ord(c)) & ')') of '\"': - # check for extended raw string literal: + # check for generalized raw string literal: var rawMode = L.bufpos > 0 and L.buf[L.bufpos-1] in SymChars getString(L, tok, rawMode) if rawMode: @@ -1186,7 +1260,7 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = getNumber(L, tok) let c = L.buf[L.bufpos] if c in SymChars+{'_'}: - lexMessage(L, errInvalidToken, c & " (\\" & $(ord(c)) & ')') + lexMessage(L, errGenerated, "invalid token: no whitespace between number and identifier") else: if c in OpChars: getOperator(L, tok) @@ -1196,6 +1270,29 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = else: tok.literal = $c tok.tokType = tkInvalid - lexMessage(L, errInvalidToken, c & " (\\" & $(ord(c)) & ')') + lexMessage(L, errGenerated, "invalid token: " & c & " (\\" & $(ord(c)) & ')') inc(L.bufpos) atTokenEnd() + +proc getIndentWidth*(fileIdx: FileIndex, inputstream: PLLStream; + cache: IdentCache; config: ConfigRef): int = + var lex: TLexer + var tok: TToken + initToken(tok) + openLexer(lex, fileIdx, inputstream, cache, config) + while true: + rawGetTok(lex, tok) + result = tok.indent + if result > 0 or tok.tokType == tkEof: break + closeLexer(lex) + +proc getPrecedence*(ident: PIdent): int = + ## assumes ident is binary operator already + var tok: TToken + initToken(tok) + tok.ident = ident + tok.tokType = + if tok.ident.id in ord(tokKeywordLow) - ord(tkSymbol) .. ord(tokKeywordHigh) - ord(tkSymbol): + TTokType(tok.ident.id + ord(tkSymbol)) + else: tkOpr + getPrecedence(tok, false) diff --git a/compiler/liftlocals.nim b/compiler/liftlocals.nim index 3610a1486..ae789cd88 100644 --- a/compiler/liftlocals.nim +++ b/compiler/liftlocals.nim @@ -11,7 +11,7 @@ import intsets, strutils, options, ast, astalgo, msgs, - idents, renderer, types, lowerings + idents, renderer, types, lowerings, lineinfos from pragmas import getPragmaVal from wordrecg import wLiftLocals @@ -20,13 +20,14 @@ type Ctx = object partialParam: PSym objType: PType + cache: IdentCache proc interestingVar(s: PSym): bool {.inline.} = result = s.kind in {skVar, skLet, skTemp, skForVar, skResult} and sfGlobal notin s.flags proc lookupOrAdd(c: var Ctx; s: PSym; info: TLineInfo): PNode = - let field = addUniqueField(c.objType, s) + let field = addUniqueField(c.objType, s, c.cache) var deref = newNodeI(nkHiddenDeref, info) deref.typ = c.objType add(deref, newSymNode(c.partialParam, info)) @@ -52,19 +53,19 @@ proc lookupParam(params, dest: PNode): PSym = if params[i].kind == nkSym and params[i].sym.name.id == dest.ident.id: return params[i].sym -proc liftLocalsIfRequested*(prc: PSym; n: PNode): PNode = +proc liftLocalsIfRequested*(prc: PSym; n: PNode; cache: IdentCache; conf: ConfigRef): PNode = let liftDest = getPragmaVal(prc.ast, wLiftLocals) if liftDest == nil: return n let partialParam = lookupParam(prc.typ.n, liftDest) if partialParam.isNil: - localError(liftDest.info, "'$1' is not a parameter of '$2'" % + localError(conf, liftDest.info, "'$1' is not a parameter of '$2'" % [$liftDest, prc.name.s]) return n let objType = partialParam.typ.skipTypes(abstractPtrs) if objType.kind != tyObject or tfPartial notin objType.flags: - localError(liftDest.info, "parameter '$1' is not a pointer to a partial object" % $liftDest) + localError(conf, liftDest.info, "parameter '$1' is not a pointer to a partial object" % $liftDest) return n - var c = Ctx(partialParam: partialParam, objType: objType) + var c = Ctx(partialParam: partialParam, objType: objType, cache: cache) let w = newTree(nkStmtList, n) liftLocals(w, 0, c) result = w[0] diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim new file mode 100644 index 000000000..41f3806d4 --- /dev/null +++ b/compiler/lineinfos.nim @@ -0,0 +1,263 @@ +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module contains the ``TMsgKind`` enum as well as the +## ``TLineInfo`` object. + +import ropes, tables + +const + explanationsBaseUrl* = "https://nim-lang.org/docs/manual" + +type + TMsgKind* = enum + errUnknown, errInternal, errIllFormedAstX, errCannotOpenFile, + errXExpected, + errGridTableNotImplemented, + errGeneralParseError, + errNewSectionExpected, + errInvalidDirectiveX, + errGenerated, + errUser, + warnCannotOpenFile, + warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, + warnDeprecated, warnConfigDeprecated, + warnSmallLshouldNotBeUsed, warnUnknownMagic, warnRedefinitionOfLabel, + warnUnknownSubstitutionX, warnLanguageXNotSupported, + warnFieldXNotSupported, warnCommentXIgnored, + warnTypelessParam, + warnUseBase, warnWriteToForeignHeap, warnUnsafeCode, + warnEachIdentIsTuple, warnShadowIdent, + warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe, warnGcUnsafe2, + warnUninit, warnGcMem, warnDestructor, warnLockLevel, warnResultShadowed, + warnInconsistentSpacing, warnUser, + hintSuccess, hintSuccessX, hintCC, + hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, + hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled, + hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath, + hintConditionAlwaysTrue, hintName, hintPattern, + hintExecuting, hintLinking, hintDependency, + hintSource, hintPerformance, hintStackTrace, hintGCStats, + hintGlobalVar, + hintUser, hintUserRaw, + hintExtendedContext + +const + MsgKindToStr*: array[TMsgKind, string] = [ + errUnknown: "unknown error", + errInternal: "internal error: $1", + errIllFormedAstX: "illformed AST: $1", + errCannotOpenFile: "cannot open '$1'", + errXExpected: "'$1' expected", + errGridTableNotImplemented: "grid table is not implemented", + errGeneralParseError: "general parse error", + errNewSectionExpected: "new section expected", + errInvalidDirectiveX: "invalid directive: '$1'", + errGenerated: "$1", + errUser: "$1", + warnCannotOpenFile: "cannot open '$1'", + warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored", + warnXIsNeverRead: "'$1' is never read", + warnXmightNotBeenInit: "'$1' might not have been initialized", + warnDeprecated: "$1 is deprecated", + warnConfigDeprecated: "config file '$1' is deprecated", + warnSmallLshouldNotBeUsed: "'l' should not be used as an identifier; may look like '1' (one)", + warnUnknownMagic: "unknown magic '$1' might crash the compiler", + warnRedefinitionOfLabel: "redefinition of label '$1'", + warnUnknownSubstitutionX: "unknown substitution '$1'", + warnLanguageXNotSupported: "language '$1' not supported", + warnFieldXNotSupported: "field '$1' not supported", + warnCommentXIgnored: "comment '$1' ignored", + warnTypelessParam: "'$1' has no type. Typeless parameters are deprecated; only allowed for 'template'", + warnUseBase: "use {.base.} for base methods; baseless methods are deprecated", + warnWriteToForeignHeap: "write to foreign heap", + warnUnsafeCode: "unsafe code: '$1'", + warnEachIdentIsTuple: "each identifier is a tuple", + warnShadowIdent: "shadowed identifier: '$1'", + warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future.", + warnProveField: "cannot prove that field '$1' is accessible", + warnProveIndex: "cannot prove index '$1' is valid", + warnGcUnsafe: "not GC-safe: '$1'", + warnGcUnsafe2: "$1", + warnUninit: "'$1' might not have been initialized", + warnGcMem: "'$1' uses GC'ed memory", + warnDestructor: "usage of a type with a destructor in a non destructible context. This will become a compile time error in the future.", + warnLockLevel: "$1", + warnResultShadowed: "Special variable 'result' is shadowed.", + warnInconsistentSpacing: "Number of spaces around '$#' is not consistent", + warnUser: "$1", + hintSuccess: "operation successful: $#", + hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#; $#)", + hintCC: "CC: \'$1\'", # unused + hintLineTooLong: "line too long", + hintXDeclaredButNotUsed: "'$1' is declared but not used", + hintConvToBaseNotNeeded: "conversion to base object is not needed", + hintConvFromXtoItselfNotNeeded: "conversion from $1 to itself is pointless", + hintExprAlwaysX: "expression evaluates always to '$1'", + hintQuitCalled: "quit() called", + hintProcessing: "$1", + hintCodeBegin: "generated code listing:", + hintCodeEnd: "end of listing", + hintConf: "used config file '$1'", + hintPath: "added path: '$1'", + hintConditionAlwaysTrue: "condition is always true: '$1'", + hintName: "name should be: '$1'", + hintPattern: "$1", + hintExecuting: "$1", + hintLinking: "", + hintDependency: "$1", + hintSource: "$1", + hintPerformance: "$1", + hintStackTrace: "$1", + hintGCStats: "$1", + hintGlobalVar: "global variable declared here", + hintUser: "$1", + hintUserRaw: "$1", + hintExtendedContext: "$1", + ] + +const + WarningsToStr* = ["CannotOpenFile", "OctalEscape", + "XIsNeverRead", "XmightNotBeenInit", + "Deprecated", "ConfigDeprecated", + "SmallLshouldNotBeUsed", "UnknownMagic", + "RedefinitionOfLabel", "UnknownSubstitutionX", + "LanguageXNotSupported", "FieldXNotSupported", + "CommentXIgnored", + "TypelessParam", "UseBase", "WriteToForeignHeap", + "UnsafeCode", "EachIdentIsTuple", "ShadowIdent", + "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit", + "GcMem", "Destructor", "LockLevel", "ResultShadowed", + "Spacing", "User"] + + HintsToStr* = [ + "Success", "SuccessX", "CC", "LineTooLong", + "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", + "ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf", + "Path", "CondTrue", "Name", "Pattern", "Exec", "Link", "Dependency", + "Source", "Performance", "StackTrace", "GCStats", "GlobalVar", + "User", "UserRaw", "ExtendedContext", + ] + +const + fatalMin* = errUnknown + fatalMax* = errInternal + errMin* = errUnknown + errMax* = errUser + warnMin* = warnCannotOpenFile + warnMax* = pred(hintSuccess) + hintMin* = hintSuccess + hintMax* = high(TMsgKind) + +static: + doAssert HintsToStr.len == ord(hintMax) - ord(hintMin) + 1 + doAssert WarningsToStr.len == ord(warnMax) - ord(warnMin) + 1 + +type + TNoteKind* = range[warnMin..hintMax] # "notes" are warnings or hints + TNoteKinds* = set[TNoteKind] + +proc computeNotesVerbosity(): array[0..3, TNoteKinds] = + result[3] = {low(TNoteKind)..high(TNoteKind)} - {} + result[2] = result[3] - {hintStackTrace, warnUninit, hintExtendedContext} + result[1] = result[2] - {warnShadowIdent, warnProveField, warnProveIndex, + warnGcUnsafe, hintPath, hintDependency, hintCodeBegin, hintCodeEnd, + hintSource, hintGlobalVar, hintGCStats} + result[0] = result[1] - {hintSuccessX, hintSuccess, hintConf, + hintProcessing, hintPattern, hintExecuting, hintLinking} + +const + NotesVerbosity* = computeNotesVerbosity() + errXMustBeCompileTime* = "'$1' can only be used in compile-time context" + errArgsNeedRunOption* = "arguments can only be given if the '--run' option is selected" + +type + TFileInfo* = object + fullPath*: string # This is a canonical full filesystem path + projPath*: string # This is relative to the project's root + shortName*: string # short name of the module + quotedName*: Rope # cached quoted short name for codegen + # purposes + quotedFullName*: Rope # cached quoted full name for codegen + # purposes + + lines*: seq[string] # the source code of the module + # used for better error messages and + # embedding the original source in the + # generated code + dirtyfile*: string # the file that is actually read into memory + # and parsed; usually "" but is used + # for 'nimsuggest' + hash*: string # the checksum of the file + dirty*: bool # for 'nimfix' / 'nimpretty' like tooling + when defined(nimpretty): + fullContent*: string + FileIndex* = distinct int32 + TLineInfo* = object # This is designed to be as small as possible, + # because it is used + # in syntax nodes. We save space here by using + # two int16 and an int32. + # On 64 bit and on 32 bit systems this is + # only 8 bytes. + line*: uint16 + col*: int16 + fileIndex*: FileIndex + when defined(nimpretty): + offsetA*, offsetB*: int + commentOffsetA*, commentOffsetB*: int + + TErrorOutput* = enum + eStdOut + eStdErr + + TErrorOutputs* = set[TErrorOutput] + + ERecoverableError* = object of ValueError + ESuggestDone* = object of Exception + +proc `==`*(a, b: FileIndex): bool {.borrow.} + +proc raiseRecoverableError*(msg: string) {.noinline, noreturn.} = + raise newException(ERecoverableError, msg) + +const + InvalidFileIDX* = FileIndex(-1) + +proc unknownLineInfo*(): TLineInfo = + result.line = uint16(0) + result.col = int16(-1) + result.fileIndex = InvalidFileIDX + +type + Severity* {.pure.} = enum ## VS Code only supports these three + Hint, Warning, Error + +const trackPosInvalidFileIdx* = FileIndex(-2) # special marker so that no suggestions + # are produced within comments and string literals + +type + MsgConfig* = object ## does not need to be stored in the incremental cache + trackPos*: TLineInfo + trackPosAttached*: bool ## whether the tracking position was attached to + ## some close token. + + errorOutputs*: TErrorOutputs + msgContext*: seq[TLineInfo] + lastError*: TLineInfo + filenameToIndexTbl*: Table[string, FileIndex] + fileInfos*: seq[TFileInfo] + systemFileIdx*: FileIndex + + +proc initMsgConfig*(): MsgConfig = + result.msgContext = @[] + result.lastError = unknownLineInfo() + result.filenameToIndexTbl = initTable[string, FileIndex]() + result.fileInfos = @[] + result.errorOutputs = {eStdOut, eStdErr} diff --git a/compiler/nimfix/pretty.nim b/compiler/linter.nim index 8ba922927..7c9cdec83 100644 --- a/compiler/nimfix/pretty.nim +++ b/compiler/linter.nim @@ -13,8 +13,18 @@ import strutils, os, intsets, strtabs -import compiler/options, compiler/ast, compiler/astalgo, compiler/msgs, - compiler/semdata, compiler/nimfix/prettybase, compiler/ropes, compiler/idents +import options, ast, astalgo, msgs, semdata, ropes, idents, + lineinfos + +const + Letters* = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '_'} + +proc identLen*(line: string, start: int): int = + while start+result < line.len and line[start+result] in Letters: + inc result + +when false: + import prettybase type StyleCheck* {.pure.} = enum None, Warn, Auto @@ -24,24 +34,24 @@ var gStyleCheck*: StyleCheck gCheckExtern*, gOnlyMainfile*: bool -proc overwriteFiles*() = - let doStrip = options.getConfigVar("pretty.strip").normalize == "on" - for i in 0 .. high(gSourceFiles): - if gSourceFiles[i].dirty and not gSourceFiles[i].isNimfixFile and - (not gOnlyMainfile or gSourceFiles[i].fileIdx == gProjectMainIdx): - let newFile = if gOverWrite: gSourceFiles[i].fullpath - else: gSourceFiles[i].fullpath.changeFileExt(".pretty.nim") +proc overwriteFiles*(conf: ConfigRef) = + let doStrip = options.getConfigVar(conf, "pretty.strip").normalize == "on" + for i in 0 .. high(conf.m.fileInfos): + if conf.m.fileInfos[i].dirty and + (not gOnlyMainfile or FileIndex(i) == conf.projectMainIdx): + let newFile = if gOverWrite: conf.m.fileInfos[i].fullpath + else: conf.m.fileInfos[i].fullpath.changeFileExt(".pretty.nim") try: var f = open(newFile, fmWrite) - for line in gSourceFiles[i].lines: + for line in conf.m.fileInfos[i].lines: if doStrip: f.write line.strip(leading = false, trailing = true) else: f.write line - f.write(gSourceFiles[i].newline) + f.write(conf.m.fileInfos[i], "\L") f.close except IOError: - rawMessage(errCannotOpenFile, newFile) + rawMessage(conf, errGenerated, "cannot open file: " & newFile) proc `=~`(s: string, a: openArray[string]): bool = for x in a: @@ -92,14 +102,16 @@ proc beautifyName(s: string, k: TSymKind): string = result.add s[i] inc i -proc replaceInFile(info: TLineInfo; newName: string) = - loadFile(info) +proc differ*(line: string, a, b: int, x: string): bool = + let y = line[a..b] + result = cmpIgnoreStyle(y, x) == 0 and y != x - let line = gSourceFiles[info.fileIndex].lines[info.line-1] +proc replaceInFile(conf: ConfigRef; info: TLineInfo; newName: string) = + let line = conf.m.fileInfos[info.fileIndex.int].lines[info.line.int-1] var first = min(info.col.int, line.len) if first < 0: return #inc first, skipIgnoreCase(line, "proc ", first) - while first > 0 and line[first-1] in prettybase.Letters: dec first + while first > 0 and line[first-1] in Letters: dec first if first < 0: return if line[first] == '`': inc first @@ -107,48 +119,58 @@ proc replaceInFile(info: TLineInfo; newName: string) = if differ(line, first, last, newName): # last-first+1 != newName.len or var x = line.substr(0, first-1) & newName & line.substr(last+1) - system.shallowCopy(gSourceFiles[info.fileIndex].lines[info.line-1], x) - gSourceFiles[info.fileIndex].dirty = true + system.shallowCopy(conf.m.fileInfos[info.fileIndex.int].lines[info.line.int-1], x) + conf.m.fileInfos[info.fileIndex.int].dirty = true -proc checkStyle(info: TLineInfo, s: string, k: TSymKind; sym: PSym) = +proc checkStyle(conf: ConfigRef; cache: IdentCache; info: TLineInfo, s: string, k: TSymKind; sym: PSym) = let beau = beautifyName(s, k) if s != beau: if gStyleCheck == StyleCheck.Auto: - sym.name = getIdent(beau) - replaceInFile(info, beau) + sym.name = getIdent(cache, beau) + replaceInFile(conf, info, beau) else: - message(info, hintName, beau) + message(conf, info, hintName, beau) -proc styleCheckDefImpl(info: TLineInfo; s: PSym; k: TSymKind) = +proc styleCheckDefImpl(conf: ConfigRef; cache: IdentCache; info: TLineInfo; s: PSym; k: TSymKind) = # operators stay as they are: - if k in {skResult, skTemp} or s.name.s[0] notin prettybase.Letters: return + if k in {skResult, skTemp} or s.name.s[0] notin Letters: return if k in {skType, skGenericParam} and sfAnon in s.flags: return if {sfImportc, sfExportc} * s.flags == {} or gCheckExtern: - checkStyle(info, s.name.s, k, s) + checkStyle(conf, cache, info, s.name.s, k, s) + +proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) = + # operators stay as they are: + if k in {skResult, skTemp} or s.name.s[0] notin Letters: return + if k in {skType, skGenericParam} and sfAnon in s.flags: return + let beau = beautifyName(s.name.s, k) + if s.name.s != beau: + message(conf, info, hintName, beau) -template styleCheckDef*(info: TLineInfo; s: PSym; k: TSymKind) = +template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) = + if optCheckNep1 in conf.globalOptions: + nep1CheckDefImpl(conf, info, s, k) when defined(nimfix): - if gStyleCheck != StyleCheck.None: styleCheckDefImpl(info, s, k) + if gStyleCheck != StyleCheck.None: styleCheckDefImpl(conf, cache, info, s, k) -template styleCheckDef*(info: TLineInfo; s: PSym) = - styleCheckDef(info, s, s.kind) -template styleCheckDef*(s: PSym) = - styleCheckDef(s.info, s, s.kind) +template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym) = + styleCheckDef(conf, info, s, s.kind) +template styleCheckDef*(conf: ConfigRef; s: PSym) = + styleCheckDef(conf, s.info, s, s.kind) -proc styleCheckUseImpl(info: TLineInfo; s: PSym) = - if info.fileIndex < 0: return +proc styleCheckUseImpl(conf: ConfigRef; info: TLineInfo; s: PSym) = + if info.fileIndex.int < 0: return # we simply convert it to what it looks like in the definition # for consistency # operators stay as they are: - if s.kind in {skResult, skTemp} or s.name.s[0] notin prettybase.Letters: + if s.kind in {skResult, skTemp} or s.name.s[0] notin Letters: return if s.kind in {skType, skGenericParam} and sfAnon in s.flags: return let newName = s.name.s - replaceInFile(info, newName) + replaceInFile(conf, info, newName) #if newName == "File": writeStackTrace() template styleCheckUse*(info: TLineInfo; s: PSym) = when defined(nimfix): - if gStyleCheck != StyleCheck.None: styleCheckUseImpl(info, s) + if gStyleCheck != StyleCheck.None: styleCheckUseImpl(conf, info, s) diff --git a/compiler/llstream.nim b/compiler/llstream.nim index 42bbb7600..9cd329320 100644 --- a/compiler/llstream.nim +++ b/compiler/llstream.nim @@ -87,9 +87,9 @@ proc endsWithOpr*(x: string): bool = result = x.endsWith(LineContinuationOprs) proc continueLine(line: string, inTripleString: bool): bool {.inline.} = - result = inTripleString or - line[0] == ' ' or - line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs) + result = inTripleString or line.len > 0 and ( + line[0] == ' ' or + line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs)) proc countTriples(s: string): int = var i = 0 diff --git a/compiler/lookups.nim b/compiler/lookups.nim index c409acc59..ec9c130e3 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -10,25 +10,25 @@ # This module implements lookup helpers. import - intsets, ast, astalgo, idents, semdata, types, msgs, options, rodread, - renderer, wordrecg, idgen, nimfix.prettybase + intsets, ast, astalgo, idents, semdata, types, msgs, options, + renderer, wordrecg, idgen, nimfix/prettybase, lineinfos, strutils -proc ensureNoMissingOrUnusedSymbols(scope: PScope) +proc ensureNoMissingOrUnusedSymbols(c: PContext; scope: PScope) -proc noidentError(n, origin: PNode) = +proc noidentError(conf: ConfigRef; n, origin: PNode) = var m = "" if origin != nil: m.add "in expression '" & origin.renderTree & "': " m.add "identifier expected, but found '" & n.renderTree & "'" - localError(n.info, m) + localError(conf, n.info, m) -proc considerQuotedIdent*(n: PNode, origin: PNode = nil): PIdent = +proc considerQuotedIdent*(c: PContext; n: PNode, origin: PNode = nil): PIdent = ## Retrieve a PIdent from a PNode, taking into account accent nodes. ## ``origin`` can be nil. If it is not nil, it is used for a better ## error message. template handleError(n, origin: PNode) = - noidentError(n, origin) - result = getIdent"<Error>" + noidentError(c.config, n, origin) + result = getIdent(c.cache, "<Error>") case n.kind of nkIdent: result = n.ident @@ -36,7 +36,7 @@ proc considerQuotedIdent*(n: PNode, origin: PNode = nil): PIdent = of nkAccQuoted: case n.len of 0: handleError(n, origin) - of 1: result = considerQuotedIdent(n.sons[0], origin) + of 1: result = considerQuotedIdent(c, n.sons[0], origin) else: var id = "" for i in 0..<n.len: @@ -44,8 +44,9 @@ proc considerQuotedIdent*(n: PNode, origin: PNode = nil): PIdent = case x.kind of nkIdent: id.add(x.ident.s) of nkSym: id.add(x.sym.name.s) + of nkLiterals - nkFloatLiterals: id.add(x.renderTree) else: handleError(n, origin) - result = getIdent(id) + result = getIdent(c.cache, id) of nkOpenSymChoice, nkClosedSymChoice: if n[0].kind == nkSym: result = n.sons[0].sym.name @@ -70,7 +71,7 @@ proc rawCloseScope*(c: PContext) = c.currentScope = c.currentScope.parent proc closeScope*(c: PContext) = - ensureNoMissingOrUnusedSymbols(c.currentScope) + ensureNoMissingOrUnusedSymbols(c, c.currentScope) rawCloseScope(c) iterator walkScopes*(scope: PScope): PScope = @@ -79,15 +80,15 @@ iterator walkScopes*(scope: PScope): PScope = yield current current = current.parent -proc skipAlias*(s: PSym; n: PNode): PSym = +proc skipAlias*(s: PSym; n: PNode; conf: ConfigRef): PSym = if s == nil or s.kind != skAlias: result = s else: result = s.owner - if gCmd == cmdPretty: - prettybase.replaceDeprecated(n.info, s, result) + if conf.cmd == cmdPretty: + prettybase.replaceDeprecated(conf, n.info, s, result) else: - message(n.info, warnDeprecated, "use " & result.name.s & " instead; " & + message(conf, n.info, warnDeprecated, "use " & result.name.s & " instead; " & s.name.s) proc localSearchInScope*(c: PContext, s: PIdent): PSym = @@ -99,15 +100,16 @@ proc searchInScopes*(c: PContext, s: PIdent): PSym = if result != nil: return result = nil -proc debugScopes*(c: PContext; limit=0) {.deprecated.} = - var i = 0 - for scope in walkScopes(c.currentScope): - echo "scope ", i - for h in 0 .. high(scope.symbols.data): - if scope.symbols.data[h] != nil: - echo scope.symbols.data[h].name.s - if i == limit: break - inc i +when declared(echo): + proc debugScopes*(c: PContext; limit=0) {.deprecated.} = + var i = 0 + for scope in walkScopes(c.currentScope): + echo "scope ", i + for h in 0 .. high(scope.symbols.data): + if scope.symbols.data[h] != nil: + echo scope.symbols.data[h].name.s + if i == limit: break + inc i proc searchInScopes*(c: PContext, s: PIdent, filter: TSymKinds): PSym = for scope in walkScopes(c.currentScope): @@ -124,21 +126,21 @@ proc errorSym*(c: PContext, n: PNode): PSym = # ensure that 'considerQuotedIdent' can't fail: if m.kind == nkDotExpr: m = m.sons[1] let ident = if m.kind in {nkIdent, nkSym, nkAccQuoted}: - considerQuotedIdent(m) + considerQuotedIdent(c, m) else: - getIdent("err:" & renderTree(m)) - result = newSym(skError, ident, getCurrOwner(c), n.info) + getIdent(c.cache, "err:" & renderTree(m)) + result = newSym(skError, ident, getCurrOwner(c), n.info, {}) result.typ = errorType(c) incl(result.flags, sfDiscardable) # pretend it's imported from some unknown module to prevent cascading errors: - if gCmd != cmdInteractive and c.compilesContextId == 0: + if c.config.cmd != cmdInteractive and c.compilesContextId == 0: c.importTable.addSym(result) type TOverloadIterMode* = enum oimDone, oimNoQualifier, oimSelfModule, oimOtherModule, oimSymChoice, oimSymChoiceLocalLookup - TOverloadIter*{.final.} = object + TOverloadIter* = object it*: TIdentIter m*: PSym mode*: TOverloadIterMode @@ -146,71 +148,71 @@ type scope*: PScope inSymChoice: IntSet -proc getSymRepr*(s: PSym): string = +proc getSymRepr*(conf: ConfigRef; s: PSym): string = case s.kind of skProc, skFunc, skMethod, skConverter, skIterator: - result = getProcHeader(s) + result = getProcHeader(conf, s) else: result = s.name.s -proc ensureNoMissingOrUnusedSymbols(scope: PScope) = +proc ensureNoMissingOrUnusedSymbols(c: PContext; scope: PScope) = # check if all symbols have been used and defined: var it: TTabIter var s = initTabIter(it, scope.symbols) var missingImpls = 0 while s != nil: - if sfForward in s.flags and s.kind != skType: + if sfForward in s.flags and s.kind notin {skType, skModule}: # too many 'implementation of X' errors are annoying # and slow 'suggest' down: if missingImpls == 0: - localError(s.info, errImplOfXexpected, getSymRepr(s)) + localError(c.config, s.info, "implementation of '$1' expected" % + getSymRepr(c.config, s)) inc missingImpls elif {sfUsed, sfExported} * s.flags == {} and optHints in s.options: - # BUGFIX: check options in s! if s.kind notin {skForVar, skParam, skMethod, skUnknown, skGenericParam}: # XXX: implicit type params are currently skTypes # maybe they can be made skGenericParam as well. if s.typ != nil and tfImplicitTypeParam notin s.typ.flags and s.typ.kind != tyGenericParam: - message(s.info, hintXDeclaredButNotUsed, getSymRepr(s)) + message(c.config, s.info, hintXDeclaredButNotUsed, getSymRepr(c.config, s)) s = nextIter(it, scope.symbols) -proc wrongRedefinition*(info: TLineInfo, s: string) = - if gCmd != cmdInteractive: - localError(info, errAttemptToRedefine, s) +proc wrongRedefinition*(c: PContext; info: TLineInfo, s: string) = + if c.config.cmd != cmdInteractive: + localError(c.config, info, "redefinition of '$1'" % s) proc addDecl*(c: PContext, sym: PSym, info: TLineInfo) = if not c.currentScope.addUniqueSym(sym): - wrongRedefinition(info, sym.name.s) + wrongRedefinition(c, info, sym.name.s) proc addDecl*(c: PContext, sym: PSym) = if not c.currentScope.addUniqueSym(sym): - wrongRedefinition(sym.info, sym.name.s) + wrongRedefinition(c, sym.info, sym.name.s) proc addPrelimDecl*(c: PContext, sym: PSym) = discard c.currentScope.addUniqueSym(sym) -proc addDeclAt*(scope: PScope, sym: PSym) = +proc addDeclAt*(c: PContext; scope: PScope, sym: PSym) = if not scope.addUniqueSym(sym): - wrongRedefinition(sym.info, sym.name.s) + wrongRedefinition(c, sym.info, sym.name.s) proc addInterfaceDeclAux(c: PContext, sym: PSym) = if sfExported in sym.flags: # add to interface: if c.module != nil: strTableAdd(c.module.tab, sym) - else: internalError(sym.info, "addInterfaceDeclAux") + else: internalError(c.config, sym.info, "addInterfaceDeclAux") proc addInterfaceDeclAt*(c: PContext, scope: PScope, sym: PSym) = - addDeclAt(scope, sym) + addDeclAt(c, scope, sym) addInterfaceDeclAux(c, sym) -proc addOverloadableSymAt*(scope: PScope, fn: PSym) = +proc addOverloadableSymAt*(c: PContext; scope: PScope, fn: PSym) = if fn.kind notin OverloadableSyms: - internalError(fn.info, "addOverloadableSymAt") + internalError(c.config, fn.info, "addOverloadableSymAt") return let check = strTableGet(scope.symbols, fn.name) if check != nil and check.kind notin OverloadableSyms: - wrongRedefinition(fn.info, fn.name.s) + wrongRedefinition(c, fn.info, fn.name.s) else: scope.addSym(fn) @@ -221,12 +223,10 @@ proc addInterfaceDecl*(c: PContext, sym: PSym) = proc addInterfaceOverloadableSymAt*(c: PContext, scope: PScope, sym: PSym) = # it adds the symbol to the interface if appropriate - addOverloadableSymAt(scope, sym) + addOverloadableSymAt(c, scope, sym) addInterfaceDeclAux(c, sym) when defined(nimfix): - import strutils - # when we cannot find the identifier, retry with a changed identifer: proc altSpelling(x: PIdent): PIdent = case x.s[0] @@ -244,7 +244,7 @@ else: template fixSpelling(n: PNode; ident: PIdent; op: untyped) = discard proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) = - var err = "Error: ambiguous identifier: '" & s.name.s & "'" + var err = "ambiguous identifier: '" & s.name.s & "'" var ti: TIdentIter var candidate = initIdentIter(ti, c.importTable.symbols, s.name) var i = 0 @@ -254,22 +254,22 @@ proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) = err.add candidate.owner.name.s & "." & candidate.name.s candidate = nextIdentIter(ti, c.importTable.symbols) inc i - localError(info, errGenerated, err) + localError(c.config, info, errGenerated, err) proc errorUndeclaredIdentifier*(c: PContext; info: TLineInfo; name: string) = var err = "undeclared identifier: '" & name & "'" if c.recursiveDep.len > 0: - err.add "\nThis might be caused by a recursive module dependency: " + err.add "\nThis might be caused by a recursive module dependency:\n" err.add c.recursiveDep # prevent excessive errors for 'nim check' - c.recursiveDep = nil - localError(info, errGenerated, err) + c.recursiveDep = "" + localError(c.config, info, errGenerated, err) proc lookUp*(c: PContext, n: PNode): PSym = # Looks up a symbol. Generates an error in case of nil. case n.kind of nkIdent: - result = searchInScopes(c, n.ident).skipAlias(n) + result = searchInScopes(c, n.ident).skipAlias(n, c.config) if result == nil: fixSpelling(n, n.ident, searchInScopes) errorUndeclaredIdentifier(c, n.info, n.ident.s) @@ -277,18 +277,19 @@ proc lookUp*(c: PContext, n: PNode): PSym = of nkSym: result = n.sym of nkAccQuoted: - var ident = considerQuotedIdent(n) - result = searchInScopes(c, ident).skipAlias(n) + var ident = considerQuotedIdent(c, n) + result = searchInScopes(c, ident).skipAlias(n, c.config) if result == nil: fixSpelling(n, ident, searchInScopes) errorUndeclaredIdentifier(c, n.info, ident.s) result = errorSym(c, n) else: - internalError(n.info, "lookUp") + internalError(c.config, n.info, "lookUp") return if contains(c.ambiguousSymbols, result.id): errorUseQualifier(c, n.info, result) - if result.kind == skStub: loadStub(result) + when false: + if result.kind == skStub: loadStub(result) type TLookupFlag* = enum @@ -298,11 +299,11 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym = const allExceptModule = {low(TSymKind)..high(TSymKind)}-{skModule,skPackage} case n.kind of nkIdent, nkAccQuoted: - var ident = considerQuotedIdent(n) + var ident = considerQuotedIdent(c, n) if checkModule in flags: - result = searchInScopes(c, ident).skipAlias(n) + result = searchInScopes(c, ident).skipAlias(n, c.config) else: - result = searchInScopes(c, ident, allExceptModule).skipAlias(n) + result = searchInScopes(c, ident, allExceptModule).skipAlias(n, c.config) if result == nil and checkPureEnumFields in flags: result = strTableGet(c.pureEnumFields, ident) if result == nil and checkUndeclared in flags: @@ -324,12 +325,12 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym = if n.sons[1].kind == nkIdent: ident = n.sons[1].ident elif n.sons[1].kind == nkAccQuoted: - ident = considerQuotedIdent(n.sons[1]) + ident = considerQuotedIdent(c, n.sons[1]) if ident != nil: if m == c.module: - result = strTableGet(c.topLevelScope.symbols, ident).skipAlias(n) + result = strTableGet(c.topLevelScope.symbols, ident).skipAlias(n, c.config) else: - result = strTableGet(m.tab, ident).skipAlias(n) + result = strTableGet(m.tab, ident).skipAlias(n, c.config) if result == nil and checkUndeclared in flags: fixSpelling(n.sons[1], ident, searchInScopes) errorUndeclaredIdentifier(c, n.sons[1].info, ident.s) @@ -338,21 +339,22 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym = result = n.sons[1].sym elif checkUndeclared in flags and n.sons[1].kind notin {nkOpenSymChoice, nkClosedSymChoice}: - localError(n.sons[1].info, errIdentifierExpected, + localError(c.config, n.sons[1].info, "identifier expected, but got: " & renderTree(n.sons[1])) result = errorSym(c, n.sons[1]) else: result = nil - if result != nil and result.kind == skStub: loadStub(result) + when false: + if result != nil and result.kind == skStub: loadStub(result) proc initOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym = case n.kind of nkIdent, nkAccQuoted: - var ident = considerQuotedIdent(n) + var ident = considerQuotedIdent(c, n) o.scope = c.currentScope o.mode = oimNoQualifier while true: - result = initIdentIter(o.it, o.scope.symbols, ident).skipAlias(n) + result = initIdentIter(o.it, o.scope.symbols, ident).skipAlias(n, c.config) if result != nil: break else: @@ -369,17 +371,17 @@ proc initOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym = if n.sons[1].kind == nkIdent: ident = n.sons[1].ident elif n.sons[1].kind == nkAccQuoted: - ident = considerQuotedIdent(n.sons[1], n) + ident = considerQuotedIdent(c, n.sons[1], n) if ident != nil: if o.m == c.module: # a module may access its private members: result = initIdentIter(o.it, c.topLevelScope.symbols, - ident).skipAlias(n) + ident).skipAlias(n, c.config) o.mode = oimSelfModule else: - result = initIdentIter(o.it, o.m.tab, ident).skipAlias(n) + result = initIdentIter(o.it, o.m.tab, ident).skipAlias(n, c.config) else: - noidentError(n.sons[1], n) + noidentError(c.config, n.sons[1], n) result = errorSym(c, n.sons[1]) of nkClosedSymChoice, nkOpenSymChoice: o.mode = oimSymChoice @@ -392,7 +394,8 @@ proc initOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym = o.inSymChoice = initIntSet() incl(o.inSymChoice, result.id) else: discard - if result != nil and result.kind == skStub: loadStub(result) + when false: + if result != nil and result.kind == skStub: loadStub(result) proc lastOverloadScope*(o: TOverloadIter): int = case o.mode @@ -407,18 +410,18 @@ proc nextOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym = result = nil of oimNoQualifier: if o.scope != nil: - result = nextIdentIter(o.it, o.scope.symbols).skipAlias(n) + result = nextIdentIter(o.it, o.scope.symbols).skipAlias(n, c.config) while result == nil: o.scope = o.scope.parent if o.scope == nil: break - result = initIdentIter(o.it, o.scope.symbols, o.it.name).skipAlias(n) + result = initIdentIter(o.it, o.scope.symbols, o.it.name).skipAlias(n, c.config) # BUGFIX: o.it.name <-> n.ident else: result = nil of oimSelfModule: - result = nextIdentIter(o.it, c.topLevelScope.symbols).skipAlias(n) + result = nextIdentIter(o.it, c.topLevelScope.symbols).skipAlias(n, c.config) of oimOtherModule: - result = nextIdentIter(o.it, o.m.tab).skipAlias(n) + result = nextIdentIter(o.it, o.m.tab).skipAlias(n, c.config) of oimSymChoice: if o.symChoiceIndex < sonsLen(n): result = n.sons[o.symChoiceIndex].sym @@ -429,21 +432,22 @@ proc nextOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym = o.mode = oimSymChoiceLocalLookup o.scope = c.currentScope result = firstIdentExcluding(o.it, o.scope.symbols, - n.sons[0].sym.name, o.inSymChoice).skipAlias(n) + n.sons[0].sym.name, o.inSymChoice).skipAlias(n, c.config) while result == nil: o.scope = o.scope.parent if o.scope == nil: break result = firstIdentExcluding(o.it, o.scope.symbols, - n.sons[0].sym.name, o.inSymChoice).skipAlias(n) + n.sons[0].sym.name, o.inSymChoice).skipAlias(n, c.config) of oimSymChoiceLocalLookup: - result = nextIdentExcluding(o.it, o.scope.symbols, o.inSymChoice).skipAlias(n) + result = nextIdentExcluding(o.it, o.scope.symbols, o.inSymChoice).skipAlias(n, c.config) while result == nil: o.scope = o.scope.parent if o.scope == nil: break result = firstIdentExcluding(o.it, o.scope.symbols, - n.sons[0].sym.name, o.inSymChoice).skipAlias(n) + n.sons[0].sym.name, o.inSymChoice).skipAlias(n, c.config) - if result != nil and result.kind == skStub: loadStub(result) + when false: + if result != nil and result.kind == skStub: loadStub(result) proc pickSym*(c: PContext, n: PNode; kinds: set[TSymKind]; flags: TSymFlags = {}): PSym = @@ -455,5 +459,3 @@ proc pickSym*(c: PContext, n: PNode; kinds: set[TSymKind]; else: return nil # ambiguous a = nextOverloadIter(o, c, n) -proc isInfixAs*(n: PNode): bool = - return n.kind == nkInfix and considerQuotedIdent(n[0]).s == "as" \ No newline at end of file diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim index 8510bf7ee..1b17f620c 100644 --- a/compiler/lowerings.nim +++ b/compiler/lowerings.nim @@ -12,26 +12,27 @@ const genPrefix* = ":tmp" # prefix for generated names -import ast, astalgo, types, idents, magicsys, msgs, options +import ast, astalgo, types, idents, magicsys, msgs, options, modulegraphs, + lineinfos from trees import getMagic proc newDeref*(n: PNode): PNode {.inline.} = result = newNodeIT(nkHiddenDeref, n.info, n.typ.sons[0]) addSon(result, n) -proc newTupleAccess*(tup: PNode, i: int): PNode = +proc newTupleAccess*(g: ModuleGraph; tup: PNode, i: int): PNode = result = newNodeIT(nkBracketExpr, tup.info, tup.typ.skipTypes( abstractInst).sons[i]) addSon(result, copyTree(tup)) - var lit = newNodeIT(nkIntLit, tup.info, getSysType(tyInt)) + var lit = newNodeIT(nkIntLit, tup.info, getSysType(g, tup.info, tyInt)) lit.intVal = i addSon(result, lit) proc addVar*(father, v: PNode) = var vpart = newNodeI(nkIdentDefs, v.info, 3) vpart.sons[0] = v - vpart.sons[1] = ast.emptyNode - vpart.sons[2] = ast.emptyNode + vpart.sons[1] = newNodeI(nkEmpty, v.info) + vpart.sons[2] = vpart[1] addSon(father, vpart) proc newAsgnStmt(le, ri: PNode): PNode = @@ -44,12 +45,12 @@ proc newFastAsgnStmt(le, ri: PNode): PNode = result.sons[0] = le result.sons[1] = ri -proc lowerTupleUnpacking*(n: PNode; owner: PSym): PNode = +proc lowerTupleUnpacking*(g: ModuleGraph; n: PNode; owner: PSym): PNode = assert n.kind == nkVarTuple let value = n.lastSon result = newNodeI(nkStmtList, n.info) - var temp = newSym(skTemp, getIdent(genPrefix), owner, value.info) + var temp = newSym(skTemp, getIdent(g.cache, genPrefix), owner, value.info, g.config.options) temp.typ = skipTypes(value.typ, abstractInst) incl(temp.flags, sfFromGeneric) @@ -61,7 +62,7 @@ proc lowerTupleUnpacking*(n: PNode; owner: PSym): PNode = result.add newAsgnStmt(tempAsNode, value) for i in 0 .. n.len-3: if n.sons[i].kind == nkSym: v.addVar(n.sons[i]) - result.add newAsgnStmt(n.sons[i], newTupleAccess(tempAsNode, i)) + result.add newAsgnStmt(n.sons[i], newTupleAccess(g, tempAsNode, i)) proc newTupleAccessRaw*(tup: PNode, i: int): PNode = result = newNodeI(nkBracketExpr, tup.info) @@ -73,17 +74,17 @@ proc newTupleAccessRaw*(tup: PNode, i: int): PNode = proc newTryFinally*(body, final: PNode): PNode = result = newTree(nkTryStmt, body, newTree(nkFinally, final)) -proc lowerTupleUnpackingForAsgn*(n: PNode; owner: PSym): PNode = +proc lowerTupleUnpackingForAsgn*(g: ModuleGraph; n: PNode; owner: PSym): PNode = let value = n.lastSon result = newNodeI(nkStmtList, n.info) - var temp = newSym(skLet, getIdent("_"), owner, value.info) + var temp = newSym(skLet, getIdent(g.cache, "_"), owner, value.info, owner.options) var v = newNodeI(nkLetSection, value.info) let tempAsNode = newSymNode(temp) #newIdentNode(getIdent(genPrefix & $temp.id), value.info) var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3) vpart.sons[0] = tempAsNode - vpart.sons[1] = ast.emptyNode + vpart.sons[1] = newNodeI(nkEmpty, value.info) vpart.sons[2] = value addSon(v, vpart) result.add(v) @@ -92,10 +93,10 @@ proc lowerTupleUnpackingForAsgn*(n: PNode; owner: PSym): PNode = for i in 0 .. lhs.len-1: result.add newAsgnStmt(lhs.sons[i], newTupleAccessRaw(tempAsNode, i)) -proc lowerSwap*(n: PNode; owner: PSym): PNode = +proc lowerSwap*(g: ModuleGraph; n: PNode; owner: PSym): PNode = result = newNodeI(nkStmtList, n.info) # note: cannot use 'skTemp' here cause we really need the copy for the VM :-( - var temp = newSym(skVar, getIdent(genPrefix), owner, n.info) + var temp = newSym(skVar, getIdent(g.cache, genPrefix), owner, n.info, owner.options) temp.typ = n.sons[1].typ incl(temp.flags, sfFromGeneric) @@ -104,7 +105,7 @@ proc lowerSwap*(n: PNode; owner: PSym): PNode = var vpart = newNodeI(nkIdentDefs, v.info, 3) vpart.sons[0] = tempAsNode - vpart.sons[1] = ast.emptyNode + vpart.sons[1] = newNodeI(nkEmpty, v.info) vpart.sons[2] = n[1] addSon(v, vpart) @@ -112,16 +113,16 @@ proc lowerSwap*(n: PNode; owner: PSym): PNode = result.add newFastAsgnStmt(n[1], n[2]) result.add newFastAsgnStmt(n[2], tempAsNode) -proc createObj*(owner: PSym, info: TLineInfo; final=true): PType = +proc createObj*(g: ModuleGraph; owner: PSym, info: TLineInfo; final=true): PType = result = newType(tyObject, owner) if final: rawAddSon(result, nil) incl result.flags, tfFinal else: - rawAddSon(result, getCompilerProc("RootObj").typ) + rawAddSon(result, getCompilerProc(g, "RootObj").typ) result.n = newNodeI(nkRecList, info) - let s = newSym(skType, getIdent("Env_" & info.toFilename), - owner, info) + let s = newSym(skType, getIdent(g.cache, "Env_" & toFilename(g.config, info)), + owner, info, owner.options) incl s.flags, sfAnon s.typ = result result.sym = s @@ -171,10 +172,11 @@ proc lookupInRecord(n: PNode, id: int): PSym = if n.sym.id == -abs(id): result = n.sym else: discard -proc addField*(obj: PType; s: PSym) = +proc addField*(obj: PType; s: PSym; cache: IdentCache) = # because of 'gensym' support, we have to mangle the name with its ID. # This is hacky but the clean solution is much more complex than it looks. - var field = newSym(skField, getIdent(s.name.s & $obj.n.len), s.owner, s.info) + var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len), s.owner, s.info, + s.options) field.id = -s.id let t = skipIntLit(s.typ) field.typ = t @@ -182,10 +184,11 @@ proc addField*(obj: PType; s: PSym) = field.position = sonsLen(obj.n) addSon(obj.n, newSymNode(field)) -proc addUniqueField*(obj: PType; s: PSym): PSym {.discardable.} = +proc addUniqueField*(obj: PType; s: PSym; cache: IdentCache): PSym {.discardable.} = result = lookupInRecord(obj.n, s.id) if result == nil: - var field = newSym(skField, getIdent(s.name.s & $obj.n.len), s.owner, s.info) + var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len), s.owner, s.info, + s.options) field.id = -s.id let t = skipIntLit(s.typ) field.typ = t @@ -218,20 +221,20 @@ proc indirectAccess*(a: PNode, b: int, info: TLineInfo): PNode = #if field == nil: # echo "FIELD ", b # debug deref.typ - internalAssert field != nil + assert field != nil addSon(deref, a) result = newNodeI(nkDotExpr, info) addSon(result, deref) addSon(result, newSymNode(field)) result.typ = field.typ -proc indirectAccess(a: PNode, b: string, info: TLineInfo): PNode = +proc indirectAccess(a: PNode, b: string, info: TLineInfo; cache: IdentCache): PNode = # returns a[].b as a node var deref = newNodeI(nkHiddenDeref, info) deref.typ = a.typ.skipTypes(abstractInst).sons[0] var t = deref.typ.skipTypes(abstractInst) var field: PSym - let bb = getIdent(b) + let bb = getIdent(cache, b) while true: assert t.kind == tyObject field = getSymFromList(t.n, bb) @@ -242,7 +245,7 @@ proc indirectAccess(a: PNode, b: string, info: TLineInfo): PNode = #if field == nil: # echo "FIELD ", b # debug deref.typ - internalAssert field != nil + assert field != nil addSon(deref, a) result = newNodeI(nkDotExpr, info) addSon(result, deref) @@ -278,12 +281,12 @@ proc genDeref*(n: PNode): PNode = n.typ.skipTypes(abstractInst).sons[0]) result.add n -proc callCodegenProc*(name: string, arg1: PNode; +proc callCodegenProc*(g: ModuleGraph; name: string, arg1: PNode; arg2, arg3, optionalArgs: PNode = nil): PNode = result = newNodeI(nkCall, arg1.info) - let sym = magicsys.getCompilerProc(name) + let sym = magicsys.getCompilerProc(g, name) if sym == nil: - localError(arg1.info, errSystemNeeds, name) + localError(g.config, arg1.info, "system module needs: " & name) else: result.add newSymNode(sym) result.add arg1 @@ -333,23 +336,36 @@ proc typeNeedsNoDeepCopy(t: PType): bool = if t.kind in {tyVar, tyLent, tySequence}: t = t.lastSon result = not containsGarbageCollectedRef(t) -proc addLocalVar(varSection, varInit: PNode; owner: PSym; typ: PType; +proc hoistExpr*(varSection, expr: PNode, name: PIdent, owner: PSym): PSym = + result = newSym(skLet, name, owner, varSection.info, owner.options) + result.flags.incl sfHoisted + result.typ = expr.typ + + var varDef = newNodeI(nkIdentDefs, varSection.info, 3) + varDef.sons[0] = newSymNode(result) + varDef.sons[1] = newNodeI(nkEmpty, varSection.info) + varDef.sons[2] = expr + + varSection.add varDef + +proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; owner: PSym; typ: PType; v: PNode; useShallowCopy=false): PSym = - result = newSym(skTemp, getIdent(genPrefix), owner, varSection.info) + result = newSym(skTemp, getIdent(g.cache, genPrefix), owner, varSection.info, + owner.options) result.typ = typ incl(result.flags, sfFromGeneric) var vpart = newNodeI(nkIdentDefs, varSection.info, 3) vpart.sons[0] = newSymNode(result) - vpart.sons[1] = ast.emptyNode - vpart.sons[2] = if varInit.isNil: v else: ast.emptyNode + vpart.sons[1] = newNodeI(nkEmpty, varSection.info) + vpart.sons[2] = if varInit.isNil: v else: vpart[1] varSection.add vpart if varInit != nil: if useShallowCopy and typeNeedsNoDeepCopy(typ): varInit.add newFastAsgnStmt(newSymNode(result), v) else: let deepCopyCall = newNodeI(nkCall, varInit.info, 3) - deepCopyCall.sons[0] = newSymNode(getSysMagic("deepCopy", mDeepCopy)) + deepCopyCall.sons[0] = newSymNode(getSysMagic(g, varSection.info, "deepCopy", mDeepCopy)) deepCopyCall.sons[1] = newSymNode(result) deepCopyCall.sons[2] = v varInit.add deepCopyCall @@ -384,58 +400,58 @@ stmtList: """ -proc createWrapperProc(f: PNode; threadParam, argsParam: PSym; +proc createWrapperProc(g: ModuleGraph; f: PNode; threadParam, argsParam: PSym; varSection, varInit, call, barrier, fv: PNode; spawnKind: TSpawnResult): PSym = var body = newNodeI(nkStmtList, f.info) var threadLocalBarrier: PSym if barrier != nil: var varSection2 = newNodeI(nkVarSection, barrier.info) - threadLocalBarrier = addLocalVar(varSection2, nil, argsParam.owner, + threadLocalBarrier = addLocalVar(g, varSection2, nil, argsParam.owner, barrier.typ, barrier) body.add varSection2 - body.add callCodegenProc("barrierEnter", threadLocalBarrier.newSymNode) + body.add callCodegenProc(g, "barrierEnter", threadLocalBarrier.newSymNode) var threadLocalProm: PSym if spawnKind == srByVar: - threadLocalProm = addLocalVar(varSection, nil, argsParam.owner, fv.typ, fv) + threadLocalProm = addLocalVar(g, varSection, nil, argsParam.owner, fv.typ, fv) elif fv != nil: - internalAssert fv.typ.kind == tyGenericInst - threadLocalProm = addLocalVar(varSection, nil, argsParam.owner, fv.typ, fv) + internalAssert g.config, fv.typ.kind == tyGenericInst + threadLocalProm = addLocalVar(g, varSection, nil, argsParam.owner, fv.typ, fv) body.add varSection body.add varInit if fv != nil and spawnKind != srByVar: # generate: # fv.owner = threadParam body.add newAsgnStmt(indirectAccess(threadLocalProm.newSymNode, - "owner", fv.info), threadParam.newSymNode) + "owner", fv.info, g.cache), threadParam.newSymNode) - body.add callCodegenProc("nimArgsPassingDone", threadParam.newSymNode) + body.add callCodegenProc(g, "nimArgsPassingDone", threadParam.newSymNode) if spawnKind == srByVar: body.add newAsgnStmt(genDeref(threadLocalProm.newSymNode), call) elif fv != nil: let fk = fv.typ.sons[1].flowVarKind if fk == fvInvalid: - localError(f.info, "cannot create a flowVar of type: " & + localError(g.config, f.info, "cannot create a flowVar of type: " & typeToString(fv.typ.sons[1])) body.add newAsgnStmt(indirectAccess(threadLocalProm.newSymNode, - if fk == fvGC: "data" else: "blob", fv.info), call) + if fk == fvGC: "data" else: "blob", fv.info, g.cache), call) if fk == fvGC: let incRefCall = newNodeI(nkCall, fv.info, 2) - incRefCall.sons[0] = newSymNode(getSysMagic("GCref", mGCref)) + incRefCall.sons[0] = newSymNode(getSysMagic(g, fv.info, "GCref", mGCref)) incRefCall.sons[1] = indirectAccess(threadLocalProm.newSymNode, - "data", fv.info) + "data", fv.info, g.cache) body.add incRefCall if barrier == nil: # by now 'fv' is shared and thus might have beeen overwritten! we need # to use the thread-local view instead: - body.add callCodegenProc("nimFlowVarSignal", threadLocalProm.newSymNode) + body.add callCodegenProc(g, "nimFlowVarSignal", threadLocalProm.newSymNode) else: body.add call if barrier != nil: - body.add callCodegenProc("barrierLeave", threadLocalBarrier.newSymNode) + body.add callCodegenProc(g, "barrierLeave", threadLocalBarrier.newSymNode) var params = newNodeI(nkFormalParams, f.info) - params.add emptyNode + params.add newNodeI(nkEmpty, f.info) params.add threadParam.newSymNode params.add argsParam.newSymNode @@ -449,38 +465,43 @@ proc createWrapperProc(f: PNode; threadParam, argsParam: PSym; t.n.add argsParam.newSymNode let name = (if f.kind == nkSym: f.sym.name.s else: genPrefix) & "Wrapper" - result = newSym(skProc, getIdent(name), argsParam.owner, f.info) - result.ast = newProcNode(nkProcDef, f.info, body, params, newSymNode(result)) + result = newSym(skProc, getIdent(g.cache, name), argsParam.owner, f.info, + argsParam.options) + let emptyNode = newNodeI(nkEmpty, f.info) + result.ast = newProcNode(nkProcDef, f.info, body = body, + params = params, name = newSymNode(result), pattern = emptyNode, + genericParams = emptyNode, pragmas = emptyNode, + exceptions = emptyNode) result.typ = t proc createCastExpr(argsParam: PSym; objType: PType): PNode = result = newNodeI(nkCast, argsParam.info) - result.add emptyNode + result.add newNodeI(nkEmpty, argsParam.info) result.add newSymNode(argsParam) result.typ = newType(tyPtr, objType.owner) result.typ.rawAddSon(objType) -proc setupArgsForConcurrency(n: PNode; objType: PType; scratchObj: PSym, +proc setupArgsForConcurrency(g: ModuleGraph; n: PNode; objType: PType; scratchObj: PSym, castExpr, call, varSection, varInit, result: PNode) = let formals = n[0].typ.n - let tmpName = getIdent(genPrefix) + let tmpName = getIdent(g.cache, genPrefix) for i in 1 ..< n.len: # we pick n's type here, which hopefully is 'tyArray' and not # 'tyOpenArray': var argType = n[i].typ.skipTypes(abstractInst) if i < formals.len and formals[i].typ.kind in {tyVar, tyLent}: - localError(n[i].info, "'spawn'ed function cannot have a 'var' parameter") + localError(g.config, n[i].info, "'spawn'ed function cannot have a 'var' parameter") #elif containsTyRef(argType): # localError(n[i].info, "'spawn'ed function cannot refer to 'ref'/closure") let fieldname = if i < formals.len: formals[i].sym.name else: tmpName - var field = newSym(skField, fieldname, objType.owner, n.info) + var field = newSym(skField, fieldname, objType.owner, n.info, g.config.options) field.typ = argType - objType.addField(field) + objType.addField(field, g.cache) result.add newFastAsgnStmt(newDotExpr(scratchObj, field), n[i]) - let temp = addLocalVar(varSection, varInit, objType.owner, argType, + let temp = addLocalVar(g, varSection, varInit, objType.owner, argType, indirectAccess(castExpr, field, n.info)) call.add(newSymNode(temp)) @@ -501,24 +522,24 @@ proc getRoot*(n: PNode): PSym = if getMagic(n) == mSlice: result = getRoot(n.sons[1]) else: discard -proc newIntLit*(value: BiggestInt): PNode = +proc newIntLit*(g: ModuleGraph; info: TLineInfo; value: BiggestInt): PNode = result = nkIntLit.newIntNode(value) - result.typ = getSysType(tyInt) + result.typ = getSysType(g, info, tyInt) -proc genHigh*(n: PNode): PNode = +proc genHigh*(g: ModuleGraph; n: PNode): PNode = if skipTypes(n.typ, abstractVar).kind == tyArray: - result = newIntLit(lastOrd(skipTypes(n.typ, abstractVar))) + result = newIntLit(g, n.info, lastOrd(g.config, skipTypes(n.typ, abstractVar))) else: result = newNodeI(nkCall, n.info, 2) - result.typ = getSysType(tyInt) - result.sons[0] = newSymNode(getSysMagic("high", mHigh)) + result.typ = getSysType(g, n.info, tyInt) + result.sons[0] = newSymNode(getSysMagic(g, n.info, "high", mHigh)) result.sons[1] = n -proc setupArgsForParallelism(n: PNode; objType: PType; scratchObj: PSym; +proc setupArgsForParallelism(g: ModuleGraph; n: PNode; objType: PType; scratchObj: PSym; castExpr, call, varSection, varInit, result: PNode) = let formals = n[0].typ.n - let tmpName = getIdent(genPrefix) + let tmpName = getIdent(g.cache, genPrefix) # we need to copy the foreign scratch object fields into local variables # for correctness: These are called 'threadLocal' here. for i in 1 ..< n.len: @@ -529,73 +550,73 @@ proc setupArgsForParallelism(n: PNode; objType: PType; scratchObj: PSym; # localError(n.info, "'spawn'ed function cannot refer to 'ref'/closure") let fieldname = if i < formals.len: formals[i].sym.name else: tmpName - var field = newSym(skField, fieldname, objType.owner, n.info) + var field = newSym(skField, fieldname, objType.owner, n.info, g.config.options) if argType.kind in {tyVarargs, tyOpenArray}: # important special case: we always create a zero-copy slice: let slice = newNodeI(nkCall, n.info, 4) slice.typ = n.typ - slice.sons[0] = newSymNode(createMagic("slice", mSlice)) - slice.sons[0].typ = getSysType(tyInt) # fake type - var fieldB = newSym(skField, tmpName, objType.owner, n.info) - fieldB.typ = getSysType(tyInt) - objType.addField(fieldB) + slice.sons[0] = newSymNode(createMagic(g, "slice", mSlice)) + slice.sons[0].typ = getSysType(g, n.info, tyInt) # fake type + var fieldB = newSym(skField, tmpName, objType.owner, n.info, g.config.options) + fieldB.typ = getSysType(g, n.info, tyInt) + objType.addField(fieldB, g.cache) if getMagic(n) == mSlice: let a = genAddrOf(n[1]) field.typ = a.typ - objType.addField(field) + objType.addField(field, g.cache) result.add newFastAsgnStmt(newDotExpr(scratchObj, field), a) - var fieldA = newSym(skField, tmpName, objType.owner, n.info) - fieldA.typ = getSysType(tyInt) - objType.addField(fieldA) + var fieldA = newSym(skField, tmpName, objType.owner, n.info, g.config.options) + fieldA.typ = getSysType(g, n.info, tyInt) + objType.addField(fieldA, g.cache) result.add newFastAsgnStmt(newDotExpr(scratchObj, fieldA), n[2]) result.add newFastAsgnStmt(newDotExpr(scratchObj, fieldB), n[3]) - let threadLocal = addLocalVar(varSection,nil, objType.owner, fieldA.typ, + let threadLocal = addLocalVar(g, varSection,nil, objType.owner, fieldA.typ, indirectAccess(castExpr, fieldA, n.info), useShallowCopy=true) slice.sons[2] = threadLocal.newSymNode else: let a = genAddrOf(n) field.typ = a.typ - objType.addField(field) + objType.addField(field, g.cache) result.add newFastAsgnStmt(newDotExpr(scratchObj, field), a) - result.add newFastAsgnStmt(newDotExpr(scratchObj, fieldB), genHigh(n)) + result.add newFastAsgnStmt(newDotExpr(scratchObj, fieldB), genHigh(g, n)) - slice.sons[2] = newIntLit(0) + slice.sons[2] = newIntLit(g, n.info, 0) # the array itself does not need to go through a thread local variable: slice.sons[1] = genDeref(indirectAccess(castExpr, field, n.info)) - let threadLocal = addLocalVar(varSection,nil, objType.owner, fieldB.typ, + let threadLocal = addLocalVar(g, varSection,nil, objType.owner, fieldB.typ, indirectAccess(castExpr, fieldB, n.info), useShallowCopy=true) slice.sons[3] = threadLocal.newSymNode call.add slice - elif (let size = computeSize(argType); size < 0 or size > 16) and + elif (let size = computeSize(g.config, argType); size < 0 or size > 16) and n.getRoot != nil: # it is more efficient to pass a pointer instead: let a = genAddrOf(n) field.typ = a.typ - objType.addField(field) + objType.addField(field, g.cache) result.add newFastAsgnStmt(newDotExpr(scratchObj, field), a) - let threadLocal = addLocalVar(varSection,nil, objType.owner, field.typ, + let threadLocal = addLocalVar(g, varSection,nil, objType.owner, field.typ, indirectAccess(castExpr, field, n.info), useShallowCopy=true) call.add(genDeref(threadLocal.newSymNode)) else: # boring case field.typ = argType - objType.addField(field) + objType.addField(field, g.cache) result.add newFastAsgnStmt(newDotExpr(scratchObj, field), n) - let threadLocal = addLocalVar(varSection, varInit, + let threadLocal = addLocalVar(g, varSection, varInit, objType.owner, field.typ, indirectAccess(castExpr, field, n.info), useShallowCopy=true) call.add(threadLocal.newSymNode) -proc wrapProcForSpawn*(owner: PSym; spawnExpr: PNode; retType: PType; +proc wrapProcForSpawn*(g: ModuleGraph; owner: PSym; spawnExpr: PNode; retType: PType; barrier, dest: PNode = nil): PNode = # if 'barrier' != nil, then it is in a 'parallel' section and we # generate quite different code @@ -603,35 +624,35 @@ proc wrapProcForSpawn*(owner: PSym; spawnExpr: PNode; retType: PType; let spawnKind = spawnResult(retType, barrier!=nil) case spawnKind of srVoid: - internalAssert dest == nil + internalAssert g.config, dest == nil result = newNodeI(nkStmtList, n.info) of srFlowVar: - internalAssert dest == nil + internalAssert g.config, dest == nil result = newNodeIT(nkStmtListExpr, n.info, retType) of srByVar: - if dest == nil: localError(n.info, "'spawn' must not be discarded") + if dest == nil: localError(g.config, n.info, "'spawn' must not be discarded") result = newNodeI(nkStmtList, n.info) if n.kind notin nkCallKinds: - localError(n.info, "'spawn' takes a call expression") + localError(g.config, n.info, "'spawn' takes a call expression") return - if optThreadAnalysis in gGlobalOptions: + if optThreadAnalysis in g.config.globalOptions: if {tfThread, tfNoSideEffect} * n[0].typ.flags == {}: - localError(n.info, "'spawn' takes a GC safe call expression") + localError(g.config, n.info, "'spawn' takes a GC safe call expression") var - threadParam = newSym(skParam, getIdent"thread", owner, n.info) - argsParam = newSym(skParam, getIdent"args", owner, n.info) + threadParam = newSym(skParam, getIdent(g.cache, "thread"), owner, n.info, g.config.options) + argsParam = newSym(skParam, getIdent(g.cache, "args"), owner, n.info, g.config.options) block: - let ptrType = getSysType(tyPointer) + let ptrType = getSysType(g, n.info, tyPointer) threadParam.typ = ptrType argsParam.typ = ptrType argsParam.position = 1 - var objType = createObj(owner, n.info) + var objType = createObj(g, owner, n.info) incl(objType.flags, tfFinal) let castExpr = createCastExpr(argsParam, objType) - var scratchObj = newSym(skVar, getIdent"scratch", owner, n.info) + var scratchObj = newSym(skVar, getIdent(g.cache, "scratch"), owner, n.info, g.config.options) block: scratchObj.typ = objType incl(scratchObj.flags, sfFromGeneric) @@ -644,65 +665,65 @@ proc wrapProcForSpawn*(owner: PSym; spawnExpr: PNode; retType: PType; # templates and macros are in fact valid here due to the nature of # the transformation: if fn.kind == nkClosure: - localError(n.info, "closure in spawn environment is not allowed") + localError(g.config, n.info, "closure in spawn environment is not allowed") if not (fn.kind == nkSym and fn.sym.kind in {skProc, skTemplate, skMacro, skFunc, skMethod, skConverter}): # for indirect calls we pass the function pointer in the scratchObj var argType = n[0].typ.skipTypes(abstractInst) - var field = newSym(skField, getIdent"fn", owner, n.info) + var field = newSym(skField, getIdent(g.cache, "fn"), owner, n.info, g.config.options) field.typ = argType - objType.addField(field) + objType.addField(field, g.cache) result.add newFastAsgnStmt(newDotExpr(scratchObj, field), n[0]) fn = indirectAccess(castExpr, field, n.info) elif fn.kind == nkSym and fn.sym.kind == skIterator: - localError(n.info, "iterator in spawn environment is not allowed") + localError(g.config, n.info, "iterator in spawn environment is not allowed") elif fn.typ.callConv == ccClosure: - localError(n.info, "closure in spawn environment is not allowed") + localError(g.config, n.info, "closure in spawn environment is not allowed") call.add(fn) var varSection = newNodeI(nkVarSection, n.info) var varInit = newNodeI(nkStmtList, n.info) if barrier.isNil: - setupArgsForConcurrency(n, objType, scratchObj, castExpr, call, + setupArgsForConcurrency(g, n, objType, scratchObj, castExpr, call, varSection, varInit, result) else: - setupArgsForParallelism(n, objType, scratchObj, castExpr, call, + setupArgsForParallelism(g, n, objType, scratchObj, castExpr, call, varSection, varInit, result) var barrierAsExpr: PNode = nil if barrier != nil: let typ = newType(tyPtr, owner) - typ.rawAddSon(magicsys.getCompilerProc("Barrier").typ) - var field = newSym(skField, getIdent"barrier", owner, n.info) + typ.rawAddSon(magicsys.getCompilerProc(g, "Barrier").typ) + var field = newSym(skField, getIdent(g.cache, "barrier"), owner, n.info, g.config.options) field.typ = typ - objType.addField(field) + objType.addField(field, g.cache) result.add newFastAsgnStmt(newDotExpr(scratchObj, field), barrier) barrierAsExpr = indirectAccess(castExpr, field, n.info) var fvField, fvAsExpr: PNode = nil if spawnKind == srFlowVar: - var field = newSym(skField, getIdent"fv", owner, n.info) + var field = newSym(skField, getIdent(g.cache, "fv"), owner, n.info, g.config.options) field.typ = retType - objType.addField(field) + objType.addField(field, g.cache) fvField = newDotExpr(scratchObj, field) fvAsExpr = indirectAccess(castExpr, field, n.info) # create flowVar: result.add newFastAsgnStmt(fvField, callProc(spawnExpr[^1])) if barrier == nil: - result.add callCodegenProc("nimFlowVarCreateSemaphore", fvField) + result.add callCodegenProc(g, "nimFlowVarCreateSemaphore", fvField) elif spawnKind == srByVar: - var field = newSym(skField, getIdent"fv", owner, n.info) + var field = newSym(skField, getIdent(g.cache, "fv"), owner, n.info, g.config.options) field.typ = newType(tyPtr, objType.owner) field.typ.rawAddSon(retType) - objType.addField(field) + objType.addField(field, g.cache) fvAsExpr = indirectAccess(castExpr, field, n.info) result.add newFastAsgnStmt(newDotExpr(scratchObj, field), genAddrOf(dest)) - let wrapper = createWrapperProc(fn, threadParam, argsParam, + let wrapper = createWrapperProc(g, fn, threadParam, argsParam, varSection, varInit, call, barrierAsExpr, fvAsExpr, spawnKind) - result.add callCodegenProc("nimSpawn" & $spawnExpr.len, wrapper.newSymNode, + result.add callCodegenProc(g, "nimSpawn" & $spawnExpr.len, wrapper.newSymNode, genAddrOf(scratchObj.newSymNode), nil, spawnExpr) if spawnKind == srFlowVar: result.add fvField diff --git a/compiler/macrocacheimpl.nim b/compiler/macrocacheimpl.nim new file mode 100644 index 000000000..d23040763 --- /dev/null +++ b/compiler/macrocacheimpl.nim @@ -0,0 +1,79 @@ +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements helpers for the macro cache. + +import lineinfos, ast, modulegraphs, vmdef, magicsys + +proc recordInc*(c: PCtx; info: TLineInfo; key: string; by: BiggestInt) = + var recorded = newNodeI(nkCommentStmt, info) + recorded.add newStrNode("inc", info) + recorded.add newStrNode(key, info) + recorded.add newIntNode(nkIntLit, by) + c.graph.recordStmt(c.graph, c.module, recorded) + +proc recordPut*(c: PCtx; info: TLineInfo; key: string; k: string; val: PNode) = + var recorded = newNodeI(nkCommentStmt, info) + recorded.add newStrNode("put", info) + recorded.add newStrNode(key, info) + recorded.add newStrNode(k, info) + recorded.add copyTree(val) + c.graph.recordStmt(c.graph, c.module, recorded) + +proc recordAdd*(c: PCtx; info: TLineInfo; key: string; val: PNode) = + var recorded = newNodeI(nkCommentStmt, info) + recorded.add newStrNode("add", info) + recorded.add newStrNode(key, info) + recorded.add copyTree(val) + c.graph.recordStmt(c.graph, c.module, recorded) + +proc recordIncl*(c: PCtx; info: TLineInfo; key: string; val: PNode) = + var recorded = newNodeI(nkCommentStmt, info) + recorded.add newStrNode("incl", info) + recorded.add newStrNode(key, info) + recorded.add copyTree(val) + c.graph.recordStmt(c.graph, c.module, recorded) + +when false: + proc genCall3(g: ModuleGraph; m: TMagic; s: string; a, b, c: PNode): PNode = + newTree(nkStaticStmt, newTree(nkCall, createMagic(g, s, m).newSymNode, a, b, c)) + + proc genCall2(g: ModuleGraph; m: TMagic; s: string; a, b: PNode): PNode = + newTree(nkStaticStmt, newTree(nkCall, createMagic(g, s, m).newSymNode, a, b)) + + template nodeFrom(s: string): PNode = + var res = newStrNode(s, info) + res.typ = getSysType(g, info, tyString) + res + + template nodeFrom(i: BiggestInt): PNode = + var res = newIntNode(i, info) + res.typ = getSysType(g, info, tyInt) + res + + template nodeFrom(n: PNode): PNode = copyTree(n) + + template record(call) = + g.recordStmt(g, c.module, call) + + proc recordInc*(c: PCtx; info: TLineInfo; key: string; by: BiggestInt) = + let g = c.graph + record genCall2(mNccInc, "inc", nodeFrom key, nodeFrom by) + + proc recordPut*(c: PCtx; info: TLineInfo; key: string; k: string; val: PNode) = + let g = c.graph + record genCall3(mNctPut, "[]=", nodeFrom key, nodeFrom k, nodeFrom val) + + proc recordAdd*(c: PCtx; info: TLineInfo; key: string; val: PNode) = + let g = c.graph + record genCall2(mNcsAdd, "add", nodeFrom key, nodeFrom val) + + proc recordIncl*(c: PCtx; info: TLineInfo; key: string; val: PNode) = + let g = c.graph + record genCall2(mNcsIncl, "incl", nodeFrom key, nodeFrom val) diff --git a/compiler/magicsys.nim b/compiler/magicsys.nim index 6a9d69082..aeeb489c0 100644 --- a/compiler/magicsys.nim +++ b/compiler/magicsys.nim @@ -10,63 +10,50 @@ # Built-in types and compilerprocs are registered here. import - ast, astalgo, hashes, msgs, platform, nversion, times, idents, rodread + ast, astalgo, hashes, msgs, platform, nversion, times, idents, + modulegraphs, lineinfos -var systemModule*: PSym +export createMagic -var - gSysTypes: array[TTypeKind, PType] - compilerprocs: TStrTable - exposed: TStrTable +proc nilOrSysInt*(g: ModuleGraph): PType = g.sysTypes[tyInt] -proc nilOrSysInt*: PType = gSysTypes[tyInt] +proc registerSysType*(g: ModuleGraph; t: PType) = + if g.sysTypes[t.kind] == nil: g.sysTypes[t.kind] = t -proc registerSysType*(t: PType) = - if gSysTypes[t.kind] == nil: gSysTypes[t.kind] = t - -proc newSysType(kind: TTypeKind, size: int): PType = - result = newType(kind, systemModule) +proc newSysType(g: ModuleGraph; kind: TTypeKind, size: int): PType = + result = newType(kind, g.systemModule) result.size = size result.align = size.int16 -proc getSysSym*(name: string): PSym = - result = strTableGet(systemModule.tab, getIdent(name)) +proc getSysSym*(g: ModuleGraph; info: TLineInfo; name: string): PSym = + result = strTableGet(g.systemModule.tab, getIdent(g.cache, name)) if result == nil: - rawMessage(errSystemNeeds, name) - result = newSym(skError, getIdent(name), systemModule, systemModule.info) - result.typ = newType(tyError, systemModule) - if result.kind == skStub: loadStub(result) + localError(g.config, info, "system module needs: " & name) + result = newSym(skError, getIdent(g.cache, name), g.systemModule, g.systemModule.info, {}) + result.typ = newType(tyError, g.systemModule) if result.kind == skAlias: result = result.owner -proc createMagic*(name: string, m: TMagic): PSym = - result = newSym(skProc, getIdent(name), nil, unknownLineInfo()) - result.magic = m - -let - opNot* = createMagic("not", mNot) - opContains* = createMagic("contains", mInSet) - -proc getSysMagic*(name: string, m: TMagic): PSym = +proc getSysMagic*(g: ModuleGraph; info: TLineInfo; name: string, m: TMagic): PSym = var ti: TIdentIter - let id = getIdent(name) - var r = initIdentIter(ti, systemModule.tab, id) + let id = getIdent(g.cache, name) + var r = initIdentIter(ti, g.systemModule.tab, id) while r != nil: - if r.kind == skStub: loadStub(r) if r.magic == m: # prefer the tyInt variant: if r.typ.sons[0] != nil and r.typ.sons[0].kind == tyInt: return r result = r - r = nextIdentIter(ti, systemModule.tab) + r = nextIdentIter(ti, g.systemModule.tab) if result != nil: return result - rawMessage(errSystemNeeds, name) - result = newSym(skError, id, systemModule, systemModule.info) - result.typ = newType(tyError, systemModule) + localError(g.config, info, "system module needs: " & name) + result = newSym(skError, id, g.systemModule, g.systemModule.info, {}) + result.typ = newType(tyError, g.systemModule) -proc sysTypeFromName*(name: string): PType = - result = getSysSym(name).typ +proc sysTypeFromName*(g: ModuleGraph; info: TLineInfo; name: string): PType = + result = getSysSym(g, info, name).typ -proc getSysType*(kind: TTypeKind): PType = - result = gSysTypes[kind] +proc getSysType*(g: ModuleGraph; info: TLineInfo; kind: TTypeKind): PType = + template sysTypeFromName(s: string): untyped = sysTypeFromName(g, info, s) + result = g.sysTypes[kind] if result == nil: case kind of tyInt: result = sysTypeFromName("int") @@ -88,112 +75,103 @@ proc getSysType*(kind: TTypeKind): PType = of tyString: result = sysTypeFromName("string") of tyCString: result = sysTypeFromName("cstring") of tyPointer: result = sysTypeFromName("pointer") - of tyNil: result = newSysType(tyNil, ptrSize) - else: internalError("request for typekind: " & $kind) - gSysTypes[kind] = result + of tyNil: result = newSysType(g, tyNil, g.config.target.ptrSize) + else: internalError(g.config, "request for typekind: " & $kind) + g.sysTypes[kind] = result if result.kind != kind: - internalError("wanted: " & $kind & " got: " & $result.kind) - if result == nil: internalError("type not found: " & $kind) - -var - intTypeCache: array[-5..64, PType] + internalError(g.config, "wanted: " & $kind & " got: " & $result.kind) + if result == nil: internalError(g.config, "type not found: " & $kind) -proc resetSysTypes* = - systemModule = nil - initStrTable(compilerprocs) - initStrTable(exposed) - for i in low(gSysTypes)..high(gSysTypes): - gSysTypes[i] = nil +proc resetSysTypes*(g: ModuleGraph) = + g.systemModule = nil + initStrTable(g.compilerprocs) + initStrTable(g.exposed) + for i in low(g.sysTypes)..high(g.sysTypes): + g.sysTypes[i] = nil - for i in low(intTypeCache)..high(intTypeCache): - intTypeCache[i] = nil + for i in low(g.intTypeCache)..high(g.intTypeCache): + g.intTypeCache[i] = nil -proc getIntLitType*(literal: PNode): PType = +proc getIntLitType*(g: ModuleGraph; literal: PNode): PType = # we cache some common integer literal types for performance: let value = literal.intVal - if value >= low(intTypeCache) and value <= high(intTypeCache): - result = intTypeCache[value.int] + if value >= low(g.intTypeCache) and value <= high(g.intTypeCache): + result = g.intTypeCache[value.int] if result == nil: - let ti = getSysType(tyInt) + let ti = getSysType(g, literal.info, tyInt) result = copyType(ti, ti.owner, false) result.n = literal - intTypeCache[value.int] = result + g.intTypeCache[value.int] = result else: - let ti = getSysType(tyInt) + let ti = getSysType(g, literal.info, tyInt) result = copyType(ti, ti.owner, false) result.n = literal -proc getFloatLitType*(literal: PNode): PType = +proc getFloatLitType*(g: ModuleGraph; literal: PNode): PType = # for now we do not cache these: - result = newSysType(tyFloat, size=8) + result = newSysType(g, tyFloat, size=8) result.n = literal proc skipIntLit*(t: PType): PType {.inline.} = - if t.n != nil: - if t.kind in {tyInt, tyFloat}: - return getSysType(t.kind) - result = t + if t.n != nil and t.kind in {tyInt, tyFloat}: + result = copyType(t, t.owner, false) + result.n = nil + else: + result = t proc addSonSkipIntLit*(father, son: PType) = - if isNil(father.sons): father.sons = @[] + when not defined(nimNoNilSeqs): + if isNil(father.sons): father.sons = @[] let s = son.skipIntLit add(father.sons, s) propagateToOwner(father, s) -proc setIntLitType*(result: PNode) = +proc setIntLitType*(g: ModuleGraph; result: PNode) = let i = result.intVal - case platform.intSize - of 8: result.typ = getIntLitType(result) + case g.config.target.intSize + of 8: result.typ = getIntLitType(g, result) of 4: if i >= low(int32) and i <= high(int32): - result.typ = getIntLitType(result) + result.typ = getIntLitType(g, result) else: - result.typ = getSysType(tyInt64) + result.typ = getSysType(g, result.info, tyInt64) of 2: if i >= low(int16) and i <= high(int16): - result.typ = getIntLitType(result) + result.typ = getIntLitType(g, result) elif i >= low(int32) and i <= high(int32): - result.typ = getSysType(tyInt32) + result.typ = getSysType(g, result.info, tyInt32) else: - result.typ = getSysType(tyInt64) + result.typ = getSysType(g, result.info, tyInt64) of 1: # 8 bit CPUs are insane ... if i >= low(int8) and i <= high(int8): - result.typ = getIntLitType(result) + result.typ = getIntLitType(g, result) elif i >= low(int16) and i <= high(int16): - result.typ = getSysType(tyInt16) + result.typ = getSysType(g, result.info, tyInt16) elif i >= low(int32) and i <= high(int32): - result.typ = getSysType(tyInt32) + result.typ = getSysType(g, result.info, tyInt32) else: - result.typ = getSysType(tyInt64) - else: internalError(result.info, "invalid int size") + result.typ = getSysType(g, result.info, tyInt64) + else: + internalError(g.config, result.info, "invalid int size") -proc getCompilerProc*(name: string): PSym = - let ident = getIdent(name) - result = strTableGet(compilerprocs, ident) - if result == nil: - result = strTableGet(rodCompilerprocs, ident) - if result != nil: - strTableAdd(compilerprocs, result) - if result.kind == skStub: loadStub(result) - if result.kind == skAlias: result = result.owner +proc getCompilerProc*(g: ModuleGraph; name: string): PSym = + let ident = getIdent(g.cache, name) + result = strTableGet(g.compilerprocs, ident) -proc registerCompilerProc*(s: PSym) = - strTableAdd(compilerprocs, s) +proc registerCompilerProc*(g: ModuleGraph; s: PSym) = + strTableAdd(g.compilerprocs, s) -proc registerNimScriptSymbol*(s: PSym) = +proc registerNimScriptSymbol*(g: ModuleGraph; s: PSym) = # Nimscript symbols must be al unique: - let conflict = strTableGet(exposed, s.name) + let conflict = strTableGet(g.exposed, s.name) if conflict == nil: - strTableAdd(exposed, s) + strTableAdd(g.exposed, s) else: - localError(s.info, "symbol conflicts with other .exportNims symbol at: " & - $conflict.info) - -proc getNimScriptSymbol*(name: string): PSym = - strTableGet(exposed, getIdent(name)) + localError(g.config, s.info, + "symbol conflicts with other .exportNims symbol at: " & g.config$conflict.info) -proc resetNimScriptSymbols*() = initStrTable(exposed) +proc getNimScriptSymbol*(g: ModuleGraph; name: string): PSym = + strTableGet(g.exposed, getIdent(g.cache, name)) -initStrTable(compilerprocs) -initStrTable(exposed) +proc resetNimScriptSymbols*(g: ModuleGraph) = initStrTable(g.exposed) diff --git a/compiler/main.nim b/compiler/main.nim index 4a8fdf998..cd05ded62 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -9,296 +9,281 @@ # implements the command dispatcher and several commands +when not defined(nimcore): + {.error: "nimcore MUST be defined for Nim's core tooling".} + import llstream, strutils, ast, astalgo, lexer, syntaxes, renderer, options, msgs, - os, condsyms, rodread, rodwrite, times, + os, condsyms, times, wordrecg, sem, semdata, idents, passes, docgen, extccomp, cgen, jsgen, json, nversion, platform, nimconf, importer, passaux, depends, vm, vmdef, types, idgen, - docgen2, service, parser, modules, ccgutils, sigmatch, ropes, - modulegraphs, tables, rod - -from magicsys import systemModule, resetSysTypes + docgen2, parser, modules, ccgutils, sigmatch, ropes, + modulegraphs, tables, rod, lineinfos -proc rodPass = - if gSymbolFiles in {enabledSf, writeOnlySf}: - registerPass(rodwritePass) +from magicsys import resetSysTypes -proc codegenPass = - registerPass cgenPass +proc codegenPass(g: ModuleGraph) = + registerPass g, cgenPass -proc semanticPasses = - registerPass verbosePass - registerPass semPass +proc semanticPasses(g: ModuleGraph) = + registerPass g, verbosePass + registerPass g, semPass proc writeDepsFile(g: ModuleGraph; project: string) = let f = open(changeFileExt(project, "deps"), fmWrite) for m in g.modules: if m != nil: - f.writeLine(toFullPath(m.position.int32)) + f.writeLine(toFullPath(g.config, m.position.FileIndex)) for k in g.inclToMod.keys: if g.getModule(k).isNil: # don't repeat includes which are also modules - f.writeLine(k.toFullPath) + f.writeLine(toFullPath(g.config, k)) f.close() -proc commandGenDepend(graph: ModuleGraph; cache: IdentCache) = - semanticPasses() - registerPass(gendependPass) - #registerPass(cleanupPass) - compileProject(graph, cache) - writeDepsFile(graph, gProjectFull) - generateDot(gProjectFull) - execExternalProgram("dot -Tpng -o" & changeFileExt(gProjectFull, "png") & - ' ' & changeFileExt(gProjectFull, "dot")) - -proc commandCheck(graph: ModuleGraph; cache: IdentCache) = - msgs.gErrorMax = high(int) # do not stop after first error - defineSymbol("nimcheck") - semanticPasses() # use an empty backend for semantic checking only - rodPass() - compileProject(graph, cache) - -proc commandDoc2(graph: ModuleGraph; cache: IdentCache; json: bool) = - msgs.gErrorMax = high(int) # do not stop after first error - semanticPasses() - if json: registerPass(docgen2JsonPass) - else: registerPass(docgen2Pass) - #registerPass(cleanupPass()) - compileProject(graph, cache) - finishDoc2Pass(gProjectName) - -proc commandCompileToC(graph: ModuleGraph; cache: IdentCache) = - extccomp.initVars() - semanticPasses() - registerPass(cgenPass) - rodPass() - #registerPass(cleanupPass()) - - compileProject(graph, cache) - cgenWriteModules(graph.backend, graph.config) - if gCmd != cmdRun: - let proj = changeFileExt(gProjectFull, "") - extccomp.callCCompiler(proj) - extccomp.writeJsonBuildInstructions(proj) - if optGenScript in gGlobalOptions: - writeDepsFile(graph, toGeneratedFile(proj, "")) - -proc commandJsonScript(graph: ModuleGraph; cache: IdentCache) = - let proj = changeFileExt(gProjectFull, "") - extccomp.runJsonBuildInstructions(proj) - -proc commandCompileToJS(graph: ModuleGraph; cache: IdentCache) = +proc commandGenDepend(graph: ModuleGraph) = + semanticPasses(graph) + registerPass(graph, gendependPass) + compileProject(graph) + let project = graph.config.projectFull + writeDepsFile(graph, project) + generateDot(graph, project) + execExternalProgram(graph.config, "dot -Tpng -o" & changeFileExt(project, "png") & + ' ' & changeFileExt(project, "dot")) + +proc commandCheck(graph: ModuleGraph) = + graph.config.errorMax = high(int) # do not stop after first error + defineSymbol(graph.config.symbols, "nimcheck") + semanticPasses(graph) # use an empty backend for semantic checking only + compileProject(graph) + +proc commandDoc2(graph: ModuleGraph; json: bool) = + graph.config.errorMax = high(int) # do not stop after first error + semanticPasses(graph) + if json: registerPass(graph, docgen2JsonPass) + else: registerPass(graph, docgen2Pass) + compileProject(graph) + finishDoc2Pass(graph.config.projectName) + +proc commandCompileToC(graph: ModuleGraph) = + let conf = graph.config + extccomp.initVars(conf) + semanticPasses(graph) + registerPass(graph, cgenPass) + + compileProject(graph) + cgenWriteModules(graph.backend, conf) + if conf.cmd != cmdRun: + let proj = changeFileExt(conf.projectFull, "") + extccomp.callCCompiler(conf, proj) + extccomp.writeJsonBuildInstructions(conf, proj) + if optGenScript in graph.config.globalOptions: + writeDepsFile(graph, toGeneratedFile(conf, proj, "")) + +proc commandJsonScript(graph: ModuleGraph) = + let proj = changeFileExt(graph.config.projectFull, "") + extccomp.runJsonBuildInstructions(graph.config, proj) + +proc commandCompileToJS(graph: ModuleGraph) = #incl(gGlobalOptions, optSafeCode) - setTarget(osJS, cpuJS) + setTarget(graph.config.target, osJS, cpuJS) #initDefines() - defineSymbol("nimrod") # 'nimrod' is always defined - defineSymbol("ecmascript") # For backward compatibility - defineSymbol("js") - if gCmd == cmdCompileToPHP: defineSymbol("nimphp") - semanticPasses() - registerPass(JSgenPass) - compileProject(graph, cache) - -proc interactivePasses(graph: ModuleGraph; cache: IdentCache) = - #incl(gGlobalOptions, optSafeCode) - #setTarget(osNimrodVM, cpuNimrodVM) - initDefines() - defineSymbol("nimscript") - when hasFFI: defineSymbol("nimffi") - registerPass(verbosePass) - registerPass(semPass) - registerPass(evalPass) - -proc commandInteractive(graph: ModuleGraph; cache: IdentCache) = - msgs.gErrorMax = high(int) # do not stop after first error - interactivePasses(graph, cache) - compileSystemModule(graph, cache) - if commandArgs.len > 0: - discard graph.compileModule(fileInfoIdx(gProjectFull), cache, {}) + defineSymbol(graph.config.symbols, "ecmascript") # For backward compatibility + defineSymbol(graph.config.symbols, "js") + semanticPasses(graph) + registerPass(graph, JSgenPass) + compileProject(graph) + +proc interactivePasses(graph: ModuleGraph) = + initDefines(graph.config.symbols) + defineSymbol(graph.config.symbols, "nimscript") + when hasFFI: defineSymbol(graph.config.symbols, "nimffi") + registerPass(graph, verbosePass) + registerPass(graph, semPass) + registerPass(graph, evalPass) + +proc commandInteractive(graph: ModuleGraph) = + graph.config.errorMax = high(int) # do not stop after first error + interactivePasses(graph) + compileSystemModule(graph) + if graph.config.commandArgs.len > 0: + discard graph.compileModule(fileInfoIdx(graph.config, graph.config.projectFull), {}) else: var m = graph.makeStdinModule() incl(m.flags, sfMainModule) - processModule(graph, m, llStreamOpenStdIn(), nil, cache) + processModule(graph, m, llStreamOpenStdIn()) const evalPasses = [verbosePass, semPass, evalPass] -proc evalNim(graph: ModuleGraph; nodes: PNode, module: PSym; cache: IdentCache) = - carryPasses(graph, nodes, module, cache, evalPasses) +proc evalNim(graph: ModuleGraph; nodes: PNode, module: PSym) = + carryPasses(graph, nodes, module, evalPasses) -proc commandEval(graph: ModuleGraph; cache: IdentCache; exp: string) = - if systemModule == nil: - interactivePasses(graph, cache) - compileSystemModule(graph, cache) +proc commandEval(graph: ModuleGraph; exp: string) = + if graph.systemModule == nil: + interactivePasses(graph) + compileSystemModule(graph) let echoExp = "echo \"eval\\t\", " & "repr(" & exp & ")" - evalNim(graph, echoExp.parseString(cache), makeStdinModule(graph), cache) + evalNim(graph, echoExp.parseString(graph.cache, graph.config), + makeStdinModule(graph)) -proc commandScan(cache: IdentCache) = - var f = addFileExt(mainCommandArg(), NimExt) +proc commandScan(cache: IdentCache, config: ConfigRef) = + var f = addFileExt(mainCommandArg(config), NimExt) var stream = llStreamOpen(f, fmRead) if stream != nil: var L: TLexer tok: TToken initToken(tok) - openLexer(L, f, stream, cache) + openLexer(L, f, stream, cache, config) while true: rawGetTok(L, tok) - printTok(tok) + printTok(config, tok) if tok.tokType == tkEof: break closeLexer(L) else: - rawMessage(errCannotOpenFile, f) + rawMessage(config, errGenerated, "cannot open file: " & f) const - SimulateCaasMemReset = false PrintRopeCacheStats = false -proc mainCommand*(graph: ModuleGraph; cache: IdentCache) = - when SimulateCaasMemReset: - gGlobalOptions.incl(optCaasEnabled) +proc mainCommand*(graph: ModuleGraph) = + let conf = graph.config + let cache = graph.cache - setupModuleCache() + setupModuleCache(graph) # In "nim serve" scenario, each command must reset the registered passes - clearPasses() - gLastCmdTime = epochTime() - searchPaths.add(options.libpath) - when false: # gProjectFull.len != 0: - # current path is always looked first for modules - prependStr(searchPaths, gProjectPath) + clearPasses(graph) + conf.lastCmdTime = epochTime() + conf.searchPaths.add(conf.libpath) setId(100) - case command.normalize + case conf.command.normalize of "c", "cc", "compile", "compiletoc": # compile means compileToC currently - gCmd = cmdCompileToC - commandCompileToC(graph, cache) + conf.cmd = cmdCompileToC + commandCompileToC(graph) of "cpp", "compiletocpp": - gCmd = cmdCompileToCpp - defineSymbol("cpp") - commandCompileToC(graph, cache) + conf.cmd = cmdCompileToCpp + defineSymbol(graph.config.symbols, "cpp") + commandCompileToC(graph) of "objc", "compiletooc": - gCmd = cmdCompileToOC - defineSymbol("objc") - commandCompileToC(graph, cache) + conf.cmd = cmdCompileToOC + defineSymbol(graph.config.symbols, "objc") + commandCompileToC(graph) of "run": - gCmd = cmdRun + conf.cmd = cmdRun when hasTinyCBackend: extccomp.setCC("tcc") - commandCompileToC(graph, cache) + commandCompileToC(graph) else: - rawMessage(errInvalidCommandX, command) + rawMessage(conf, errGenerated, "'run' command not available; rebuild with -d:tinyc") of "js", "compiletojs": - gCmd = cmdCompileToJS - commandCompileToJS(graph, cache) - of "php": - gCmd = cmdCompileToPHP - commandCompileToJS(graph, cache) + conf.cmd = cmdCompileToJS + commandCompileToJS(graph) of "doc0": - wantMainModule() - gCmd = cmdDoc - loadConfigs(DocConfig, cache) - commandDoc() + wantMainModule(conf) + conf.cmd = cmdDoc + loadConfigs(DocConfig, cache, conf) + commandDoc(cache, conf) of "doc2", "doc": - gCmd = cmdDoc - loadConfigs(DocConfig, cache) - defineSymbol("nimdoc") - commandDoc2(graph, cache, false) + conf.cmd = cmdDoc + loadConfigs(DocConfig, cache, conf) + defineSymbol(conf.symbols, "nimdoc") + commandDoc2(graph, false) of "rst2html": - gCmd = cmdRst2html - loadConfigs(DocConfig, cache) - commandRst2Html() + conf.cmd = cmdRst2html + loadConfigs(DocConfig, cache, conf) + commandRst2Html(cache, conf) of "rst2tex": - gCmd = cmdRst2tex - loadConfigs(DocTexConfig, cache) - commandRst2TeX() - of "jsondoc": - wantMainModule() - gCmd = cmdDoc - loadConfigs(DocConfig, cache) - wantMainModule() - defineSymbol("nimdoc") - commandJson() - of "jsondoc2": - gCmd = cmdDoc - loadConfigs(DocConfig, cache) - wantMainModule() - defineSymbol("nimdoc") - commandDoc2(graph, cache, true) + conf.cmd = cmdRst2tex + loadConfigs(DocTexConfig, cache, conf) + commandRst2TeX(cache, conf) + of "jsondoc0": + wantMainModule(conf) + conf.cmd = cmdDoc + loadConfigs(DocConfig, cache, conf) + wantMainModule(conf) + defineSymbol(conf.symbols, "nimdoc") + commandJson(cache, conf) + of "jsondoc2", "jsondoc": + conf.cmd = cmdDoc + loadConfigs(DocConfig, cache, conf) + wantMainModule(conf) + defineSymbol(conf.symbols, "nimdoc") + commandDoc2(graph, true) of "ctags": - wantMainModule() - gCmd = cmdDoc - loadConfigs(DocConfig, cache) - wantMainModule() - defineSymbol("nimdoc") - commandTags() + wantMainModule(conf) + conf.cmd = cmdDoc + loadConfigs(DocConfig, cache, conf) + defineSymbol(conf.symbols, "nimdoc") + commandTags(cache, conf) of "buildindex": - gCmd = cmdDoc - loadConfigs(DocConfig, cache) - commandBuildIndex() + conf.cmd = cmdDoc + loadConfigs(DocConfig, cache, conf) + commandBuildIndex(cache, conf) of "gendepend": - gCmd = cmdGenDepend - commandGenDepend(graph, cache) + conf.cmd = cmdGenDepend + commandGenDepend(graph) of "dump": - gCmd = cmdDump - if getConfigVar("dump.format") == "json": - wantMainModule() + conf.cmd = cmdDump + if getConfigVar(conf, "dump.format") == "json": + wantMainModule(conf) var definedSymbols = newJArray() - for s in definedSymbolNames(): definedSymbols.elems.add(%s) + for s in definedSymbolNames(conf.symbols): definedSymbols.elems.add(%s) var libpaths = newJArray() - for dir in searchPaths: libpaths.elems.add(%dir) + for dir in conf.searchPaths: libpaths.elems.add(%dir) var dumpdata = % [ (key: "version", val: %VersionAsString), - (key: "project_path", val: %gProjectFull), + (key: "project_path", val: %conf.projectFull), (key: "defined_symbols", val: definedSymbols), (key: "lib_paths", val: libpaths) ] - msgWriteln($dumpdata, {msgStdout, msgSkipHook}) + msgWriteln(conf, $dumpdata, {msgStdout, msgSkipHook}) else: - msgWriteln("-- list of currently defined symbols --", + msgWriteln(conf, "-- list of currently defined symbols --", {msgStdout, msgSkipHook}) - for s in definedSymbolNames(): msgWriteln(s, {msgStdout, msgSkipHook}) - msgWriteln("-- end of list --", {msgStdout, msgSkipHook}) + for s in definedSymbolNames(conf.symbols): msgWriteln(conf, s, {msgStdout, msgSkipHook}) + msgWriteln(conf, "-- end of list --", {msgStdout, msgSkipHook}) - for it in searchPaths: msgWriteln(it) + for it in conf.searchPaths: msgWriteln(conf, it) of "check": - gCmd = cmdCheck - commandCheck(graph, cache) + conf.cmd = cmdCheck + commandCheck(graph) of "parse": - gCmd = cmdParse - wantMainModule() - discard parseFile(gProjectMainIdx, cache) + conf.cmd = cmdParse + wantMainModule(conf) + discard parseFile(conf.projectMainIdx, cache, conf) of "scan": - gCmd = cmdScan - wantMainModule() - commandScan(cache) - msgWriteln("Beware: Indentation tokens depend on the parser's state!") + conf.cmd = cmdScan + wantMainModule(conf) + commandScan(cache, conf) + msgWriteln(conf, "Beware: Indentation tokens depend on the parser's state!") of "secret": - gCmd = cmdInteractive - commandInteractive(graph, cache) + conf.cmd = cmdInteractive + commandInteractive(graph) of "e": - commandEval(graph, cache, mainCommandArg()) + commandEval(graph, mainCommandArg(conf)) of "nop", "help": # prevent the "success" message: - gCmd = cmdDump + conf.cmd = cmdDump of "jsonscript": - gCmd = cmdJsonScript - commandJsonScript(graph, cache) + conf.cmd = cmdJsonScript + commandJsonScript(graph) else: - rawMessage(errInvalidCommandX, command) + rawMessage(conf, errGenerated, "invalid command: " & conf.command) - if msgs.gErrorCounter == 0 and - gCmd notin {cmdInterpret, cmdRun, cmdDump}: + if conf.errorCounter == 0 and + conf.cmd notin {cmdInterpret, cmdRun, cmdDump}: when declared(system.getMaxMem): let usedMem = formatSize(getMaxMem()) & " peakmem" else: let usedMem = formatSize(getTotalMem()) - rawMessage(hintSuccessX, [$gLinesCompiled, - formatFloat(epochTime() - gLastCmdTime, ffDecimal, 3), + rawMessage(conf, hintSuccessX, [$conf.linesCompiled, + formatFloat(epochTime() - conf.lastCmdTime, ffDecimal, 3), usedMem, - if condSyms.isDefined("release"): "Release Build" + if isDefined(conf, "release"): "Release Build" else: "Debug Build"]) when PrintRopeCacheStats: @@ -309,9 +294,4 @@ proc mainCommand*(graph: ModuleGraph; cache: IdentCache) = echo " efficiency: ", formatFloat(1-(gCacheMisses.float/gCacheTries.float), ffDecimal, 3) - when SimulateCaasMemReset: - resetMemory() - - resetAttributes() - -proc mainCommand*() = mainCommand(newModuleGraph(newConfigRef()), newIdentCache()) + resetAttributes(conf) diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index 2c59a9097..1eecc4176 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -9,7 +9,7 @@ ## This module implements the module graph data structure. The module graph ## represents a complete Nim project. Single modules can either be kept in RAM -## or stored in a ROD file. The ROD file mechanism is not yet integrated here. +## or stored in a Sqlite database. ## ## The caching of modules is critical for 'nimsuggest' and is tricky to get ## right. If module E is being edited, we need autocompletion (and type @@ -25,7 +25,8 @@ ## - Its dependent module stays the same. ## -import ast, intsets, tables, options, rod +import ast, intsets, tables, options, lineinfos, hashes, idents, + incremental, btrees type ModuleGraph* = ref object @@ -34,73 +35,105 @@ type deps*: IntSet # the dependency graph or potentially its transitive closure. suggestMode*: bool # whether we are in nimsuggest mode or not. invalidTransitiveClosure: bool - inclToMod*: Table[int32, int32] # mapping of include file to the - # first module that included it - importStack*: seq[int32] # The current import stack. Used for detecting recursive - # module dependencies. + inclToMod*: Table[FileIndex, FileIndex] # mapping of include file to the + # first module that included it + importStack*: seq[FileIndex] # The current import stack. Used for detecting recursive + # module dependencies. backend*: RootRef # minor hack so that a backend can extend this easily config*: ConfigRef + cache*: IdentCache + vm*: RootRef # unfortunately the 'vm' state is shared project-wise, this will + # be clarified in later compiler implementations. doStopCompile*: proc(): bool {.closure.} usageSym*: PSym # for nimsuggest owners*: seq[PSym] - methods*: seq[tuple[methods: TSymSeq, dispatcher: PSym]] - -{.this: g.} + methods*: seq[tuple[methods: TSymSeq, dispatcher: PSym]] # needs serialization! + systemModule*: PSym + sysTypes*: array[TTypeKind, PType] + compilerprocs*: TStrTable + exposed*: TStrTable + intTypeCache*: array[-5..64, PType] + opContains*, opNot*: PSym + emptyNode*: PNode + incr*: IncrementalCtx + importModuleCallback*: proc (graph: ModuleGraph; m: PSym, fileIdx: FileIndex): PSym {.nimcall.} + includeFileCallback*: proc (graph: ModuleGraph; m: PSym, fileIdx: FileIndex): PNode {.nimcall.} + recordStmt*: proc (graph: ModuleGraph; m: PSym; n: PNode) {.nimcall.} + cacheSeqs*: Table[string, PNode] # state that is shared to suppor the 'macrocache' API + cacheCounters*: Table[string, BiggestInt] + cacheTables*: Table[string, BTree[string, PNode]] + +proc hash*(x: FileIndex): Hash {.borrow.} proc stopCompile*(g: ModuleGraph): bool {.inline.} = - result = doStopCompile != nil and doStopCompile() + result = g.doStopCompile != nil and g.doStopCompile() + +proc createMagic*(g: ModuleGraph; name: string, m: TMagic): PSym = + result = newSym(skProc, getIdent(g.cache, name), nil, unknownLineInfo(), {}) + result.magic = m -proc newModuleGraph*(config: ConfigRef = nil): ModuleGraph = +proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph = result = ModuleGraph() initStrTable(result.packageSyms) result.deps = initIntSet() result.modules = @[] result.importStack = @[] - result.inclToMod = initTable[int32, int32]() - if config.isNil: - result.config = newConfigRef() - else: - result.config = config + result.inclToMod = initTable[FileIndex, FileIndex]() + result.config = config + result.cache = cache result.owners = @[] result.methods = @[] + initStrTable(result.compilerprocs) + initStrTable(result.exposed) + result.opNot = createMagic(result, "not", mNot) + result.opContains = createMagic(result, "contains", mInSet) + result.emptyNode = newNode(nkEmpty) + init(result.incr) + result.recordStmt = proc (graph: ModuleGraph; m: PSym; n: PNode) {.nimcall.} = + discard + result.cacheSeqs = initTable[string, PNode]() + result.cacheCounters = initTable[string, BiggestInt]() + result.cacheTables = initTable[string, BTree[string, PNode]]() proc resetAllModules*(g: ModuleGraph) = - initStrTable(packageSyms) - deps = initIntSet() - modules = @[] - importStack = @[] - inclToMod = initTable[int32, int32]() - usageSym = nil - owners = @[] - methods = @[] - -proc getModule*(g: ModuleGraph; fileIdx: int32): PSym = - if fileIdx >= 0 and fileIdx < modules.len: - result = modules[fileIdx] + initStrTable(g.packageSyms) + g.deps = initIntSet() + g.modules = @[] + g.importStack = @[] + g.inclToMod = initTable[FileIndex, FileIndex]() + g.usageSym = nil + g.owners = @[] + g.methods = @[] + initStrTable(g.compilerprocs) + initStrTable(g.exposed) + +proc getModule*(g: ModuleGraph; fileIdx: FileIndex): PSym = + if fileIdx.int32 >= 0 and fileIdx.int32 < g.modules.len: + result = g.modules[fileIdx.int32] proc dependsOn(a, b: int): int {.inline.} = (a shl 15) + b -proc addDep*(g: ModuleGraph; m: PSym, dep: int32) = - assert m.position == m.info.fileIndex - addModuleDep(m.info.fileIndex, dep, isIncludeFile = false) - if suggestMode: - deps.incl m.position.dependsOn(dep) +proc addDep*(g: ModuleGraph; m: PSym, dep: FileIndex) = + assert m.position == m.info.fileIndex.int32 + addModuleDep(g.incr, g.config, m.info.fileIndex, dep, isIncludeFile = false) + if g.suggestMode: + g.deps.incl m.position.dependsOn(dep.int) # we compute the transitive closure later when quering the graph lazily. - # this improve efficiency quite a lot: + # this improves efficiency quite a lot: #invalidTransitiveClosure = true -proc addIncludeDep*(g: ModuleGraph; module, includeFile: int32) = - addModuleDep(module, includeFile, isIncludeFile = true) - discard hasKeyOrPut(inclToMod, includeFile, module) +proc addIncludeDep*(g: ModuleGraph; module, includeFile: FileIndex) = + addModuleDep(g.incr, g.config, module, includeFile, isIncludeFile = true) + discard hasKeyOrPut(g.inclToMod, includeFile, module) -proc parentModule*(g: ModuleGraph; fileIdx: int32): int32 = +proc parentModule*(g: ModuleGraph; fileIdx: FileIndex): FileIndex = ## returns 'fileIdx' if the file belonging to this index is ## directly used as a module or else the module that first ## references this include file. - if fileIdx >= 0 and fileIdx < modules.len and modules[fileIdx] != nil: + if fileIdx.int32 >= 0 and fileIdx.int32 < g.modules.len and g.modules[fileIdx.int32] != nil: result = fileIdx else: - result = inclToMod.getOrDefault(fileIdx) + result = g.inclToMod.getOrDefault(fileIdx) proc transitiveClosure(g: var IntSet; n: int) = # warshall's algorithm @@ -111,23 +144,23 @@ proc transitiveClosure(g: var IntSet; n: int) = if g.contains(i.dependsOn(k)) and g.contains(k.dependsOn(j)): g.incl i.dependsOn(j) -proc markDirty*(g: ModuleGraph; fileIdx: int32) = - let m = getModule fileIdx +proc markDirty*(g: ModuleGraph; fileIdx: FileIndex) = + let m = g.getModule fileIdx if m != nil: incl m.flags, sfDirty -proc markClientsDirty*(g: ModuleGraph; fileIdx: int32) = +proc markClientsDirty*(g: ModuleGraph; fileIdx: FileIndex) = # we need to mark its dependent modules D as dirty right away because after # nimsuggest is done with this module, the module's dirty flag will be # cleared but D still needs to be remembered as 'dirty'. - if invalidTransitiveClosure: - invalidTransitiveClosure = false - transitiveClosure(deps, modules.len) + if g.invalidTransitiveClosure: + g.invalidTransitiveClosure = false + transitiveClosure(g.deps, g.modules.len) # every module that *depends* on this file is also dirty: - for i in 0i32..<modules.len.int32: - let m = modules[i] - if m != nil and deps.contains(i.dependsOn(fileIdx)): + for i in 0i32..<g.modules.len.int32: + let m = g.modules[i] + if m != nil and g.deps.contains(i.dependsOn(fileIdx.int)): incl m.flags, sfDirty proc isDirty*(g: ModuleGraph; m: PSym): bool = - result = suggestMode and sfDirty in m.flags + result = g.suggestMode and sfDirty in m.flags diff --git a/compiler/modulepaths.nim b/compiler/modulepaths.nim index 878c22cf8..118002fcf 100644 --- a/compiler/modulepaths.nim +++ b/compiler/modulepaths.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -import ast, renderer, strutils, msgs, options, idents, os +import ast, renderer, strutils, msgs, options, idents, os, lineinfos import nimblecmd @@ -45,13 +45,6 @@ when false: if best.len > 0 and fileExists(res): result = res -const stdlibDirs = [ - "pure", "core", "arch", - "pure/collections", - "pure/concurrency", "impure", - "wrappers", "wrappers/linenoise", - "windows", "posix", "js"] - when false: proc resolveDollar(project, source, pkg, subdir: string; info: TLineInfo): string = template attempt(a) = @@ -113,16 +106,18 @@ when false: localError(pkg.info, "package name must be an identifier or string literal") result = "" -proc getModuleName*(n: PNode): string = +proc getModuleName*(conf: ConfigRef; n: PNode): string = # This returns a short relative module name without the nim extension # e.g. like "system", "importer" or "somepath/module" # The proc won't perform any checks that the path is actually valid case n.kind of nkStrLit, nkRStrLit, nkTripleStrLit: try: - result = pathSubs(n.strVal, n.info.toFullPath().splitFile().dir) + result = + pathSubs(conf, n.strVal, toFullPath(conf, n.info).splitFile().dir) + .replace(" ") except ValueError: - localError(n.info, "invalid path: " & n.strVal) + localError(conf, n.info, "invalid path: " & n.strVal) result = n.strVal of nkIdent: result = n.ident.s @@ -131,13 +126,6 @@ proc getModuleName*(n: PNode): string = of nkInfix: let n0 = n[0] let n1 = n[1] - if n0.kind == nkIdent and n0.ident.id == getIdent("as").id: - # XXX hack ahead: - n.kind = nkImportAs - n.sons[0] = n.sons[1] - n.sons[1] = n.sons[2] - n.sons.setLen(2) - return getModuleName(n.sons[0]) when false: if n1.kind == nkPrefix and n1[0].kind == nkIdent and n1[0].ident.s == "$": if n0.kind == nkIdent and n0.ident.s == "/": @@ -146,17 +134,10 @@ proc getModuleName*(n: PNode): string = localError(n.info, "only '/' supported with $package notation") result = "" else: - let modname = getModuleName(n[2]) - if $n1 == "std": - template attempt(a) = - let x = addFileExt(a, "nim") - if fileExists(x): return x - for candidate in stdlibDirs: - attempt(options.libpath / candidate / modname) - + let modname = getModuleName(conf, n[2]) # hacky way to implement 'x / y /../ z': - result = getModuleName(n1) - result.add renderTree(n0, {renderNoComments}) + result = getModuleName(conf, n1) + result.add renderTree(n0, {renderNoComments}).replace(" ") result.add modname of nkPrefix: when false: @@ -167,21 +148,22 @@ proc getModuleName*(n: PNode): string = # hacky way to implement 'x / y /../ z': result = renderTree(n, {renderNoComments}).replace(" ") of nkDotExpr: + localError(conf, n.info, warnDeprecated, "using '.' instead of '/' in import paths") result = renderTree(n, {renderNoComments}).replace(".", "/") of nkImportAs: - result = getModuleName(n.sons[0]) + result = getModuleName(conf, n.sons[0]) else: - localError(n.info, errGenerated, "invalid module name: '$1'" % n.renderTree) + localError(conf, n.info, "invalid module name: '$1'" % n.renderTree) result = "" -proc checkModuleName*(n: PNode; doLocalError=true): int32 = +proc checkModuleName*(conf: ConfigRef; n: PNode; doLocalError=true): FileIndex = # This returns the full canonical path for a given module import - let modulename = n.getModuleName - let fullPath = findModule(modulename, n.info.toFullPath) + let modulename = getModuleName(conf, n) + let fullPath = findModule(conf, modulename, toFullPath(conf, n.info)) if fullPath.len == 0: if doLocalError: let m = if modulename.len > 0: modulename else: $n - localError(n.info, errCannotOpenFile, m) + localError(conf, n.info, "cannot open file: " & m) result = InvalidFileIDX else: - result = fullPath.fileInfoIdx + result = fileInfoIdx(conf, fullPath) diff --git a/compiler/modules.nim b/compiler/modules.nim index a3be5a518..b3a1e90d6 100644 --- a/compiler/modules.nim +++ b/compiler/modules.nim @@ -10,143 +10,41 @@ ## Implements the module handling, including the caching of modules. import - ast, astalgo, magicsys, std / sha1, rodread, msgs, cgendata, sigmatch, options, - idents, os, lexer, idgen, passes, syntaxes, llstream, modulegraphs, rod - -when false: - type - TNeedRecompile* = enum Maybe, No, Yes, Probing, Recompiled - THashStatus* = enum hashNotTaken, hashCached, hashHasChanged, hashNotChanged - - TModuleInMemory* = object - hash*: SecureHash - deps*: seq[int32] ## XXX: slurped files are currently not tracked - - needsRecompile*: TNeedRecompile - hashStatus*: THashStatus - - var - gCompiledModules: seq[PSym] = @[] - gMemCacheData*: seq[TModuleInMemory] = @[] - ## XXX: we should implement recycling of file IDs - ## if the user keeps renaming modules, the file IDs will keep growing - gFuzzyGraphChecking*: bool # nimsuggest uses this. XXX figure out why. - - proc hashChanged(fileIdx: int32): bool = - internalAssert fileIdx >= 0 and fileIdx < gMemCacheData.len - - template updateStatus = - gMemCacheData[fileIdx].hashStatus = if result: hashHasChanged - else: hashNotChanged - # echo "TESTING Hash: ", fileIdx.toFilename, " ", result - - case gMemCacheData[fileIdx].hashStatus - of hashHasChanged: - result = true - of hashNotChanged: - result = false - of hashCached: - let newHash = secureHashFile(fileIdx.toFullPath) - result = newHash != gMemCacheData[fileIdx].hash - gMemCacheData[fileIdx].hash = newHash - updateStatus() - of hashNotTaken: - gMemCacheData[fileIdx].hash = secureHashFile(fileIdx.toFullPath) - result = true - updateStatus() - - proc doHash(fileIdx: int32) = - if gMemCacheData[fileIdx].hashStatus == hashNotTaken: - # echo "FIRST Hash: ", fileIdx.ToFilename - gMemCacheData[fileIdx].hash = secureHashFile(fileIdx.toFullPath) - - proc resetModule*(fileIdx: int32) = - # echo "HARD RESETTING ", fileIdx.toFilename - if fileIdx <% gMemCacheData.len: - gMemCacheData[fileIdx].needsRecompile = Yes - if fileIdx <% gCompiledModules.len: - gCompiledModules[fileIdx] = nil - if fileIdx <% cgendata.gModules.len: - cgendata.gModules[fileIdx] = nil - - proc resetModule*(module: PSym) = - let conflict = getModule(module.position.int32) - if conflict == nil: return - doAssert conflict == module - resetModule(module.position.int32) - initStrTable(module.tab) - - proc resetAllModules* = - for i in 0..gCompiledModules.high: - if gCompiledModules[i] != nil: - resetModule(i.int32) - resetPackageCache() - # for m in cgenModules(): echo "CGEN MODULE FOUND" - - proc resetAllModulesHard* = - resetPackageCache() - gCompiledModules.setLen 0 - gMemCacheData.setLen 0 - magicsys.resetSysTypes() - # XXX - #gOwners = @[] - - proc checkDepMem(fileIdx: int32): TNeedRecompile = - template markDirty = - resetModule(fileIdx) - return Yes - - if gFuzzyGraphChecking: - if gMemCacheData[fileIdx].needsRecompile != Maybe: - return gMemCacheData[fileIdx].needsRecompile - else: - # cycle detection: We claim that a cycle does no harm. - if gMemCacheData[fileIdx].needsRecompile == Probing: - return No - - if optForceFullMake in gGlobalOptions or hashChanged(fileIdx): - markDirty() - - if gMemCacheData[fileIdx].deps != nil: - gMemCacheData[fileIdx].needsRecompile = Probing - for dep in gMemCacheData[fileIdx].deps: - let d = checkDepMem(dep) - if d in {Yes, Recompiled}: - # echo fileIdx.toFilename, " depends on ", dep.toFilename, " ", d - markDirty() - - gMemCacheData[fileIdx].needsRecompile = No - return No - -proc resetSystemArtifacts*() = - magicsys.resetSysTypes() - -proc newModule(graph: ModuleGraph; fileIdx: int32): PSym = + ast, astalgo, magicsys, std / sha1, msgs, cgendata, sigmatch, options, + idents, os, lexer, idgen, passes, syntaxes, llstream, modulegraphs, rod, + lineinfos + +proc resetSystemArtifacts*(g: ModuleGraph) = + magicsys.resetSysTypes(g) + +proc newModule(graph: ModuleGraph; fileIdx: FileIndex): PSym = # We cannot call ``newSym`` here, because we have to circumvent the ID # mechanism, which we do in order to assign each module a persistent ID. new(result) result.id = -1 # for better error checking result.kind = skModule - let filename = fileIdx.toFullPath - result.name = getIdent(splitFile(filename).name) + let filename = toFullPath(graph.config, fileIdx) + result.name = getIdent(graph.cache, splitFile(filename).name) if not isNimIdentifier(result.name.s): - rawMessage(errInvalidModuleName, result.name.s) + rawMessage(graph.config, errGenerated, "invalid module name: " & result.name.s) result.info = newLineInfo(fileIdx, 1, 1) let - pck = getPackageName(filename) + pck = getPackageName(graph.config, filename) pck2 = if pck.len > 0: pck else: "unknown" - pack = getIdent(pck2) + pack = getIdent(graph.cache, pck2) var packSym = graph.packageSyms.strTableGet(pack) if packSym == nil: - packSym = newSym(skPackage, getIdent(pck2), nil, result.info) + packSym = newSym(skPackage, getIdent(graph.cache, pck2), nil, result.info) initStrTable(packSym.tab) graph.packageSyms.strTableAdd(packSym) result.owner = packSym - result.position = fileIdx + result.position = int fileIdx - growCache graph.modules, fileIdx + if int(fileIdx) >= graph.modules.len: + setLen(graph.modules, int(fileIdx) + 1) + #growCache graph.modules, int fileIdx graph.modules[result.position] = result incl(result.flags, sfUsed) @@ -154,95 +52,79 @@ proc newModule(graph: ModuleGraph; fileIdx: int32): PSym = strTableAdd(result.tab, result) # a module knows itself let existing = strTableGet(packSym.tab, result.name) if existing != nil and existing.info.fileIndex != result.info.fileIndex: - localError(result.info, "module names need to be unique per Nimble package; module clashes with " & existing.info.fileIndex.toFullPath) + localError(graph.config, result.info, + "module names need to be unique per Nimble package; module clashes with " & + toFullPath(graph.config, existing.info.fileIndex)) # strTableIncl() for error corrections: discard strTableIncl(packSym.tab, result) -proc compileModule*(graph: ModuleGraph; fileIdx: int32; cache: IdentCache, flags: TSymFlags): PSym = +proc compileModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags): PSym = result = graph.getModule(fileIdx) if result == nil: - #growCache gMemCacheData, fileIdx - #gMemCacheData[fileIdx].needsRecompile = Probing result = newModule(graph, fileIdx) - var rd: PRodReader result.flags = result.flags + flags if sfMainModule in result.flags: - gMainPackageId = result.owner.id - - when false: - if gCmd in {cmdCompileToC, cmdCompileToCpp, cmdCheck, cmdIdeTools}: - rd = handleSymbolFile(result, cache) - if result.id < 0: - internalError("handleSymbolFile should have set the module's ID") - return - else: - discard - result.id = getModuleId(fileIdx, toFullPath(fileIdx)) + graph.config.mainPackageId = result.owner.id + + result.id = getModuleId(graph, fileIdx, toFullPath(graph.config, fileIdx)) discard processModule(graph, result, - if sfMainModule in flags and gProjectIsStdin: stdin.llStreamOpen else: nil, - rd, cache) - #if optCaasEnabled in gGlobalOptions: - # gMemCacheData[fileIdx].needsRecompile = Recompiled - # if validFile: doHash fileIdx + if sfMainModule in flags and graph.config.projectIsStdin: stdin.llStreamOpen else: nil) elif graph.isDirty(result): result.flags.excl sfDirty # reset module fields: initStrTable(result.tab) result.ast = nil discard processModule(graph, result, - if sfMainModule in flags and gProjectIsStdin: stdin.llStreamOpen else: nil, - nil, cache) + if sfMainModule in flags and graph.config.projectIsStdin: stdin.llStreamOpen else: nil) graph.markClientsDirty(fileIdx) - when false: - if checkDepMem(fileIdx) == Yes: - result = compileModule(fileIdx, cache, flags) - else: - result = gCompiledModules[fileIdx] - -proc importModule*(graph: ModuleGraph; s: PSym, fileIdx: int32; - cache: IdentCache): PSym {.procvar.} = + +proc importModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PSym {.procvar.} = # this is called by the semantic checking phase - result = compileModule(graph, fileIdx, cache, {}) + assert graph.config != nil + result = compileModule(graph, fileIdx, {}) graph.addDep(s, fileIdx) #if sfSystemModule in result.flags: # localError(result.info, errAttemptToRedefine, result.name.s) # restore the notes for outer module: - gNotes = if s.owner.id == gMainPackageId: gMainPackageNotes - else: ForeignPackageNotes + graph.config.notes = + if s.owner.id == graph.config.mainPackageId: graph.config.mainPackageNotes + else: graph.config.foreignPackageNotes -proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: int32; - cache: IdentCache): PNode {.procvar.} = - result = syntaxes.parseFile(fileIdx, cache) +proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PNode {.procvar.} = + result = syntaxes.parseFile(fileIdx, graph.cache, graph.config) graph.addDep(s, fileIdx) - graph.addIncludeDep(s.position.int32, fileIdx) - -proc compileSystemModule*(graph: ModuleGraph; cache: IdentCache) = - if magicsys.systemModule == nil: - systemFileIdx = fileInfoIdx(options.libpath/"system.nim") - discard graph.compileModule(systemFileIdx, cache, {sfSystemModule}) - -proc wantMainModule* = - if gProjectFull.len == 0: - fatal(gCmdLineInfo, errCommandExpectsFilename) - gProjectMainIdx = addFileExt(gProjectFull, NimExt).fileInfoIdx - -passes.gIncludeFile = includeModule -passes.gImportModule = importModule - -proc compileProject*(graph: ModuleGraph; cache: IdentCache; - projectFileIdx = -1'i32) = - wantMainModule() - let systemFileIdx = fileInfoIdx(options.libpath / "system.nim") - let projectFile = if projectFileIdx < 0: gProjectMainIdx else: projectFileIdx + graph.addIncludeDep(s.position.FileIndex, fileIdx) + +proc connectCallbacks*(graph: ModuleGraph) = + graph.includeFileCallback = includeModule + graph.importModuleCallback = importModule + +proc compileSystemModule*(graph: ModuleGraph) = + if graph.systemModule == nil: + connectCallbacks(graph) + graph.config.m.systemFileIdx = fileInfoIdx(graph.config, graph.config.libpath / "system.nim") + discard graph.compileModule(graph.config.m.systemFileIdx, {sfSystemModule}) + +proc wantMainModule*(conf: ConfigRef) = + if conf.projectFull.len == 0: + fatal(conf, newLineInfo(conf, "command line", 1, 1), errGenerated, "command expects a filename") + conf.projectMainIdx = fileInfoIdx(conf, addFileExt(conf.projectFull, NimExt)) + +proc compileProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIDX) = + connectCallbacks(graph) + let conf = graph.config + wantMainModule(conf) + let systemFileIdx = fileInfoIdx(conf, conf.libpath / "system.nim") + let projectFile = if projectFileIdx == InvalidFileIDX: conf.projectMainIdx else: projectFileIdx graph.importStack.add projectFile if projectFile == systemFileIdx: - discard graph.compileModule(projectFile, cache, {sfMainModule, sfSystemModule}) + discard graph.compileModule(projectFile, {sfMainModule, sfSystemModule}) else: - graph.compileSystemModule(cache) - discard graph.compileModule(projectFile, cache, {sfMainModule}) + graph.compileSystemModule() + discard graph.compileModule(projectFile, {sfMainModule}) proc makeModule*(graph: ModuleGraph; filename: string): PSym = - result = graph.newModule(fileInfoIdx filename) + result = graph.newModule(fileInfoIdx(graph.config, filename)) result.id = getID() proc makeStdinModule*(graph: ModuleGraph): PSym = graph.makeModule"stdin" diff --git a/compiler/msgs.nim b/compiler/msgs.nim index ac4242e67..b7b7c8474 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -8,563 +8,29 @@ # import - options, strutils, os, tables, ropes, platform, terminal, macros + options, strutils, os, tables, ropes, platform, terminal, macros, + lineinfos -type - TMsgKind* = enum - errUnknown, errInternal, errIllFormedAstX, errCannotOpenFile, errGenerated, - errXCompilerDoesNotSupportCpp, errStringLiteralExpected, - errIntLiteralExpected, errInvalidCharacterConstant, - errClosingTripleQuoteExpected, errClosingQuoteExpected, - errTabulatorsAreNotAllowed, errInvalidToken, errLineTooLong, - errInvalidNumber, errInvalidNumberOctalCode, errNumberOutOfRange, - errNnotAllowedInCharacter, errClosingBracketExpected, errMissingFinalQuote, - errIdentifierExpected, errNewlineExpected, errInvalidModuleName, - errOperatorExpected, errTokenExpected, errStringAfterIncludeExpected, - errRecursiveDependencyX, errOnOrOffExpected, errNoneSpeedOrSizeExpected, - errInvalidPragma, errUnknownPragma, errInvalidDirectiveX, - errAtPopWithoutPush, errEmptyAsm, errInvalidIndentation, - errExceptionExpected, errExceptionAlreadyHandled, - errYieldNotAllowedHere, errYieldNotAllowedInTryStmt, - errInvalidNumberOfYieldExpr, errCannotReturnExpr, - errNoReturnWithReturnTypeNotAllowed, errAttemptToRedefine, - errStmtInvalidAfterReturn, errStmtExpected, errInvalidLabel, - errInvalidCmdLineOption, errCmdLineArgExpected, errCmdLineNoArgExpected, - errInvalidVarSubstitution, errUnknownVar, errUnknownCcompiler, - errOnOrOffExpectedButXFound, errOnOffOrListExpectedButXFound, - errNoneBoehmRefcExpectedButXFound, - errNoneSpeedOrSizeExpectedButXFound, errGuiConsoleOrLibExpectedButXFound, - errUnknownOS, errUnknownCPU, errGenOutExpectedButXFound, - errArgsNeedRunOption, errInvalidMultipleAsgn, errColonOrEqualsExpected, - errExprExpected, errUndeclaredField, - errUndeclaredRoutine, errUseQualifier, - errTypeExpected, - errSystemNeeds, errExecutionOfProgramFailed, errNotOverloadable, - errInvalidArgForX, errStmtHasNoEffect, errXExpectsTypeOrValue, - errXExpectsArrayType, errIteratorCannotBeInstantiated, errExprXAmbiguous, - errConstantDivisionByZero, errOrdinalTypeExpected, - errOrdinalOrFloatTypeExpected, errOverOrUnderflow, - errCannotEvalXBecauseIncompletelyDefined, errChrExpectsRange0_255, - errDynlibRequiresExportc, errUndeclaredFieldX, errNilAccess, - errIndexOutOfBounds, errIndexTypesDoNotMatch, errBracketsInvalidForType, - errValueOutOfSetBounds, errFieldInitTwice, errFieldNotInit, - errExprXCannotBeCalled, errExprHasNoType, errExprXHasNoType, - errCastNotInSafeMode, errExprCannotBeCastToX, errCommaOrParRiExpected, - errCurlyLeOrParLeExpected, errSectionExpected, errRangeExpected, - errMagicOnlyInSystem, errPowerOfTwoExpected, - errStringMayNotBeEmpty, errCallConvExpected, errProcOnlyOneCallConv, - errSymbolMustBeImported, errExprMustBeBool, errConstExprExpected, - errDuplicateCaseLabel, errRangeIsEmpty, errSelectorMustBeOfCertainTypes, - errSelectorMustBeOrdinal, errOrdXMustNotBeNegative, errLenXinvalid, - errWrongNumberOfVariables, errExprCannotBeRaised, errBreakOnlyInLoop, - errTypeXhasUnknownSize, errConstNeedsConstExpr, errConstNeedsValue, - errResultCannotBeOpenArray, errSizeTooBig, errSetTooBig, - errBaseTypeMustBeOrdinal, errInheritanceOnlyWithNonFinalObjects, - errInheritanceOnlyWithEnums, errIllegalRecursionInTypeX, - errCannotInstantiateX, errExprHasNoAddress, errXStackEscape, - errVarForOutParamNeededX, - errPureTypeMismatch, errTypeMismatch, errButExpected, errButExpectedX, - errAmbiguousCallXYZ, errWrongNumberOfArguments, - errWrongNumberOfArgumentsInCall, - errMissingGenericParamsForTemplate, - errXCannotBePassedToProcVar, - errXCannotBeInParamDecl, errPragmaOnlyInHeaderOfProcX, errImplOfXNotAllowed, - errImplOfXexpected, errNoSymbolToBorrowFromFound, errDiscardValueX, - errInvalidDiscard, errIllegalConvFromXtoY, errCannotBindXTwice, - errInvalidOrderInArrayConstructor, - errInvalidOrderInEnumX, errEnumXHasHoles, errExceptExpected, errInvalidTry, - errOptionExpected, errXisNoLabel, errNotAllCasesCovered, - errUnknownSubstitionVar, errComplexStmtRequiresInd, errXisNotCallable, - errNoPragmasAllowedForX, errNoGenericParamsAllowedForX, - errInvalidParamKindX, errDefaultArgumentInvalid, errNamedParamHasToBeIdent, - errNoReturnTypeForX, errConvNeedsOneArg, errInvalidPragmaX, - errXNotAllowedHere, errInvalidControlFlowX, - errXisNoType, errCircumNeedsPointer, errInvalidExpression, - errInvalidExpressionX, errEnumHasNoValueX, errNamedExprExpected, - errNamedExprNotAllowed, errXExpectsOneTypeParam, - errArrayExpectsTwoTypeParams, errInvalidVisibilityX, errInitHereNotAllowed, - errXCannotBeAssignedTo, errIteratorNotAllowed, errXNeedsReturnType, - errNoReturnTypeDeclared, - errNoCommand, errInvalidCommandX, errXOnlyAtModuleScope, - errXNeedsParamObjectType, - errTemplateInstantiationTooNested, errInstantiationFrom, - errInvalidIndexValueForTuple, errCommandExpectsFilename, - errMainModuleMustBeSpecified, - errXExpected, - errTIsNotAConcreteType, - errCastToANonConcreteType, - errInvalidSectionStart, errGridTableNotImplemented, errGeneralParseError, - errNewSectionExpected, errWhitespaceExpected, errXisNoValidIndexFile, - errCannotRenderX, errVarVarTypeNotAllowed, errInstantiateXExplicitly, - errOnlyACallOpCanBeDelegator, errUsingNoSymbol, - errMacroBodyDependsOnGenericTypes, - errDestructorNotGenericEnough, - errInlineIteratorsAsProcParams, - errXExpectsTwoArguments, - errXExpectsObjectTypes, errXcanNeverBeOfThisSubtype, errTooManyIterations, - errCannotInterpretNodeX, errFieldXNotFound, errInvalidConversionFromTypeX, - errAssertionFailed, errCannotGenerateCodeForX, errXRequiresOneArgument, - errUnhandledExceptionX, errCyclicTree, errXisNoMacroOrTemplate, - errXhasSideEffects, errIteratorExpected, errLetNeedsInit, - errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX, - errXCannotBeClosure, errXMustBeCompileTime, - errCannotInferTypeOfTheLiteral, - errCannotInferReturnType, - errCannotInferStaticParam, - errGenericLambdaNotAllowed, - errProcHasNoConcreteType, - errCompilerDoesntSupportTarget, - errInOutFlagNotExtern, - errUser, - warnCannotOpenFile, - warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, - warnDeprecated, warnConfigDeprecated, - warnSmallLshouldNotBeUsed, warnUnknownMagic, warnRedefinitionOfLabel, - warnUnknownSubstitutionX, warnLanguageXNotSupported, - warnFieldXNotSupported, warnCommentXIgnored, - warnNilStatement, warnTypelessParam, - warnUseBase, warnWriteToForeignHeap, warnUnsafeCode, - warnEachIdentIsTuple, warnShadowIdent, - warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe, warnGcUnsafe2, - warnUninit, warnGcMem, warnDestructor, warnLockLevel, warnResultShadowed, - warnUser, - hintSuccess, hintSuccessX, - hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, - hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled, - hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath, - hintConditionAlwaysTrue, hintName, hintPattern, - hintExecuting, hintLinking, hintDependency, - hintSource, hintStackTrace, hintGCStats, - hintUser, hintUserRaw - -const - MsgKindToStr*: array[TMsgKind, string] = [ - errUnknown: "unknown error", - errInternal: "internal error: $1", - errIllFormedAstX: "illformed AST: $1", - errCannotOpenFile: "cannot open \'$1\'", - errGenerated: "$1", - errXCompilerDoesNotSupportCpp: "\'$1\' compiler does not support C++", - errStringLiteralExpected: "string literal expected", - errIntLiteralExpected: "integer literal expected", - errInvalidCharacterConstant: "invalid character constant", - errClosingTripleQuoteExpected: "closing \"\"\" expected, but end of file reached", - errClosingQuoteExpected: "closing \" expected", - errTabulatorsAreNotAllowed: "tabulators are not allowed", - errInvalidToken: "invalid token: $1", - errLineTooLong: "line too long", - errInvalidNumber: "$1 is not a valid number", - errInvalidNumberOctalCode: "$1 is not a valid number; did you mean octal? Then use one of '0o', '0c' or '0C'.", - errNumberOutOfRange: "number $1 out of valid range", - errNnotAllowedInCharacter: "\\n not allowed in character literal", - errClosingBracketExpected: "closing ']' expected, but end of file reached", - errMissingFinalQuote: "missing final \' for character literal", - errIdentifierExpected: "identifier expected, but found \'$1\'", - errNewlineExpected: "newline expected, but found \'$1\'", - errInvalidModuleName: "invalid module name: '$1'", - errOperatorExpected: "operator expected, but found \'$1\'", - errTokenExpected: "\'$1\' expected", - errStringAfterIncludeExpected: "string after \'include\' expected", - errRecursiveDependencyX: "recursive dependency: \'$1\'", - errOnOrOffExpected: "\'on\' or \'off\' expected", - errNoneSpeedOrSizeExpected: "\'none\', \'speed\' or \'size\' expected", - errInvalidPragma: "invalid pragma", - errUnknownPragma: "unknown pragma: \'$1\'", - errInvalidDirectiveX: "invalid directive: \'$1\'", - errAtPopWithoutPush: "\'pop\' without a \'push\' pragma", - errEmptyAsm: "empty asm statement", - errInvalidIndentation: "invalid indentation", - errExceptionExpected: "exception expected", - errExceptionAlreadyHandled: "exception already handled", - errYieldNotAllowedHere: "'yield' only allowed in an iterator", - errYieldNotAllowedInTryStmt: "'yield' cannot be used within 'try' in a non-inlined iterator", - errInvalidNumberOfYieldExpr: "invalid number of \'yield\' expressions", - errCannotReturnExpr: "current routine cannot return an expression", - errNoReturnWithReturnTypeNotAllowed: "routines with NoReturn pragma are not allowed to have return type", - errAttemptToRedefine: "redefinition of \'$1\'", - errStmtInvalidAfterReturn: "statement not allowed after \'return\', \'break\', \'raise\', \'continue\' or proc call with noreturn pragma", - errStmtExpected: "statement expected", - errInvalidLabel: "\'$1\' is no label", - errInvalidCmdLineOption: "invalid command line option: \'$1\'", - errCmdLineArgExpected: "argument for command line option expected: \'$1\'", - errCmdLineNoArgExpected: "invalid argument for command line option: \'$1\'", - errInvalidVarSubstitution: "invalid variable substitution in \'$1\'", - errUnknownVar: "unknown variable: \'$1\'", - errUnknownCcompiler: "unknown C compiler: \'$1\'", - errOnOrOffExpectedButXFound: "\'on\' or \'off\' expected, but \'$1\' found", - errOnOffOrListExpectedButXFound: "\'on\', \'off\' or \'list\' expected, but \'$1\' found", - errNoneBoehmRefcExpectedButXFound: "'none', 'boehm' or 'refc' expected, but '$1' found", - errNoneSpeedOrSizeExpectedButXFound: "'none', 'speed' or 'size' expected, but '$1' found", - errGuiConsoleOrLibExpectedButXFound: "'gui', 'console' or 'lib' expected, but '$1' found", - errUnknownOS: "unknown OS: '$1'", - errUnknownCPU: "unknown CPU: '$1'", - errGenOutExpectedButXFound: "'c', 'c++' or 'yaml' expected, but '$1' found", - errArgsNeedRunOption: "arguments can only be given if the '--run' option is selected", - errInvalidMultipleAsgn: "multiple assignment is not allowed", - errColonOrEqualsExpected: "\':\' or \'=\' expected, but found \'$1\'", - errExprExpected: "expression expected, but found \'$1\'", - errUndeclaredField: "undeclared field: \'$1\'", - errUndeclaredRoutine: "attempting to call undeclared routine: \'$1\'", - errUseQualifier: "ambiguous identifier: \'$1\' -- use a qualifier", - errTypeExpected: "type expected", - errSystemNeeds: "system module needs \'$1\'", - errExecutionOfProgramFailed: "execution of an external program failed: '$1'", - errNotOverloadable: "overloaded \'$1\' leads to ambiguous calls", - errInvalidArgForX: "invalid argument for \'$1\'", - errStmtHasNoEffect: "statement has no effect", - errXExpectsTypeOrValue: "\'$1\' expects a type or value", - errXExpectsArrayType: "\'$1\' expects an array type", - errIteratorCannotBeInstantiated: "'$1' cannot be instantiated because its body has not been compiled yet", - errExprXAmbiguous: "expression '$1' ambiguous in this context", - errConstantDivisionByZero: "division by zero", - errOrdinalTypeExpected: "ordinal type expected", - errOrdinalOrFloatTypeExpected: "ordinal or float type expected", - errOverOrUnderflow: "over- or underflow", - errCannotEvalXBecauseIncompletelyDefined: "cannot evaluate '$1' because type is not defined completely", - errChrExpectsRange0_255: "\'chr\' expects an int in the range 0..255", - errDynlibRequiresExportc: "\'dynlib\' requires \'exportc\'", - errUndeclaredFieldX: "undeclared field: \'$1\'", - errNilAccess: "attempt to access a nil address", - errIndexOutOfBounds: "index out of bounds", - errIndexTypesDoNotMatch: "index types do not match", - errBracketsInvalidForType: "\'[]\' operator invalid for this type", - errValueOutOfSetBounds: "value out of set bounds", - errFieldInitTwice: "field initialized twice: \'$1\'", - errFieldNotInit: "field \'$1\' not initialized", - errExprXCannotBeCalled: "expression \'$1\' cannot be called", - errExprHasNoType: "expression has no type", - errExprXHasNoType: "expression \'$1\' has no type (or is ambiguous)", - errCastNotInSafeMode: "\'cast\' not allowed in safe mode", - errExprCannotBeCastToX: "expression cannot be cast to $1", - errCommaOrParRiExpected: "',' or ')' expected", - errCurlyLeOrParLeExpected: "\'{\' or \'(\' expected", - errSectionExpected: "section (\'type\', \'proc\', etc.) expected", - errRangeExpected: "range expected", - errMagicOnlyInSystem: "\'magic\' only allowed in system module", - errPowerOfTwoExpected: "power of two expected", - errStringMayNotBeEmpty: "string literal may not be empty", - errCallConvExpected: "calling convention expected", - errProcOnlyOneCallConv: "a proc can only have one calling convention", - errSymbolMustBeImported: "symbol must be imported if 'lib' pragma is used", - errExprMustBeBool: "expression must be of type 'bool'", - errConstExprExpected: "constant expression expected", - errDuplicateCaseLabel: "duplicate case label", - errRangeIsEmpty: "range is empty", - errSelectorMustBeOfCertainTypes: "selector must be of an ordinal type, float or string", - errSelectorMustBeOrdinal: "selector must be of an ordinal type", - errOrdXMustNotBeNegative: "ord($1) must not be negative", - errLenXinvalid: "len($1) must be less than 32768", - errWrongNumberOfVariables: "wrong number of variables", - errExprCannotBeRaised: "only a 'ref object' can be raised", - errBreakOnlyInLoop: "'break' only allowed in loop construct", - errTypeXhasUnknownSize: "type \'$1\' has unknown size", - errConstNeedsConstExpr: "a constant can only be initialized with a constant expression", - errConstNeedsValue: "a constant needs a value", - errResultCannotBeOpenArray: "the result type cannot be on open array", - errSizeTooBig: "computing the type\'s size produced an overflow", - errSetTooBig: "set is too large", - errBaseTypeMustBeOrdinal: "base type of a set must be an ordinal", - errInheritanceOnlyWithNonFinalObjects: "inheritance only works with non-final objects", - errInheritanceOnlyWithEnums: "inheritance only works with an enum", - errIllegalRecursionInTypeX: "illegal recursion in type \'$1\'", - errCannotInstantiateX: "cannot instantiate: \'$1\'", - errExprHasNoAddress: "expression has no address", - errXStackEscape: "address of '$1' may not escape its stack frame", - errVarForOutParamNeededX: "for a \'var\' type a variable needs to be passed; but '$1' is immutable", - errPureTypeMismatch: "type mismatch", - errTypeMismatch: "type mismatch: got <", - errButExpected: "but expected one of: ", - errButExpectedX: "but expected \'$1\'", - errAmbiguousCallXYZ: "ambiguous call; both $1 and $2 match for: $3", - errWrongNumberOfArguments: "wrong number of arguments", - errWrongNumberOfArgumentsInCall: "wrong number of arguments in call to '$1'", - errMissingGenericParamsForTemplate: "'$1' has unspecified generic parameters", - errXCannotBePassedToProcVar: "\'$1\' cannot be passed to a procvar", - errXCannotBeInParamDecl: "$1 cannot be declared in parameter declaration", - errPragmaOnlyInHeaderOfProcX: "pragmas are only allowed in the header of a proc; redefinition of $1", - errImplOfXNotAllowed: "implementation of \'$1\' is not allowed", - errImplOfXexpected: "implementation of \'$1\' expected", - errNoSymbolToBorrowFromFound: "no symbol to borrow from found", - errDiscardValueX: "value of type '$1' has to be discarded", - errInvalidDiscard: "statement returns no value that can be discarded", - errIllegalConvFromXtoY: "conversion from $1 to $2 is invalid", - errCannotBindXTwice: "cannot bind parameter \'$1\' twice", - errInvalidOrderInArrayConstructor: "invalid order in array constructor", - errInvalidOrderInEnumX: "invalid order in enum \'$1\'", - errEnumXHasHoles: "enum \'$1\' has holes", - errExceptExpected: "\'except\' or \'finally\' expected", - errInvalidTry: "after catch all \'except\' or \'finally\' no section may follow", - errOptionExpected: "option expected, but found \'$1\'", - errXisNoLabel: "\'$1\' is not a label", - errNotAllCasesCovered: "not all cases are covered", - errUnknownSubstitionVar: "unknown substitution variable: \'$1\'", - errComplexStmtRequiresInd: "complex statement requires indentation", - errXisNotCallable: "\'$1\' is not callable", - errNoPragmasAllowedForX: "no pragmas allowed for $1", - errNoGenericParamsAllowedForX: "no generic parameters allowed for $1", - errInvalidParamKindX: "invalid param kind: \'$1\'", - errDefaultArgumentInvalid: "default argument invalid", - errNamedParamHasToBeIdent: "named parameter has to be an identifier", - errNoReturnTypeForX: "no return type allowed for $1", - errConvNeedsOneArg: "a type conversion needs exactly one argument", - errInvalidPragmaX: "invalid pragma: $1", - errXNotAllowedHere: "$1 not allowed here", - errInvalidControlFlowX: "invalid control flow: $1", - errXisNoType: "invalid type: \'$1\'", - errCircumNeedsPointer: "'[]' needs a pointer or reference type", - errInvalidExpression: "invalid expression", - errInvalidExpressionX: "invalid expression: \'$1\'", - errEnumHasNoValueX: "enum has no value \'$1\'", - errNamedExprExpected: "named expression expected", - errNamedExprNotAllowed: "named expression not allowed here", - errXExpectsOneTypeParam: "\'$1\' expects one type parameter", - errArrayExpectsTwoTypeParams: "array expects two type parameters", - errInvalidVisibilityX: "invalid visibility: \'$1\'", - errInitHereNotAllowed: "initialization not allowed here", - errXCannotBeAssignedTo: "\'$1\' cannot be assigned to", - errIteratorNotAllowed: "iterators can only be defined at the module\'s top level", - errXNeedsReturnType: "$1 needs a return type", - errNoReturnTypeDeclared: "no return type declared", - errNoCommand: "no command given", - errInvalidCommandX: "invalid command: \'$1\'", - errXOnlyAtModuleScope: "\'$1\' is only allowed at top level", - errXNeedsParamObjectType: "'$1' needs a parameter that has an object type", - errTemplateInstantiationTooNested: "template/macro instantiation too nested", - errInstantiationFrom: "template/generic instantiation from here", - errInvalidIndexValueForTuple: "invalid index value for tuple subscript", - errCommandExpectsFilename: "command expects a filename argument", - errMainModuleMustBeSpecified: "please, specify a main module in the project configuration file", - errXExpected: "\'$1\' expected", - errTIsNotAConcreteType: "\'$1\' is not a concrete type.", - errCastToANonConcreteType: "cannot cast to a non concrete type: \'$1\'", - errInvalidSectionStart: "invalid section start", - errGridTableNotImplemented: "grid table is not implemented", - errGeneralParseError: "general parse error", - errNewSectionExpected: "new section expected", - errWhitespaceExpected: "whitespace expected, got \'$1\'", - errXisNoValidIndexFile: "\'$1\' is no valid index file", - errCannotRenderX: "cannot render reStructuredText element \'$1\'", - errVarVarTypeNotAllowed: "type \'var var\' is not allowed", - errInstantiateXExplicitly: "instantiate '$1' explicitly", - errOnlyACallOpCanBeDelegator: "only a call operator can be a delegator", - errUsingNoSymbol: "'$1' is not a variable, constant or a proc name", - errMacroBodyDependsOnGenericTypes: "the macro body cannot be compiled, " & - "because the parameter '$1' has a generic type", - errDestructorNotGenericEnough: "Destructor signature is too specific. " & - "A destructor must be associated will all instantiations of a generic type", - errInlineIteratorsAsProcParams: "inline iterators can be used as parameters only for " & - "templates, macros and other inline iterators", - errXExpectsTwoArguments: "\'$1\' expects two arguments", - errXExpectsObjectTypes: "\'$1\' expects object types", - errXcanNeverBeOfThisSubtype: "\'$1\' can never be of this subtype", - errTooManyIterations: "interpretation requires too many iterations", - errCannotInterpretNodeX: "cannot evaluate \'$1\'", - errFieldXNotFound: "field \'$1\' cannot be found", - errInvalidConversionFromTypeX: "invalid conversion from type \'$1\'", - errAssertionFailed: "assertion failed", - errCannotGenerateCodeForX: "cannot generate code for \'$1\'", - errXRequiresOneArgument: "$1 requires one parameter", - errUnhandledExceptionX: "unhandled exception: $1", - errCyclicTree: "macro returned a cyclic abstract syntax tree", - errXisNoMacroOrTemplate: "\'$1\' is no macro or template", - errXhasSideEffects: "\'$1\' can have side effects", - errIteratorExpected: "iterator within for loop context expected", - errLetNeedsInit: "'let' symbol requires an initialization", - errThreadvarCannotInit: "a thread var cannot be initialized explicitly; this would only run for the main thread", - errWrongSymbolX: "usage of \'$1\' is a user-defined error", - errIllegalCaptureX: "illegal capture '$1'", - errXCannotBeClosure: "'$1' cannot have 'closure' calling convention", - errXMustBeCompileTime: "'$1' can only be used in compile-time context", - errCannotInferTypeOfTheLiteral: "cannot infer the type of the $1", - errCannotInferReturnType: "cannot infer the return type of the proc", - errCannotInferStaticParam: "cannot infer the value of the static param `$1`", - errGenericLambdaNotAllowed: "A nested proc can have generic parameters only when " & - "it is used as an operand to another routine and the types " & - "of the generic paramers can be inferred from the expected signature.", - errProcHasNoConcreteType: "'$1' doesn't have a concrete type, due to unspecified generic parameters.", - errCompilerDoesntSupportTarget: "The current compiler \'$1\' doesn't support the requested compilation target", - errInOutFlagNotExtern: "The `$1` modifier can be used only with imported types", - errUser: "$1", - warnCannotOpenFile: "cannot open \'$1\'", - warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored", - warnXIsNeverRead: "\'$1\' is never read", - warnXmightNotBeenInit: "\'$1\' might not have been initialized", - warnDeprecated: "$1 is deprecated", - warnConfigDeprecated: "config file '$1' is deprecated", - warnSmallLshouldNotBeUsed: "\'l\' should not be used as an identifier; may look like \'1\' (one)", - warnUnknownMagic: "unknown magic \'$1\' might crash the compiler", - warnRedefinitionOfLabel: "redefinition of label \'$1\'", - warnUnknownSubstitutionX: "unknown substitution \'$1\'", - warnLanguageXNotSupported: "language \'$1\' not supported", - warnFieldXNotSupported: "field \'$1\' not supported", - warnCommentXIgnored: "comment \'$1\' ignored", - warnNilStatement: "'nil' statement is deprecated; use an empty 'discard' statement instead", - warnTypelessParam: "'$1' has no type. Typeless parameters are deprecated; only allowed for 'template'", - warnUseBase: "use {.base.} for base methods; baseless methods are deprecated", - warnWriteToForeignHeap: "write to foreign heap", - warnUnsafeCode: "unsafe code: '$1'", - warnEachIdentIsTuple: "each identifier is a tuple", - warnShadowIdent: "shadowed identifier: '$1'", - warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future.", - warnProveField: "cannot prove that field '$1' is accessible", - warnProveIndex: "cannot prove index '$1' is valid", - warnGcUnsafe: "not GC-safe: '$1'", - warnGcUnsafe2: "$1", - warnUninit: "'$1' might not have been initialized", - warnGcMem: "'$1' uses GC'ed memory", - warnDestructor: "usage of a type with a destructor in a non destructible context. This will become a compile time error in the future.", - warnLockLevel: "$1", - warnResultShadowed: "Special variable 'result' is shadowed.", - warnUser: "$1", - hintSuccess: "operation successful", - hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#; $#)", - hintLineTooLong: "line too long", - hintXDeclaredButNotUsed: "\'$1\' is declared but not used", - hintConvToBaseNotNeeded: "conversion to base object is not needed", - hintConvFromXtoItselfNotNeeded: "conversion from $1 to itself is pointless", - hintExprAlwaysX: "expression evaluates always to \'$1\'", - hintQuitCalled: "quit() called", - hintProcessing: "$1", - hintCodeBegin: "generated code listing:", - hintCodeEnd: "end of listing", - hintConf: "used config file \'$1\'", - hintPath: "added path: '$1'", - hintConditionAlwaysTrue: "condition is always true: '$1'", - hintName: "name should be: '$1'", - hintPattern: "$1", - hintExecuting: "$1", - hintLinking: "", - hintDependency: "$1", - hintSource: "$1", - hintStackTrace: "$1", - hintGCStats: "$1", - hintUser: "$1", - hintUserRaw: "$1"] - -const - WarningsToStr* = ["CannotOpenFile", "OctalEscape", - "XIsNeverRead", "XmightNotBeenInit", - "Deprecated", "ConfigDeprecated", - "SmallLshouldNotBeUsed", "UnknownMagic", - "RedefinitionOfLabel", "UnknownSubstitutionX", - "LanguageXNotSupported", "FieldXNotSupported", - "CommentXIgnored", "NilStmt", - "TypelessParam", "UseBase", "WriteToForeignHeap", - "UnsafeCode", "EachIdentIsTuple", "ShadowIdent", - "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit", - "GcMem", "Destructor", "LockLevel", "ResultShadowed", "User"] - - HintsToStr* = ["Success", "SuccessX", "LineTooLong", - "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", - "ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf", - "Path", "CondTrue", "Name", "Pattern", "Exec", "Link", "Dependency", - "Source", "StackTrace", "GCStats", - "User", "UserRaw"] - -const - fatalMin* = errUnknown - fatalMax* = errInternal - errMin* = errUnknown - errMax* = errUser - warnMin* = warnCannotOpenFile - warnMax* = pred(hintSuccess) - hintMin* = hintSuccess - hintMax* = high(TMsgKind) - -type - TNoteKind* = range[warnMin..hintMax] # "notes" are warnings or hints - TNoteKinds* = set[TNoteKind] - - TFileInfo* = object - fullPath: string # This is a canonical full filesystem path - projPath*: string # This is relative to the project's root - shortName*: string # short name of the module - quotedName*: Rope # cached quoted short name for codegen - # purposes - quotedFullName*: Rope # cached quoted full name for codegen - # purposes - - lines*: seq[Rope] # the source code of the module - # used for better error messages and - # embedding the original source in the - # generated code - dirtyfile: string # the file that is actually read into memory - # and parsed; usually 'nil' but is used - # for 'nimsuggest' - hash*: string # the checksum of the file - - TLineInfo* = object # This is designed to be as small as possible, - # because it is used - # in syntax nodes. We save space here by using - # two int16 and an int32. - # On 64 bit and on 32 bit systems this is - # only 8 bytes. - line*, col*: int16 - fileIndex*: int32 - when defined(nimpretty): - offsetA*, offsetB*: int - commentOffsetA*, commentOffsetB*: int - - TErrorOutput* = enum - eStdOut - eStdErr - - TErrorOutputs* = set[TErrorOutput] - - ERecoverableError* = object of ValueError - ESuggestDone* = object of Exception - -const - NotesVerbosity*: array[0..3, TNoteKinds] = [ - {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent, warnUninit, - warnProveField, warnProveIndex, - warnGcUnsafe, - hintSuccessX, hintPath, hintConf, - hintProcessing, hintPattern, - hintDependency, - hintExecuting, hintLinking, - hintCodeBegin, hintCodeEnd, - hintSource, hintStackTrace, - hintGCStats}, - {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent, warnUninit, - warnProveField, warnProveIndex, - warnGcUnsafe, - hintPath, - hintDependency, - hintCodeBegin, hintCodeEnd, - hintSource, hintStackTrace, - hintGCStats}, - {low(TNoteKind)..high(TNoteKind)} - {hintStackTrace, warnUninit}, - {low(TNoteKind)..high(TNoteKind)}] - -const - InvalidFileIDX* = int32(-1) - -var - ForeignPackageNotes*: TNoteKinds = {hintProcessing, warnUnknownMagic, - hintQuitCalled, hintExecuting} - filenameToIndexTbl = initTable[string, int32]() - fileInfos*: seq[TFileInfo] = @[] - systemFileIdx*: int32 - -proc toCChar*(c: char): string = +proc toCChar*(c: char; result: var string) = case c - of '\0'..'\x1F', '\x7F'..'\xFF': result = '\\' & toOctal(c) - of '\'', '\"', '\\', '?': result = '\\' & c - else: result = $(c) + of '\0'..'\x1F', '\x7F'..'\xFF': + result.add '\\' + result.add toOctal(c) + of '\'', '\"', '\\', '?': + result.add '\\' + result.add c + else: + result.add c proc makeCString*(s: string): Rope = - const - MaxLineLength = 64 + const MaxLineLength = 64 result = nil var res = newStringOfCap(int(s.len.toFloat * 1.1) + 1) add(res, "\"") for i in countup(0, len(s) - 1): if (i + 1) mod MaxLineLength == 0: - add(res, '\"') - add(res, tnl) - add(res, '\"') - add(res, toCChar(s[i])) + add(res, "\"\L\"") + toCChar(s[i], res) add(res, '\"') add(result, rope(res)) @@ -578,25 +44,36 @@ proc newFileInfo(fullPath, projPath: string): TFileInfo = result.shortName = fileName.changeFileExt("") result.quotedName = fileName.makeCString result.quotedFullName = fullPath.makeCString - if optEmbedOrigSrc in gGlobalOptions or true: - result.lines = @[] - -proc fileInfoKnown*(filename: string): bool = + result.lines = @[] + when defined(nimpretty): + if result.fullPath.len > 0: + try: + result.fullContent = readFile(result.fullPath) + except IOError: + #rawMessage(errCannotOpenFile, result.fullPath) + # XXX fixme + result.fullContent = "" + +when defined(nimpretty): + proc fileSection*(conf: ConfigRef; fid: FileIndex; a, b: int): string = + substr(conf.m.fileInfos[fid.int].fullContent, a, b) + +proc fileInfoKnown*(conf: ConfigRef; filename: string): bool = var canon: string try: - canon = canonicalizePath(filename) + canon = canonicalizePath(conf, filename) except: canon = filename - result = filenameToIndexTbl.hasKey(canon) + result = conf.m.filenameToIndexTbl.hasKey(canon) -proc fileInfoIdx*(filename: string; isKnownFile: var bool): int32 = +proc fileInfoIdx*(conf: ConfigRef; filename: string; isKnownFile: var bool): FileIndex = var canon: string pseudoPath = false try: - canon = canonicalizePath(filename) + canon = canonicalizePath(conf, filename) shallow(canon) except: canon = filename @@ -604,62 +81,27 @@ proc fileInfoIdx*(filename: string; isKnownFile: var bool): int32 = # This flag indicates that we are working with such a path here pseudoPath = true - if filenameToIndexTbl.hasKey(canon): - result = filenameToIndexTbl[canon] + if conf.m.filenameToIndexTbl.hasKey(canon): + result = conf.m.filenameToIndexTbl[canon] else: isKnownFile = false - result = fileInfos.len.int32 - fileInfos.add(newFileInfo(canon, if pseudoPath: filename - else: canon.shortenDir)) - filenameToIndexTbl[canon] = result + result = conf.m.fileInfos.len.FileIndex + conf.m.fileInfos.add(newFileInfo(canon, if pseudoPath: filename + else: shortenDir(conf, canon))) + conf.m.filenameToIndexTbl[canon] = result -proc fileInfoIdx*(filename: string): int32 = +proc fileInfoIdx*(conf: ConfigRef; filename: string): FileIndex = var dummy: bool - result = fileInfoIdx(filename, dummy) + result = fileInfoIdx(conf, filename, dummy) -proc newLineInfo*(fileInfoIdx: int32, line, col: int): TLineInfo = +proc newLineInfo*(fileInfoIdx: FileIndex, line, col: int): TLineInfo = result.fileIndex = fileInfoIdx - result.line = int16(line) + result.line = uint16(line) result.col = int16(col) -proc newLineInfo*(filename: string, line, col: int): TLineInfo {.inline.} = - result = newLineInfo(filename.fileInfoIdx, line, col) - -fileInfos.add(newFileInfo("", "command line")) -var gCmdLineInfo* = newLineInfo(int32(0), 1, 1) - -fileInfos.add(newFileInfo("", "compilation artifact")) -var gCodegenLineInfo* = newLineInfo(int32(1), 1, 1) - -proc raiseRecoverableError*(msg: string) {.noinline, noreturn.} = - raise newException(ERecoverableError, msg) - -proc sourceLine*(i: TLineInfo): Rope - -var - gNotes*: TNoteKinds = NotesVerbosity[1] # defaults to verbosity of 1 - gErrorCounter*: int = 0 # counts the number of errors - gHintCounter*: int = 0 - gWarnCounter*: int = 0 - gErrorMax*: int = 1 # stop after gErrorMax errors - gMainPackageNotes*: TNoteKinds = NotesVerbosity[1] - -proc unknownLineInfo*(): TLineInfo = - result.line = int16(-1) - result.col = int16(-1) - result.fileIndex = -1 +proc newLineInfo*(conf: ConfigRef; filename: string, line, col: int): TLineInfo {.inline.} = + result = newLineInfo(fileInfoIdx(conf, filename), line, col) -type - Severity* {.pure.} = enum ## VS Code only supports these three - Hint, Warning, Error - -var - msgContext: seq[TLineInfo] = @[] - lastError = unknownLineInfo() - - errorOutputs* = {eStdOut, eStdErr} - writelnHook*: proc (output: string) {.closure.} - structuredErrorHook*: proc (info: TLineInfo; msg: string; severity: Severity) {.closure.} proc concat(strings: openarray[string]): string = var totalLen = 0 @@ -667,13 +109,13 @@ proc concat(strings: openarray[string]): string = result = newStringOfCap totalLen for s in strings: result.add s -proc suggestWriteln*(s: string) = - if eStdOut in errorOutputs: - if isNil(writelnHook): +proc suggestWriteln*(conf: ConfigRef; s: string) = + if eStdOut in conf.m.errorOutputs: + if isNil(conf.writelnHook): writeLine(stdout, s) flushFile(stdout) else: - writelnHook(s) + conf.writelnHook(s) proc msgQuit*(x: int8) = quit x proc msgQuit*(x: string) = quit x @@ -694,85 +136,81 @@ const HintTitle = "Hint: " HintColor = fgGreen -proc getInfoContextLen*(): int = return msgContext.len -proc setInfoContextLen*(L: int) = setLen(msgContext, L) +proc getInfoContextLen*(conf: ConfigRef): int = return conf.m.msgContext.len +proc setInfoContextLen*(conf: ConfigRef; L: int) = setLen(conf.m.msgContext, L) -proc pushInfoContext*(info: TLineInfo) = - msgContext.add(info) +proc pushInfoContext*(conf: ConfigRef; info: TLineInfo) = + conf.m.msgContext.add(info) -proc popInfoContext*() = - setLen(msgContext, len(msgContext) - 1) +proc popInfoContext*(conf: ConfigRef) = + setLen(conf.m.msgContext, len(conf.m.msgContext) - 1) -proc getInfoContext*(index: int): TLineInfo = - let L = msgContext.len +proc getInfoContext*(conf: ConfigRef; index: int): TLineInfo = + let L = conf.m.msgContext.len let i = if index < 0: L + index else: index if i >=% L: result = unknownLineInfo() - else: result = msgContext[i] + else: result = conf.m.msgContext[i] -template toFilename*(fileIdx: int32): string = - (if fileIdx < 0: "???" else: fileInfos[fileIdx].projPath) +template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string = + (if fileIdx.int32 < 0 or conf == nil: "???" else: conf.m.fileInfos[fileIdx.int32].projPath) -proc toFullPath*(fileIdx: int32): string = - if fileIdx < 0: result = "???" - else: result = fileInfos[fileIdx].fullPath +proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string = + if fileIdx.int32 < 0 or conf == nil: result = "???" + else: result = conf.m.fileInfos[fileIdx.int32].fullPath -proc setDirtyFile*(fileIdx: int32; filename: string) = - assert fileIdx >= 0 - fileInfos[fileIdx].dirtyFile = filename +proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: string) = + assert fileIdx.int32 >= 0 + conf.m.fileInfos[fileIdx.int32].dirtyFile = filename -proc setHash*(fileIdx: int32; hash: string) = - assert fileIdx >= 0 - shallowCopy(fileInfos[fileIdx].hash, hash) +proc setHash*(conf: ConfigRef; fileIdx: FileIndex; hash: string) = + assert fileIdx.int32 >= 0 + shallowCopy(conf.m.fileInfos[fileIdx.int32].hash, hash) -proc getHash*(fileIdx: int32): string = - assert fileIdx >= 0 - shallowCopy(result, fileInfos[fileIdx].hash) +proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string = + assert fileIdx.int32 >= 0 + shallowCopy(result, conf.m.fileInfos[fileIdx.int32].hash) -proc toFullPathConsiderDirty*(fileIdx: int32): string = - if fileIdx < 0: +proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): string = + if fileIdx.int32 < 0: result = "???" - elif not fileInfos[fileIdx].dirtyFile.isNil: - result = fileInfos[fileIdx].dirtyFile + elif conf.m.fileInfos[fileIdx.int32].dirtyFile.len > 0: + result = conf.m.fileInfos[fileIdx.int32].dirtyFile else: - result = fileInfos[fileIdx].fullPath + result = conf.m.fileInfos[fileIdx.int32].fullPath -template toFilename*(info: TLineInfo): string = - info.fileIndex.toFilename +template toFilename*(conf: ConfigRef; info: TLineInfo): string = + toFilename(conf, info.fileIndex) -template toFullPath*(info: TLineInfo): string = - info.fileIndex.toFullPath +template toFullPath*(conf: ConfigRef; info: TLineInfo): string = + toFullPath(conf, info.fileIndex) -proc toMsgFilename*(info: TLineInfo): string = - if info.fileIndex < 0: +proc toMsgFilename*(conf: ConfigRef; info: TLineInfo): string = + if info.fileIndex.int32 < 0: result = "???" - elif gListFullPaths: - result = fileInfos[info.fileIndex].fullPath + elif optListFullPaths in conf.globalOptions: + result = conf.m.fileInfos[info.fileIndex.int32].fullPath else: - result = fileInfos[info.fileIndex].projPath + result = conf.m.fileInfos[info.fileIndex.int32].projPath proc toLinenumber*(info: TLineInfo): int {.inline.} = - result = info.line + result = int info.line proc toColumn*(info: TLineInfo): int {.inline.} = result = info.col -proc toFileLine*(info: TLineInfo): string {.inline.} = - result = info.toFilename & ":" & $info.line +proc toFileLine*(conf: ConfigRef; info: TLineInfo): string {.inline.} = + result = toFilename(conf, info) & ":" & $info.line -proc toFileLineCol*(info: TLineInfo): string {.inline.} = - result = info.toFilename & "(" & $info.line & ", " & $info.col & ")" +proc toFileLineCol*(conf: ConfigRef; info: TLineInfo): string {.inline.} = + result = toFilename(conf, info) & "(" & $info.line & ", " & $info.col & ")" -proc `$`*(info: TLineInfo): string = toFileLineCol(info) +proc `$`*(conf: ConfigRef; info: TLineInfo): string = toFileLineCol(conf, info) -proc `??`* (info: TLineInfo, filename: string): bool = - # only for debugging purposes - result = filename in info.toFilename +proc `$`*(info: TLineInfo): string {.error.} = discard -const trackPosInvalidFileIdx* = -2 # special marker so that no suggestions - # are produced within comments and string literals -var gTrackPos*: TLineInfo -var gTrackPosAttached*: bool ## whether the tracking position was attached to some - ## close token. +proc `??`* (conf: ConfigRef; info: TLineInfo, filename: string): bool = + # only for debugging purposes + result = filename in toFilename(conf, info) type MsgFlag* = enum ## flags altering msgWriteln behavior @@ -780,7 +218,7 @@ type msgSkipHook ## skip message hook even if it is present MsgFlags* = set[MsgFlag] -proc msgWriteln*(s: string, flags: MsgFlags = {}) = +proc msgWriteln*(conf: ConfigRef; s: string, flags: MsgFlags = {}) = ## Writes given message string to stderr by default. ## If ``--stdout`` option is given, writes to stdout instead. If message hook ## is present, then it is used to output message rather than stderr/stdout. @@ -788,16 +226,16 @@ proc msgWriteln*(s: string, flags: MsgFlags = {}) = ## This is used for 'nim dump' etc. where we don't have nimsuggest ## support. - #if gCmd == cmdIdeTools and optCDebug notin gGlobalOptions: return + #if conf.cmd == cmdIdeTools and optCDebug notin gGlobalOptions: return - if not isNil(writelnHook) and msgSkipHook notin flags: - writelnHook(s) - elif optStdout in gGlobalOptions or msgStdout in flags: - if eStdOut in errorOutputs: + if not isNil(conf.writelnHook) and msgSkipHook notin flags: + conf.writelnHook(s) + elif optStdout in conf.globalOptions or msgStdout in flags: + if eStdOut in conf.m.errorOutputs: writeLine(stdout, s) flushFile(stdout) else: - if eStdErr in errorOutputs: + if eStdErr in conf.m.errorOutputs: writeLine(stderr, s) # On Windows stderr is fully-buffered when piped, regardless of C std. when defined(windows): @@ -828,18 +266,18 @@ macro callStyledWriteLineStderr(args: varargs[typed]): untyped = result.add(arg) template callWritelnHook(args: varargs[string, `$`]) = - writelnHook concat(args) + conf.writelnHook concat(args) template styledMsgWriteln*(args: varargs[typed]) = - if not isNil(writelnHook): + if not isNil(conf.writelnHook): callIgnoringStyle(callWritelnHook, nil, args) - elif optStdout in gGlobalOptions: - if eStdOut in errorOutputs: + elif optStdout in conf.globalOptions: + if eStdOut in conf.m.errorOutputs: callIgnoringStyle(writeLine, stdout, args) flushFile(stdout) else: - if eStdErr in errorOutputs: - if optUseColors in gGlobalOptions: + if eStdErr in conf.m.errorOutputs: + if optUseColors in conf.globalOptions: callStyledWriteLineStderr(args) else: callIgnoringStyle(writeLine, stderr, args) @@ -867,27 +305,27 @@ proc log*(s: string) {.procvar.} = f.writeLine(s) close(f) -proc quit(msg: TMsgKind) = - if defined(debug) or msg == errInternal or hintStackTrace in gNotes: - if stackTraceAvailable() and isNil(writelnHook): +proc quit(conf: ConfigRef; msg: TMsgKind) = + if defined(debug) or msg == errInternal or hintStackTrace in conf.notes: + if stackTraceAvailable() and isNil(conf.writelnHook): writeStackTrace() else: styledMsgWriteln(fgRed, "No stack traceback available\n" & "To create a stacktrace, rerun compilation with ./koch temp " & - options.command & " <file>") + conf.command & " <file>") quit 1 -proc handleError(msg: TMsgKind, eh: TErrorHandling, s: string) = +proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string) = if msg >= fatalMin and msg <= fatalMax: - if gCmd == cmdIdeTools: log(s) - quit(msg) + if conf.cmd == cmdIdeTools: log(s) + quit(conf, msg) if msg >= errMin and msg <= errMax: - inc(gErrorCounter) - options.gExitcode = 1'i8 - if gErrorCounter >= gErrorMax: - quit(msg) - elif eh == doAbort and gCmd != cmdIdeTools: - quit(msg) + inc(conf.errorCounter) + conf.exitcode = 1'i8 + if conf.errorCounter >= conf.errorMax: + quit(conf, msg) + elif eh == doAbort and conf.cmd != cmdIdeTools: + quit(conf, msg) elif eh == doRaise: raiseRecoverableError(s) @@ -897,26 +335,27 @@ proc `==`*(a, b: TLineInfo): bool = proc exactEquals*(a, b: TLineInfo): bool = result = a.fileIndex == b.fileIndex and a.line == b.line and a.col == b.col -proc writeContext(lastinfo: TLineInfo) = +proc writeContext(conf: ConfigRef; lastinfo: TLineInfo) = + const instantiationFrom = "template/generic instantiation from here" var info = lastinfo - for i in countup(0, len(msgContext) - 1): - if msgContext[i] != lastinfo and msgContext[i] != info: - if structuredErrorHook != nil: - structuredErrorHook(msgContext[i], getMessageStr(errInstantiationFrom, ""), - Severity.Error) + for i in 0 ..< len(conf.m.msgContext): + if conf.m.msgContext[i] != lastinfo and conf.m.msgContext[i] != info: + if conf.structuredErrorHook != nil: + conf.structuredErrorHook(conf, conf.m.msgContext[i], instantiationFrom, + Severity.Error) else: styledMsgWriteln(styleBright, - PosFormat % [toMsgFilename(msgContext[i]), - coordToStr(msgContext[i].line), - coordToStr(msgContext[i].col+1)], + PosFormat % [toMsgFilename(conf, conf.m.msgContext[i]), + coordToStr(conf.m.msgContext[i].line.int), + coordToStr(conf.m.msgContext[i].col+1)], resetStyle, - getMessageStr(errInstantiationFrom, "")) - info = msgContext[i] + instantiationFrom) + info = conf.m.msgContext[i] -proc ignoreMsgBecauseOfIdeTools(msg: TMsgKind): bool = - msg >= errGenerated and gCmd == cmdIdeTools and optIdeDebug notin gGlobalOptions +proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool = + msg >= errGenerated and conf.cmd == cmdIdeTools and optIdeDebug notin conf.globalOptions -proc rawMessage*(msg: TMsgKind, args: openArray[string]) = +proc rawMessage*(conf: ConfigRef; msg: TMsgKind, args: openArray[string]) = var title: string color: ForegroundColor @@ -925,62 +364,81 @@ proc rawMessage*(msg: TMsgKind, args: openArray[string]) = case msg of errMin..errMax: sev = Severity.Error - writeContext(unknownLineInfo()) + writeContext(conf, unknownLineInfo()) title = ErrorTitle color = ErrorColor of warnMin..warnMax: sev = Severity.Warning - if optWarns notin gOptions: return - if msg notin gNotes: return - writeContext(unknownLineInfo()) + if optWarns notin conf.options: return + if msg notin conf.notes: return + writeContext(conf, unknownLineInfo()) title = WarningTitle color = WarningColor kind = WarningsToStr[ord(msg) - ord(warnMin)] - inc(gWarnCounter) + inc(conf.warnCounter) of hintMin..hintMax: sev = Severity.Hint - if optHints notin gOptions: return - if msg notin gNotes: return + if optHints notin conf.options: return + if msg notin conf.notes: return title = HintTitle color = HintColor if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)] - inc(gHintCounter) + inc(conf.hintCounter) let s = msgKindToString(msg) % args - if structuredErrorHook != nil: - structuredErrorHook(unknownLineInfo(), s & (if kind != nil: KindFormat % kind else: ""), sev) + if conf.structuredErrorHook != nil: + conf.structuredErrorHook(conf, unknownLineInfo(), + s & (if kind.len > 0: KindFormat % kind else: ""), sev) - if not ignoreMsgBecauseOfIdeTools(msg): - if kind != nil: + if not ignoreMsgBecauseOfIdeTools(conf, msg): + if kind.len > 0: styledMsgWriteln(color, title, resetStyle, s, KindColor, `%`(KindFormat, kind)) else: styledMsgWriteln(color, title, resetStyle, s) - handleError(msg, doAbort, s) + handleError(conf, msg, doAbort, s) -proc rawMessage*(msg: TMsgKind, arg: string) = - rawMessage(msg, [arg]) +proc rawMessage*(conf: ConfigRef; msg: TMsgKind, arg: string) = + rawMessage(conf, msg, [arg]) -proc resetAttributes* = - if {optUseColors, optStdout} * gGlobalOptions == {optUseColors}: +proc resetAttributes*(conf: ConfigRef) = + if {optUseColors, optStdout} * conf.globalOptions == {optUseColors}: terminal.resetAttributes(stderr) -proc writeSurroundingSrc(info: TLineInfo) = +proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) = + conf.m.fileInfos[fileIdx.int32].lines.add line + +proc sourceLine*(conf: ConfigRef; i: TLineInfo): string = + if i.fileIndex.int32 < 0: return "" + + if not optPreserveOrigSource(conf) and conf.m.fileInfos[i.fileIndex.int32].lines.len == 0: + try: + for line in lines(toFullPath(conf, i)): + addSourceLine conf, i.fileIndex, line.string + except IOError: + discard + assert i.fileIndex.int32 < conf.m.fileInfos.len + # can happen if the error points to EOF: + if i.line.int > conf.m.fileInfos[i.fileIndex.int32].lines.len: return "" + + result = conf.m.fileInfos[i.fileIndex.int32].lines[i.line.int-1] + +proc writeSurroundingSrc(conf: ConfigRef; info: TLineInfo) = const indent = " " - msgWriteln(indent & $info.sourceLine) - msgWriteln(indent & spaces(info.col) & '^') + msgWriteln(conf, indent & $sourceLine(conf, info)) + msgWriteln(conf, indent & spaces(info.col) & '^') -proc formatMsg*(info: TLineInfo, msg: TMsgKind, arg: string): string = +proc formatMsg*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string): string = let title = case msg of warnMin..warnMax: WarningTitle of hintMin..hintMax: HintTitle else: ErrorTitle - result = PosFormat % [toMsgFilename(info), coordToStr(info.line), + result = PosFormat % [toMsgFilename(conf, info), coordToStr(info.line.int), coordToStr(info.col+1)] & title & getMessageStr(msg, arg) -proc liMessage(info: TLineInfo, msg: TMsgKind, arg: string, +proc liMessage(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string, eh: TErrorHandling) = var title: string @@ -991,137 +449,108 @@ proc liMessage(info: TLineInfo, msg: TMsgKind, arg: string, case msg of errMin..errMax: sev = Severity.Error - writeContext(info) + writeContext(conf, info) title = ErrorTitle color = ErrorColor # we try to filter error messages so that not two error message # in the same file and line are produced: #ignoreMsg = lastError == info and eh != doAbort - lastError = info + conf.m.lastError = info of warnMin..warnMax: sev = Severity.Warning - ignoreMsg = optWarns notin gOptions or msg notin gNotes - if not ignoreMsg: writeContext(info) + ignoreMsg = optWarns notin conf.options or msg notin conf.notes + if not ignoreMsg: writeContext(conf, info) title = WarningTitle color = WarningColor kind = WarningsToStr[ord(msg) - ord(warnMin)] - inc(gWarnCounter) + inc(conf.warnCounter) of hintMin..hintMax: sev = Severity.Hint - ignoreMsg = optHints notin gOptions or msg notin gNotes + ignoreMsg = optHints notin conf.options or msg notin conf.notes title = HintTitle color = HintColor if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)] - inc(gHintCounter) + inc(conf.hintCounter) # NOTE: currently line info line numbers start with 1, # but column numbers start with 0, however most editors expect # first column to be 1, so we need to +1 here - let x = PosFormat % [toMsgFilename(info), coordToStr(info.line), + let x = PosFormat % [toMsgFilename(conf, info), coordToStr(info.line.int), coordToStr(info.col+1)] let s = getMessageStr(msg, arg) if not ignoreMsg: - if structuredErrorHook != nil: - structuredErrorHook(info, s & (if kind != nil: KindFormat % kind else: ""), sev) - if not ignoreMsgBecauseOfIdeTools(msg): - if kind != nil: + if conf.structuredErrorHook != nil: + conf.structuredErrorHook(conf, info, s & (if kind.len > 0: KindFormat % kind else: ""), sev) + if not ignoreMsgBecauseOfIdeTools(conf, msg): + if kind.len > 0: styledMsgWriteln(styleBright, x, resetStyle, color, title, resetStyle, s, KindColor, `%`(KindFormat, kind)) else: styledMsgWriteln(styleBright, x, resetStyle, color, title, resetStyle, s) - if msg in errMin..errMax and hintSource in gNotes: - info.writeSurroundingSrc - handleError(msg, eh, s) + if hintSource in conf.notes: + conf.writeSurroundingSrc(info) + handleError(conf, msg, eh, s) -proc fatal*(info: TLineInfo, msg: TMsgKind, arg = "") = +proc fatal*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") = # this fixes bug #7080 so that it is at least obvious 'fatal' # was executed. - errorOutputs = {eStdOut, eStdErr} - liMessage(info, msg, arg, doAbort) + conf.m.errorOutputs = {eStdOut, eStdErr} + liMessage(conf, info, msg, arg, doAbort) -proc globalError*(info: TLineInfo, msg: TMsgKind, arg = "") = - liMessage(info, msg, arg, doRaise) +proc globalError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") = + liMessage(conf, info, msg, arg, doRaise) -proc globalError*(info: TLineInfo, arg: string) = - liMessage(info, errGenerated, arg, doRaise) +proc globalError*(conf: ConfigRef; info: TLineInfo, arg: string) = + liMessage(conf, info, errGenerated, arg, doRaise) -proc localError*(info: TLineInfo, msg: TMsgKind, arg = "") = - liMessage(info, msg, arg, doNothing) +proc localError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") = + liMessage(conf, info, msg, arg, doNothing) -proc localError*(info: TLineInfo, arg: string) = - liMessage(info, errGenerated, arg, doNothing) +proc localError*(conf: ConfigRef; info: TLineInfo, arg: string) = + liMessage(conf, info, errGenerated, arg, doNothing) -proc localError*(info: TLineInfo, format: string, params: openarray[string]) = - localError(info, format % params) +proc localError*(conf: ConfigRef; info: TLineInfo, format: string, params: openarray[string]) = + localError(conf, info, format % params) -proc message*(info: TLineInfo, msg: TMsgKind, arg = "") = - liMessage(info, msg, arg, doNothing) +proc message*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") = + liMessage(conf, info, msg, arg, doNothing) -proc internalError*(info: TLineInfo, errMsg: string) = - if gCmd == cmdIdeTools and structuredErrorHook.isNil: return - writeContext(info) - liMessage(info, errInternal, errMsg, doAbort) +proc internalError*(conf: ConfigRef; info: TLineInfo, errMsg: string) = + if conf.cmd == cmdIdeTools and conf.structuredErrorHook.isNil: return + writeContext(conf, info) + liMessage(conf, info, errInternal, errMsg, doAbort) -proc internalError*(errMsg: string) = - if gCmd == cmdIdeTools and structuredErrorHook.isNil: return - writeContext(unknownLineInfo()) - rawMessage(errInternal, errMsg) +proc internalError*(conf: ConfigRef; errMsg: string) = + if conf.cmd == cmdIdeTools and conf.structuredErrorHook.isNil: return + writeContext(conf, unknownLineInfo()) + rawMessage(conf, errInternal, errMsg) -template assertNotNil*(e): untyped = - if e == nil: internalError($instantiationInfo()) +template assertNotNil*(conf: ConfigRef; e): untyped = + if e == nil: internalError(conf, $instantiationInfo()) e -template internalAssert*(e: bool) = - if not e: internalError($instantiationInfo()) - -proc addSourceLine*(fileIdx: int32, line: string) = - fileInfos[fileIdx].lines.add line.rope - -proc sourceLine*(i: TLineInfo): Rope = - if i.fileIndex < 0: return nil +template internalAssert*(conf: ConfigRef, e: bool) = + if not e: internalError(conf, $instantiationInfo()) - if not optPreserveOrigSource and fileInfos[i.fileIndex].lines.len == 0: - try: - for line in lines(i.toFullPath): - addSourceLine i.fileIndex, line.string - except IOError: - discard - internalAssert i.fileIndex < fileInfos.len - # can happen if the error points to EOF: - if i.line > fileInfos[i.fileIndex].lines.len: return nil - - result = fileInfos[i.fileIndex].lines[i.line-1] - -proc quotedFilename*(i: TLineInfo): Rope = - internalAssert i.fileIndex >= 0 - if optExcessiveStackTrace in gGlobalOptions: - result = fileInfos[i.fileIndex].quotedFullName +proc quotedFilename*(conf: ConfigRef; i: TLineInfo): Rope = + assert i.fileIndex.int32 >= 0 + if optExcessiveStackTrace in conf.globalOptions: + result = conf.m.fileInfos[i.fileIndex.int32].quotedFullName else: - result = fileInfos[i.fileIndex].quotedName + result = conf.m.fileInfos[i.fileIndex.int32].quotedName -ropes.errorHandler = proc (err: RopesError, msg: string, useWarning: bool) = - case err - of rInvalidFormatStr: - internalError("ropes: invalid format string: " & msg) - of rCannotOpenFile: - rawMessage(if useWarning: warnCannotOpenFile else: errCannotOpenFile, msg) - -proc listWarnings*() = - msgWriteln("Warnings:") +proc listWarnings*(conf: ConfigRef) = + msgWriteln(conf, "Warnings:") for warn in warnMin..warnMax: - msgWriteln(" [$1] $2" % [ - if warn in gNotes: "x" else: " ", - msgs.WarningsToStr[ord(warn) - ord(warnMin)] + msgWriteln(conf, " [$1] $2" % [ + if warn in conf.notes: "x" else: " ", + lineinfos.WarningsToStr[ord(warn) - ord(warnMin)] ]) -proc listHints*() = - msgWriteln("Hints:") +proc listHints*(conf: ConfigRef) = + msgWriteln(conf, "Hints:") for hint in hintMin..hintMax: - msgWriteln(" [$1] $2" % [ - if hint in gNotes: "x" else: " ", - msgs.HintsToStr[ord(hint) - ord(hintMin)] + msgWriteln(conf, " [$1] $2" % [ + if hint in conf.notes: "x" else: " ", + lineinfos.HintsToStr[ord(hint) - ord(hintMin)] ]) - -# enable colors by default on terminals -if terminal.isatty(stderr): - incl(gGlobalOptions, optUseColors) diff --git a/compiler/ndi.nim b/compiler/ndi.nim index a7ca02193..9708c388d 100644 --- a/compiler/ndi.nim +++ b/compiler/ndi.nim @@ -10,7 +10,7 @@ ## This module implements the generation of ``.ndi`` files for better debugging ## support of Nim code. "ndi" stands for "Nim debug info". -import ast, msgs, ropes +import ast, msgs, ropes, options type NdiFile* = object @@ -18,19 +18,19 @@ type f: File buf: string -proc doWrite(f: var NdiFile; s: PSym) = +proc doWrite(f: var NdiFile; s: PSym; conf: ConfigRef) = f.buf.setLen 0 f.buf.add s.info.line.int f.buf.add "\t" f.buf.add s.info.col.int f.f.write(s.name.s, "\t") f.f.writeRope(s.loc.r) - f.f.writeLine("\t", s.info.toFullPath, "\t", f.buf) + f.f.writeLine("\t", toFullPath(conf, s.info), "\t", f.buf) -template writeMangledName*(f: NdiFile; s: PSym) = - if f.enabled: doWrite(f, s) +template writeMangledName*(f: NdiFile; s: PSym; conf: ConfigRef) = + if f.enabled: doWrite(f, s, conf) -proc open*(f: var NdiFile; filename: string) = +proc open*(f: var NdiFile; filename: string; conf: ConfigRef) = f.enabled = filename.len > 0 if f.enabled: f.f = open(filename, fmWrite, 8000) diff --git a/compiler/nim.cfg b/compiler/nim.cfg index 853ae7e00..1bd3fbfd6 100644 --- a/compiler/nim.cfg +++ b/compiler/nim.cfg @@ -5,6 +5,8 @@ path:"llvm" path:"$projectPath/.." define:booting +define:nimcore +#define:nimIncremental #import:"$projectpath/testability" @if windows: @@ -13,6 +15,5 @@ define:booting define:useStdoutAsStdmsg -cs:partial #define:useNodeIds #gc:markAndSweep diff --git a/compiler/nim.nim b/compiler/nim.nim index 89225a5e0..0fed72dc7 100644 --- a/compiler/nim.nim +++ b/compiler/nim.nim @@ -20,8 +20,8 @@ when defined(i386) and defined(windows) and defined(vcc): import commands, lexer, condsyms, options, msgs, nversion, nimconf, ropes, - extccomp, strutils, os, osproc, platform, main, parseopt, service, - nodejs, scriptconfig, idents, modulegraphs + extccomp, strutils, os, osproc, platform, main, parseopt, + nodejs, scriptconfig, idents, modulegraphs, lineinfos, cmdlinehelper when hasTinyCBackend: import tccgen @@ -37,77 +37,63 @@ proc prependCurDir(f: string): string = else: result = f -proc handleCmdLine(cache: IdentCache; config: ConfigRef) = +proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) = + var p = parseopt.initOptParser(cmd) + var argsCount = 0 + while true: + parseopt.next(p) + case p.kind + of cmdEnd: break + of cmdLongoption, cmdShortOption: + if p.key == " ": + p.key = "-" + if processArgument(pass, p, argsCount, config): break + else: + processSwitch(pass, p, config) + of cmdArgument: + if processArgument(pass, p, argsCount, config): break + if pass == passCmd2: + if optRun notin config.globalOptions and config.arguments.len > 0 and config.command.normalize != "run": + rawMessage(config, errGenerated, errArgsNeedRunOption) + +proc handleCmdLine(cache: IdentCache; conf: ConfigRef) = + let self = NimProg( + supportsStdinFile: true, + processCmdLine: processCmdLine, + mainCommand: mainCommand + ) + self.initDefinesProg(conf, "nim_compiler") if paramCount() == 0: - writeCommandLineUsage() - else: - # Process command line arguments: - processCmdLine(passCmd1, "") - if gProjectName == "-": - gProjectName = "stdinfile" - gProjectFull = "stdinfile" - gProjectPath = canonicalizePath getCurrentDir() - gProjectIsStdin = true - elif gProjectName != "": - try: - gProjectFull = canonicalizePath(gProjectName) - except OSError: - gProjectFull = gProjectName - let p = splitFile(gProjectFull) - let dir = if p.dir.len > 0: p.dir else: getCurrentDir() - gProjectPath = canonicalizePath dir - gProjectName = p.name + writeCommandLineUsage(conf, conf.helpWritten) + return + + self.processCmdLineAndProjectPath(conf) + if not self.loadConfigsAndRunMainCommand(cache, conf): return + if optHints in conf.options and hintGCStats in conf.notes: echo(GC_getStatistics()) + #echo(GC_getStatistics()) + if conf.errorCounter != 0: return + when hasTinyCBackend: + if conf.cmd == cmdRun: + tccgen.run(conf.arguments) + if optRun in conf.globalOptions: + if conf.cmd == cmdCompileToJS: + var ex: string + if conf.outFile.len > 0: + ex = conf.outFile.prependCurDir.quoteShell + else: + ex = quoteShell( + completeCFilePath(conf, changeFileExt(conf.projectFull, "js").prependCurDir)) + execExternalProgram(conf, findNodeJs() & " " & ex & ' ' & conf.arguments) else: - gProjectPath = canonicalizePath getCurrentDir() - loadConfigs(DefaultConfig, config) # load all config files - let scriptFile = gProjectFull.changeFileExt("nims") - if fileExists(scriptFile): - runNimScript(cache, scriptFile, freshDefines=false, config) - # 'nim foo.nims' means to just run the NimScript file and do nothing more: - if scriptFile == gProjectFull: return - elif fileExists(gProjectPath / "config.nims"): - # directory wide NimScript file - runNimScript(cache, gProjectPath / "config.nims", freshDefines=false, config) - # now process command line arguments again, because some options in the - # command line can overwite the config file's settings - extccomp.initVars() - processCmdLine(passCmd2, "") - if options.command == "": - rawMessage(errNoCommand, command) - mainCommand(newModuleGraph(config), cache) - if optHints in gOptions and hintGCStats in gNotes: echo(GC_getStatistics()) - #echo(GC_getStatistics()) - if msgs.gErrorCounter == 0: - when hasTinyCBackend: - if gCmd == cmdRun: - tccgen.run(commands.arguments) - if optRun in gGlobalOptions: - if gCmd == cmdCompileToJS: - var ex: string - if options.outFile.len > 0: - ex = options.outFile.prependCurDir.quoteShell - else: - ex = quoteShell( - completeCFilePath(changeFileExt(gProjectFull, "js").prependCurDir)) - execExternalProgram(findNodeJs() & " " & ex & ' ' & commands.arguments) - elif gCmd == cmdCompileToPHP: - var ex: string - if options.outFile.len > 0: - ex = options.outFile.prependCurDir.quoteShell - else: - ex = quoteShell( - completeCFilePath(changeFileExt(gProjectFull, "php").prependCurDir)) - execExternalProgram("php " & ex & ' ' & commands.arguments) - else: - var binPath: string - if options.outFile.len > 0: - # If the user specified an outFile path, use that directly. - binPath = options.outFile.prependCurDir - else: - # Figure out ourselves a valid binary name. - binPath = changeFileExt(gProjectFull, ExeExt).prependCurDir - var ex = quoteShell(binPath) - execExternalProgram(ex & ' ' & commands.arguments) + var binPath: string + if conf.outFile.len > 0: + # If the user specified an outFile path, use that directly. + binPath = conf.outFile.prependCurDir + else: + # Figure out ourselves a valid binary name. + binPath = changeFileExt(conf.projectFull, ExeExt).prependCurDir + var ex = quoteShell(binPath) + execExternalProgram(conf, ex & ' ' & conf.arguments) when declared(GC_setMaxPause): GC_setMaxPause 2_000 @@ -115,10 +101,10 @@ when declared(GC_setMaxPause): when compileOption("gc", "v2") or compileOption("gc", "refc"): # the new correct mark&sweet collector is too slow :-/ GC_disableMarkAndSweep() -condsyms.initDefines() when not defined(selftest): - handleCmdLine(newIdentCache(), newConfigRef()) + let conf = newConfigRef() + handleCmdLine(newIdentCache(), conf) when declared(GC_setMaxPause): echo GC_getStatistics() - msgQuit(int8(msgs.gErrorCounter > 0)) + msgQuit(int8(conf.errorCounter > 0)) diff --git a/compiler/nimblecmd.nim b/compiler/nimblecmd.nim index 0f9e03352..c5521735b 100644 --- a/compiler/nimblecmd.nim +++ b/compiler/nimblecmd.nim @@ -9,11 +9,12 @@ ## Implements some helper procs for Nimble (Nim's package manager) support. -import parseutils, strutils, strtabs, os, options, msgs, sequtils +import parseutils, strutils, strtabs, os, options, msgs, sequtils, + lineinfos -proc addPath*(path: string, info: TLineInfo) = - if not options.searchPaths.contains(path): - options.searchPaths.insert(path, 0) +proc addPath*(conf: ConfigRef; path: string, info: TLineInfo) = + if not conf.searchPaths.contains(path): + conf.searchPaths.insert(path, 0) type Version* = distinct string @@ -84,7 +85,7 @@ proc getPathVersion*(p: string): tuple[name, version: string] = result.name = p[0 .. sepIdx - 1] result.version = p.substr(sepIdx + 1) -proc addPackage(packages: StringTableRef, p: string; info: TLineInfo) = +proc addPackage(conf: ConfigRef; packages: StringTableRef, p: string; info: TLineInfo) = let (name, ver) = getPathVersion(p) if isValidVersion(ver): let version = newVersion(ver) @@ -92,14 +93,14 @@ proc addPackage(packages: StringTableRef, p: string; info: TLineInfo) = (not packages.hasKey(name)): packages[name] = $version else: - localError(info, "invalid package name: " & p) + localError(conf, info, "invalid package name: " & p) iterator chosen(packages: StringTableRef): string = for key, val in pairs(packages): let res = if val.len == 0: key else: key & '-' & val yield res -proc addNimblePath(p: string, info: TLineInfo) = +proc addNimblePath(conf: ConfigRef; p: string, info: TLineInfo) = var path = p let nimbleLinks = toSeq(walkPattern(p / "*.nimble-link")) if nimbleLinks.len > 0: @@ -111,23 +112,23 @@ proc addNimblePath(p: string, info: TLineInfo) = if not path.isAbsolute(): path = p / path - if not contains(options.searchPaths, path): - message(info, hintPath, path) - options.lazyPaths.insert(path, 0) + if not contains(conf.searchPaths, path): + message(conf, info, hintPath, path) + conf.lazyPaths.insert(path, 0) -proc addPathRec(dir: string, info: TLineInfo) = +proc addPathRec(conf: ConfigRef; dir: string, info: TLineInfo) = var packages = newStringTable(modeStyleInsensitive) var pos = dir.len-1 if dir[pos] in {DirSep, AltSep}: inc(pos) for k,p in os.walkDir(dir): if k == pcDir and p[pos] != '.': - addPackage(packages, p, info) + addPackage(conf, packages, p, info) for p in packages.chosen: - addNimblePath(p, info) + addNimblePath(conf, p, info) -proc nimblePath*(path: string, info: TLineInfo) = - addPathRec(path, info) - addNimblePath(path, info) +proc nimblePath*(conf: ConfigRef; path: string, info: TLineInfo) = + addPathRec(conf, path, info) + addNimblePath(conf, path, info) when isMainModule: proc v(s: string): Version = s.newVersion @@ -139,18 +140,19 @@ when isMainModule: doAssert v"#aaaqwe" < v"1.1" # We cannot assume that a branch is newer. doAssert v"#a111" < v"#head" + let conf = newConfigRef() var rr = newStringTable() - addPackage rr, "irc-#a111" - addPackage rr, "irc-#head" - addPackage rr, "irc-0.1.0" - addPackage rr, "irc" - addPackage rr, "another" - addPackage rr, "another-0.1" + addPackage conf, rr, "irc-#a111", unknownLineInfo() + addPackage conf, rr, "irc-#head", unknownLineInfo() + addPackage conf, rr, "irc-0.1.0", unknownLineInfo() + #addPackage conf, rr, "irc", unknownLineInfo() + #addPackage conf, rr, "another", unknownLineInfo() + addPackage conf, rr, "another-0.1", unknownLineInfo() - addPackage rr, "ab-0.1.3" - addPackage rr, "ab-0.1" - addPackage rr, "justone" + addPackage conf, rr, "ab-0.1.3", unknownLineInfo() + addPackage conf, rr, "ab-0.1", unknownLineInfo() + addPackage conf, rr, "justone-1.0", unknownLineInfo() doAssert toSeq(rr.chosen) == - @["irc-#head", "another-0.1", "ab-0.1.3", "justone"] + @["irc-#head", "another-0.1", "ab-0.1.3", "justone-1.0"] diff --git a/compiler/nimconf.nim b/compiler/nimconf.nim index c19b41af1..5f6889a6f 100644 --- a/compiler/nimconf.nim +++ b/compiler/nimconf.nim @@ -11,7 +11,7 @@ import llstream, nversion, commands, os, strutils, msgs, platform, condsyms, lexer, - options, idents, wordrecg, strtabs + options, idents, wordrecg, strtabs, lineinfos # ---------------- configuration file parser ----------------------------- # we use Nim's scanner here to save space and work @@ -27,12 +27,12 @@ proc parseAtom(L: var TLexer, tok: var TToken; config: ConfigRef): bool = ppGetTok(L, tok) result = parseExpr(L, tok, config) if tok.tokType == tkParRi: ppGetTok(L, tok) - else: lexMessage(L, errTokenExpected, "\')\'") + else: lexMessage(L, errGenerated, "expected closing ')'") elif tok.ident.id == ord(wNot): ppGetTok(L, tok) result = not parseAtom(L, tok, config) else: - result = isDefined(tok.ident) + result = isDefined(config, tok.ident.s) ppGetTok(L, tok) proc parseAndExpr(L: var TLexer, tok: var TToken; config: ConfigRef): bool = @@ -53,12 +53,12 @@ proc evalppIf(L: var TLexer, tok: var TToken; config: ConfigRef): bool = ppGetTok(L, tok) # skip 'if' or 'elif' result = parseExpr(L, tok, config) if tok.tokType == tkColon: ppGetTok(L, tok) - else: lexMessage(L, errTokenExpected, "\':\'") + else: lexMessage(L, errGenerated, "expected ':'") -var condStack: seq[bool] = @[] +#var condStack: seq[bool] = @[] -proc doEnd(L: var TLexer, tok: var TToken) = - if high(condStack) < 0: lexMessage(L, errTokenExpected, "@if") +proc doEnd(L: var TLexer, tok: var TToken; condStack: var seq[bool]) = + if high(condStack) < 0: lexMessage(L, errGenerated, "expected @if") ppGetTok(L, tok) # skip 'end' setLen(condStack, high(condStack)) @@ -66,20 +66,22 @@ type TJumpDest = enum jdEndif, jdElseEndif -proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest; config: ConfigRef) -proc doElse(L: var TLexer, tok: var TToken; config: ConfigRef) = - if high(condStack) < 0: lexMessage(L, errTokenExpected, "@if") +proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest; config: ConfigRef; + condStack: var seq[bool]) +proc doElse(L: var TLexer, tok: var TToken; config: ConfigRef; condStack: var seq[bool]) = + if high(condStack) < 0: lexMessage(L, errGenerated, "expected @if") ppGetTok(L, tok) if tok.tokType == tkColon: ppGetTok(L, tok) - if condStack[high(condStack)]: jumpToDirective(L, tok, jdEndif, config) + if condStack[high(condStack)]: jumpToDirective(L, tok, jdEndif, config, condStack) -proc doElif(L: var TLexer, tok: var TToken; config: ConfigRef) = - if high(condStack) < 0: lexMessage(L, errTokenExpected, "@if") +proc doElif(L: var TLexer, tok: var TToken; config: ConfigRef; condStack: var seq[bool]) = + if high(condStack) < 0: lexMessage(L, errGenerated, "expected @if") var res = evalppIf(L, tok, config) - if condStack[high(condStack)] or not res: jumpToDirective(L, tok, jdElseEndif, config) + if condStack[high(condStack)] or not res: jumpToDirective(L, tok, jdElseEndif, config, condStack) else: condStack[high(condStack)] = true -proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest; config: ConfigRef) = +proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest; config: ConfigRef; + condStack: var seq[bool]) = var nestedIfs = 0 while true: if tok.ident != nil and tok.ident.s == "@": @@ -89,39 +91,39 @@ proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest; config: Co inc(nestedIfs) of wElse: if dest == jdElseEndif and nestedIfs == 0: - doElse(L, tok, config) + doElse(L, tok, config, condStack) break of wElif: if dest == jdElseEndif and nestedIfs == 0: - doElif(L, tok, config) + doElif(L, tok, config, condStack) break of wEnd: if nestedIfs == 0: - doEnd(L, tok) + doEnd(L, tok, condStack) break if nestedIfs > 0: dec(nestedIfs) else: discard ppGetTok(L, tok) elif tok.tokType == tkEof: - lexMessage(L, errTokenExpected, "@end") + lexMessage(L, errGenerated, "expected @end") else: ppGetTok(L, tok) -proc parseDirective(L: var TLexer, tok: var TToken; config: ConfigRef) = +proc parseDirective(L: var TLexer, tok: var TToken; config: ConfigRef; condStack: var seq[bool]) = ppGetTok(L, tok) # skip @ case whichKeyword(tok.ident) of wIf: setLen(condStack, len(condStack) + 1) let res = evalppIf(L, tok, config) condStack[high(condStack)] = res - if not res: jumpToDirective(L, tok, jdElseEndif, config) - of wElif: doElif(L, tok, config) - of wElse: doElse(L, tok, config) - of wEnd: doEnd(L, tok) + if not res: jumpToDirective(L, tok, jdElseEndif, config, condStack) + of wElif: doElif(L, tok, config, condStack) + of wElse: doElse(L, tok, config, condStack) + of wEnd: doEnd(L, tok, condStack) of wWrite: ppGetTok(L, tok) - msgs.msgWriteln(strtabs.`%`(tokToStr(tok), options.gConfigVars, + msgs.msgWriteln(config, strtabs.`%`(tokToStr(tok), config.configVars, {useEnvironment, useKey})) ppGetTok(L, tok) else: @@ -144,60 +146,63 @@ proc parseDirective(L: var TLexer, tok: var TToken; config: ConfigRef) = ppGetTok(L, tok) os.putEnv(key, os.getEnv(key) & tokToStr(tok)) ppGetTok(L, tok) - else: lexMessage(L, errInvalidDirectiveX, tokToStr(tok)) + else: + lexMessage(L, errGenerated, "invalid directive: '$1'" % tokToStr(tok)) -proc confTok(L: var TLexer, tok: var TToken; config: ConfigRef) = +proc confTok(L: var TLexer, tok: var TToken; config: ConfigRef; condStack: var seq[bool]) = ppGetTok(L, tok) while tok.ident != nil and tok.ident.s == "@": - parseDirective(L, tok, config) # else: give the token to the parser + parseDirective(L, tok, config, condStack) # else: give the token to the parser proc checkSymbol(L: TLexer, tok: TToken) = if tok.tokType notin {tkSymbol..tkInt64Lit, tkStrLit..tkTripleStrLit}: - lexMessage(L, errIdentifierExpected, tokToStr(tok)) + lexMessage(L, errGenerated, "expected identifier, but got: " & tokToStr(tok)) -proc parseAssignment(L: var TLexer, tok: var TToken; config: ConfigRef) = +proc parseAssignment(L: var TLexer, tok: var TToken; + config: ConfigRef; condStack: var seq[bool]) = if tok.ident.s == "-" or tok.ident.s == "--": - confTok(L, tok, config) # skip unnecessary prefix + confTok(L, tok, config, condStack) # skip unnecessary prefix var info = getLineInfo(L, tok) # save for later in case of an error checkSymbol(L, tok) var s = tokToStr(tok) - confTok(L, tok, config) # skip symbol + confTok(L, tok, config, condStack) # skip symbol var val = "" while tok.tokType == tkDot: add(s, '.') - confTok(L, tok, config) + confTok(L, tok, config, condStack) checkSymbol(L, tok) add(s, tokToStr(tok)) - confTok(L, tok, config) + confTok(L, tok, config, condStack) if tok.tokType == tkBracketLe: # BUGFIX: val, not s! - # BUGFIX: do not copy '['! - confTok(L, tok, config) + confTok(L, tok, config, condStack) checkSymbol(L, tok) + add(val, '[') add(val, tokToStr(tok)) - confTok(L, tok, config) - if tok.tokType == tkBracketRi: confTok(L, tok, config) - else: lexMessage(L, errTokenExpected, "']'") + confTok(L, tok, config, condStack) + if tok.tokType == tkBracketRi: confTok(L, tok, config, condStack) + else: lexMessage(L, errGenerated, "expected closing ']'") add(val, ']') let percent = tok.ident != nil and tok.ident.s == "%=" if tok.tokType in {tkColon, tkEquals} or percent: if len(val) > 0: add(val, ':') - confTok(L, tok, config) # skip ':' or '=' or '%' + confTok(L, tok, config, condStack) # skip ':' or '=' or '%' checkSymbol(L, tok) add(val, tokToStr(tok)) - confTok(L, tok, config) # skip symbol + confTok(L, tok, config, condStack) # skip symbol while tok.ident != nil and tok.ident.s == "&": - confTok(L, tok, config) + confTok(L, tok, config, condStack) checkSymbol(L, tok) add(val, tokToStr(tok)) - confTok(L, tok, config) + confTok(L, tok, config, condStack) if percent: - processSwitch(s, strtabs.`%`(val, options.gConfigVars, + processSwitch(s, strtabs.`%`(val, config.configVars, {useEnvironment, useEmpty}), passPP, info, config) else: processSwitch(s, val, passPP, info, config) -proc readConfigFile(filename: string; cache: IdentCache; config: ConfigRef) = +proc readConfigFile( + filename: string; cache: IdentCache; config: ConfigRef): bool = var L: TLexer tok: TToken @@ -205,54 +210,58 @@ proc readConfigFile(filename: string; cache: IdentCache; config: ConfigRef) = stream = llStreamOpen(filename, fmRead) if stream != nil: initToken(tok) - openLexer(L, filename, stream, cache) + openLexer(L, filename, stream, cache, config) tok.tokType = tkEof # to avoid a pointless warning - confTok(L, tok, config) # read in the first token - while tok.tokType != tkEof: parseAssignment(L, tok, config) - if len(condStack) > 0: lexMessage(L, errTokenExpected, "@end") + var condStack: seq[bool] = @[] + confTok(L, tok, config, condStack) # read in the first token + while tok.tokType != tkEof: parseAssignment(L, tok, config, condStack) + if len(condStack) > 0: lexMessage(L, errGenerated, "expected @end") closeLexer(L) - rawMessage(hintConf, filename) + return true -proc getUserConfigPath(filename: string): string = - result = joinPath(getConfigDir(), filename) +proc getUserConfigPath*(filename: string): string = + result = joinPath([getConfigDir(), "nim", filename]) -proc getSystemConfigPath(filename: string): string = +proc getSystemConfigPath*(conf: ConfigRef; filename: string): string = # try standard configuration file (installation did not distribute files # the UNIX way) - let p = getPrefixDir() + let p = getPrefixDir(conf) result = joinPath([p, "config", filename]) when defined(unix): - if not existsFile(result): result = joinPath([p, "etc", filename]) - if not existsFile(result): result = "/etc/" & filename + if not existsFile(result): result = joinPath([p, "etc/nim", filename]) + if not existsFile(result): result = "/etc/nim/" & filename + +proc loadConfigs*(cfg: string; cache: IdentCache; conf: ConfigRef) = + setDefaultLibpath(conf) -proc loadConfigs*(cfg: string; cache: IdentCache; config: ConfigRef = nil) = - setDefaultLibpath() + var configFiles = newSeq[string]() - if optSkipConfigFile notin gGlobalOptions: - readConfigFile(getSystemConfigPath(cfg), cache, config) + template readConfigFile(path: string) = + let configPath = path + if readConfigFile(configPath, cache, conf): + add(configFiles, configPath) - if optSkipUserConfigFile notin gGlobalOptions: - readConfigFile(getUserConfigPath(cfg), cache, config) + if optSkipSystemConfigFile notin conf.globalOptions: + readConfigFile(getSystemConfigPath(conf, cfg)) - var pd = if gProjectPath.len > 0: gProjectPath else: getCurrentDir() - if optSkipParentConfigFiles notin gGlobalOptions: + if optSkipUserConfigFile notin conf.globalOptions: + readConfigFile(getUserConfigPath(cfg)) + + let pd = if conf.projectPath.len > 0: conf.projectPath else: getCurrentDir() + if optSkipParentConfigFiles notin conf.globalOptions: for dir in parentDirs(pd, fromRoot=true, inclusive=false): - readConfigFile(dir / cfg, cache, config) + readConfigFile(dir / cfg) - if optSkipProjConfigFile notin gGlobalOptions: - readConfigFile(pd / cfg, cache, config) + if optSkipProjConfigFile notin conf.globalOptions: + readConfigFile(pd / cfg) - if gProjectName.len != 0: + if conf.projectName.len != 0: # new project wide config file: - var projectConfig = changeFileExt(gProjectFull, "nimcfg") - if not fileExists(projectConfig): - projectConfig = changeFileExt(gProjectFull, "nim.cfg") + var projectConfig = changeFileExt(conf.projectFull, "nimcfg") if not fileExists(projectConfig): - projectConfig = changeFileExt(gProjectFull, "nimrod.cfg") - if fileExists(projectConfig): - rawMessage(warnDeprecated, projectConfig) - readConfigFile(projectConfig, cache, config) + projectConfig = changeFileExt(conf.projectFull, "nim.cfg") + readConfigFile(projectConfig) -proc loadConfigs*(cfg: string; config: ConfigRef = nil) = - # for backwards compatibility only. - loadConfigs(cfg, newIdentCache(), config) + for filename in configFiles: + # delayed to here so that `hintConf` is honored + rawMessage(conf, hintConf, filename) diff --git a/compiler/nimeval.nim b/compiler/nimeval.nim index aca03fc16..f20b5642c 100644 --- a/compiler/nimeval.nim +++ b/compiler/nimeval.nim @@ -1,7 +1,7 @@ # # # The Nim Compiler -# (c) Copyright 2013 Andreas Rumpf +# (c) Copyright 2018 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -9,26 +9,112 @@ ## exposes the Nim VM to clients. import - ast, modules, passes, passaux, condsyms, - options, nimconf, sem, semdata, llstream, vm, modulegraphs, idents - -proc execute*(program: string) = - passes.gIncludeFile = includeModule - passes.gImportModule = importModule - initDefines() - loadConfigs(DefaultConfig) - - initDefines() - defineSymbol("nimrodvm") - when hasFFI: defineSymbol("nimffi") - registerPass(verbosePass) - registerPass(semPass) - registerPass(evalPass) - - searchPaths.add options.libpath - var graph = newModuleGraph() + ast, astalgo, modules, passes, condsyms, + options, sem, semdata, llstream, vm, vmdef, + modulegraphs, idents, os + +type + Interpreter* = ref object ## Use Nim as an interpreter with this object + mainModule: PSym + graph: ModuleGraph + scriptName: string + +iterator exportedSymbols*(i: Interpreter): PSym = + assert i != nil + assert i.mainModule != nil, "no main module selected" + var it: TTabIter + var s = initTabIter(it, i.mainModule.tab) + while s != nil: + yield s + s = nextIter(it, i.mainModule.tab) + +proc selectUniqueSymbol*(i: Interpreter; name: string; + symKinds: set[TSymKind] = {skLet, skVar}): PSym = + ## Can be used to access a unique symbol of ``name`` and + ## the given ``symKinds`` filter. + assert i != nil + assert i.mainModule != nil, "no main module selected" + let n = getIdent(i.graph.cache, name) + var it: TIdentIter + var s = initIdentIter(it, i.mainModule.tab, n) + result = nil + while s != nil: + if s.kind in symKinds: + if result == nil: result = s + else: return nil # ambiguous + s = nextIdentIter(it, i.mainModule.tab) + +proc selectRoutine*(i: Interpreter; name: string): PSym = + ## Selects a declared rountine (proc/func/etc) from the main module. + ## The routine needs to have the export marker ``*``. The only matching + ## routine is returned and ``nil`` if it is overloaded. + result = selectUniqueSymbol(i, name, {skTemplate, skMacro, skFunc, + skMethod, skProc, skConverter}) + +proc callRoutine*(i: Interpreter; routine: PSym; args: openArray[PNode]): PNode = + assert i != nil + result = vm.execProc(PCtx i.graph.vm, routine, args) + +proc getGlobalValue*(i: Interpreter; letOrVar: PSym): PNode = + result = vm.getGlobalValue(PCtx i.graph.vm, letOrVar) + +proc implementRoutine*(i: Interpreter; pkg, module, name: string; + impl: proc (a: VmArgs) {.closure, gcsafe.}) = + assert i != nil + let vm = PCtx(i.graph.vm) + vm.registerCallback(pkg & "." & module & "." & name, impl) + +proc evalScript*(i: Interpreter; scriptStream: PLLStream = nil) = + ## This can also be used to *reload* the script. + assert i != nil + assert i.mainModule != nil, "no main module selected" + initStrTable(i.mainModule.tab) + i.mainModule.ast = nil + + let s = if scriptStream != nil: scriptStream + else: llStreamOpen(findFile(i.graph.config, i.scriptName), fmRead) + processModule(i.graph, i.mainModule, s) + +proc findNimStdLib*(): string = + ## Tries to find a path to a valid "system.nim" file. + ## Returns "" on failure. + try: + let nimexe = os.findExe("nim") + if nimexe.len == 0: return "" + result = nimexe.splitPath()[0] /../ "lib" + if not fileExists(result / "system.nim"): + when defined(unix): + result = nimexe.expandSymlink.splitPath()[0] /../ "lib" + if not fileExists(result / "system.nim"): return "" + except OSError, ValueError: + return "" + +proc createInterpreter*(scriptName: string; + searchPaths: openArray[string]; + flags: TSandboxFlags = {}): Interpreter = + var conf = newConfigRef() var cache = newIdentCache() - var m = makeStdinModule(graph) + var graph = newModuleGraph(cache, conf) + connectCallbacks(graph) + initDefines(conf.symbols) + defineSymbol(conf.symbols, "nimscript") + defineSymbol(conf.symbols, "nimconfig") + registerPass(graph, semPass) + registerPass(graph, evalPass) + + for p in searchPaths: + conf.searchPaths.add(p) + if conf.libpath.len == 0: conf.libpath = p + + var m = graph.makeModule(scriptName) incl(m.flags, sfMainModule) - compileSystemModule(graph,cache) - processModule(graph,m, llStreamOpen(program), nil, cache) + var vm = newCtx(m, cache, graph) + vm.mode = emRepl + vm.features = flags + graph.vm = vm + graph.compileSystemModule() + result = Interpreter(mainModule: m, graph: graph, scriptName: scriptName) + +proc destroyInterpreter*(i: Interpreter) = + ## destructor. + discard "currently nothing to do." diff --git a/compiler/nimfix/nimfix.nim b/compiler/nimfix/nimfix.nim index a97d88078..58b019cd3 100644 --- a/compiler/nimfix/nimfix.nim +++ b/compiler/nimfix/nimfix.nim @@ -11,7 +11,7 @@ import strutils, os, parseopt import compiler/[options, commands, modules, sem, - passes, passaux, nimfix/pretty, + passes, passaux, linter, msgs, nimconf, extccomp, condsyms, modulegraphs, idents] @@ -38,7 +38,7 @@ In addition, all command line options of Nim are supported. proc mainCommand = registerPass verbosePass registerPass semPass - gCmd = cmdPretty + conf.cmd = cmdPretty searchPaths.add options.libpath if gProjectFull.len != 0: # current path is always looked first for modules @@ -47,7 +47,7 @@ proc mainCommand = compileProject(newModuleGraph(), newIdentCache()) pretty.overwriteFiles() -proc processCmdLine*(pass: TCmdLinePass, cmd: string) = +proc processCmdLine*(pass: TCmdLinePass, cmd: string, config: ConfigRef) = var p = parseopt.initOptParser(cmd) var argsCount = 0 gOnlyMainfile = true @@ -76,16 +76,16 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) = of "wholeproject": gOnlyMainfile = false of "besteffort": msgs.gErrorMax = high(int) # don't stop after first error else: - processSwitch(pass, p) + processSwitch(pass, p, config) of cmdArgument: options.gProjectName = unixToNativePath(p.key) # if processArgument(pass, p, argsCount): break -proc handleCmdLine() = +proc handleCmdLine(config: ConfigRef) = if paramCount() == 0: stdout.writeLine(Usage) else: - processCmdLine(passCmd1, "") + processCmdLine(passCmd1, "", config) if gProjectName != "": try: gProjectFull = canonicalizePath(gProjectName) @@ -96,11 +96,11 @@ proc handleCmdLine() = gProjectName = p.name else: gProjectPath = getCurrentDir() - loadConfigs(DefaultConfig) # load all config files + loadConfigs(DefaultConfig, config) # load all config files # now process command line arguments again, because some options in the # command line can overwite the config file's settings extccomp.initVars() - processCmdLine(passCmd2, "") + processCmdLine(passCmd2, "", config) mainCommand() when compileOption("gc", "v2") or compileOption("gc", "refc"): @@ -108,4 +108,4 @@ when compileOption("gc", "v2") or compileOption("gc", "refc"): condsyms.initDefines() defineSymbol "nimfix" -handleCmdline() +handleCmdline newConfigRef() diff --git a/compiler/nimfix/prettybase.nim b/compiler/nimfix/prettybase.nim index 0f17cbcb1..c3e16e5ba 100644 --- a/compiler/nimfix/prettybase.nim +++ b/compiler/nimfix/prettybase.nim @@ -7,64 +7,13 @@ # distribution, for details about the copyright. # -import strutils, lexbase, streams -import compiler/ast, compiler/msgs, compiler/idents +import strutils except Letters +import lexbase, streams +import ".." / [ast, msgs, lineinfos, idents, options, linter] from os import splitFile -type - TSourceFile* = object - lines*: seq[string] - dirty*, isNimfixFile*: bool - fullpath*, newline*: string - fileIdx*: int32 - -var - gSourceFiles*: seq[TSourceFile] = @[] - -proc loadFile*(info: TLineInfo) = - let i = info.fileIndex - if i >= gSourceFiles.len: - gSourceFiles.setLen(i+1) - if gSourceFiles[i].lines.isNil: - gSourceFiles[i].fileIdx = info.fileIndex - gSourceFiles[i].lines = @[] - let path = info.toFullPath - gSourceFiles[i].fullpath = path - gSourceFiles[i].isNimfixFile = path.splitFile.ext == ".nimfix" - # we want to die here for IOError: - for line in lines(path): - gSourceFiles[i].lines.add(line) - # extract line ending of the file: - var lex: BaseLexer - open(lex, newFileStream(path, fmRead)) - var pos = lex.bufpos - while true: - case lex.buf[pos] - of '\c': - gSourceFiles[i].newline = "\c\L" - break - of '\L', '\0': - gSourceFiles[i].newline = "\L" - break - else: discard - inc pos - close(lex) - -const - Letters* = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '_'} - -proc identLen*(line: string, start: int): int = - while start+result < line.len and line[start+result] in Letters: - inc result - -proc differ*(line: string, a, b: int, x: string): bool = - let y = line[a..b] - result = cmpIgnoreStyle(y, x) == 0 and y != x - -proc replaceDeprecated*(info: TLineInfo; oldSym, newSym: PIdent) = - loadFile(info) - - let line = gSourceFiles[info.fileIndex].lines[info.line-1] +proc replaceDeprecated*(conf: ConfigRef; info: TLineInfo; oldSym, newSym: PIdent) = + let line = sourceLine(conf, info) var first = min(info.col.int, line.len) if first < 0: return #inc first, skipIgnoreCase(line, "proc ", first) @@ -75,20 +24,18 @@ proc replaceDeprecated*(info: TLineInfo; oldSym, newSym: PIdent) = let last = first+identLen(line, first)-1 if cmpIgnoreStyle(line[first..last], oldSym.s) == 0: var x = line.substr(0, first-1) & newSym.s & line.substr(last+1) - system.shallowCopy(gSourceFiles[info.fileIndex].lines[info.line-1], x) - gSourceFiles[info.fileIndex].dirty = true + system.shallowCopy(conf.m.fileInfos[info.fileIndex.int32].lines[info.line.int-1], x) + conf.m.fileInfos[info.fileIndex.int32].dirty = true #if newSym.s == "File": writeStackTrace() -proc replaceDeprecated*(info: TLineInfo; oldSym, newSym: PSym) = - replaceDeprecated(info, oldSym.name, newSym.name) - -proc replaceComment*(info: TLineInfo) = - loadFile(info) +proc replaceDeprecated*(conf: ConfigRef; info: TLineInfo; oldSym, newSym: PSym) = + replaceDeprecated(conf, info, oldSym.name, newSym.name) - let line = gSourceFiles[info.fileIndex].lines[info.line-1] +proc replaceComment*(conf: ConfigRef; info: TLineInfo) = + let line = sourceLine(conf, info) var first = info.col.int if line[first] != '#': inc first var x = line.substr(0, first-1) & "discard " & line.substr(first+1).escape - system.shallowCopy(gSourceFiles[info.fileIndex].lines[info.line-1], x) - gSourceFiles[info.fileIndex].dirty = true + system.shallowCopy(conf.m.fileInfos[info.fileIndex.int32].lines[info.line.int-1], x) + conf.m.fileInfos[info.fileIndex.int32].dirty = true diff --git a/compiler/nimsets.nim b/compiler/nimsets.nim index bda753e85..b00353e20 100644 --- a/compiler/nimsets.nim +++ b/compiler/nimsets.nim @@ -10,29 +10,13 @@ # this unit handles Nim sets; it implements symbolic sets import - ast, astalgo, trees, nversion, msgs, platform, bitsets, types, renderer - -proc toBitSet*(s: PNode, b: var TBitSet) - # this function is used for case statement checking: -proc overlap*(a, b: PNode): bool -proc inSet*(s: PNode, elem: PNode): bool -proc someInSet*(s: PNode, a, b: PNode): bool -proc emptyRange*(a, b: PNode): bool -proc setHasRange*(s: PNode): bool - # returns true if set contains a range (needed by the code generator) - # these are used for constant folding: -proc unionSets*(a, b: PNode): PNode -proc diffSets*(a, b: PNode): PNode -proc intersectSets*(a, b: PNode): PNode -proc symdiffSets*(a, b: PNode): PNode -proc containsSets*(a, b: PNode): bool -proc equalSets*(a, b: PNode): bool -proc cardSet*(s: PNode): BiggestInt -# implementation - -proc inSet(s: PNode, elem: PNode): bool = + ast, astalgo, trees, nversion, lineinfos, platform, bitsets, types, renderer, + options + +proc inSet*(s: PNode, elem: PNode): bool = + assert s.kind == nkCurly if s.kind != nkCurly: - internalError(s.info, "inSet") + #internalError(s.info, "inSet") return false for i in countup(0, sonsLen(s) - 1): if s.sons[i].kind == nkRange: @@ -44,7 +28,7 @@ proc inSet(s: PNode, elem: PNode): bool = return true result = false -proc overlap(a, b: PNode): bool = +proc overlap*(a, b: PNode): bool = if a.kind == nkRange: if b.kind == nkRange: # X..Y and C..D overlap iff (X <= D and C <= Y) @@ -58,10 +42,11 @@ proc overlap(a, b: PNode): bool = else: result = sameValue(a, b) -proc someInSet(s: PNode, a, b: PNode): bool = +proc someInSet*(s: PNode, a, b: PNode): bool = # checks if some element of a..b is in the set s + assert s.kind == nkCurly if s.kind != nkCurly: - internalError(s.info, "SomeInSet") + #internalError(s.info, "SomeInSet") return false for i in countup(0, sonsLen(s) - 1): if s.sons[i].kind == nkRange: @@ -74,10 +59,10 @@ proc someInSet(s: PNode, a, b: PNode): bool = return true result = false -proc toBitSet(s: PNode, b: var TBitSet) = +proc toBitSet*(conf: ConfigRef; s: PNode, b: var TBitSet) = var first, j: BiggestInt - first = firstOrd(s.typ.sons[0]) - bitSetInit(b, int(getSize(s.typ))) + first = firstOrd(conf, s.typ.sons[0]) + bitSetInit(b, int(getSize(conf, s.typ))) for i in countup(0, sonsLen(s) - 1): if s.sons[i].kind == nkRange: j = getOrdValue(s.sons[i].sons[0]) @@ -87,13 +72,13 @@ proc toBitSet(s: PNode, b: var TBitSet) = else: bitSetIncl(b, getOrdValue(s.sons[i]) - first) -proc toTreeSet(s: TBitSet, settype: PType, info: TLineInfo): PNode = +proc toTreeSet*(conf: ConfigRef; s: TBitSet, settype: PType, info: TLineInfo): PNode = var a, b, e, first: BiggestInt # a, b are interval borders elemType: PType n: PNode elemType = settype.sons[0] - first = firstOrd(elemType) + first = firstOrd(conf, elemType) result = newNodeI(nkCurly, info) result.typ = settype result.info = info @@ -123,59 +108,52 @@ proc toTreeSet(s: TBitSet, settype: PType, info: TLineInfo): PNode = template nodeSetOp(a, b: PNode, op: untyped) {.dirty.} = var x, y: TBitSet - toBitSet(a, x) - toBitSet(b, y) + toBitSet(conf, a, x) + toBitSet(conf, b, y) op(x, y) - result = toTreeSet(x, a.typ, a.info) + result = toTreeSet(conf, x, a.typ, a.info) -proc unionSets(a, b: PNode): PNode = nodeSetOp(a, b, bitSetUnion) -proc diffSets(a, b: PNode): PNode = nodeSetOp(a, b, bitSetDiff) -proc intersectSets(a, b: PNode): PNode = nodeSetOp(a, b, bitSetIntersect) -proc symdiffSets(a, b: PNode): PNode = nodeSetOp(a, b, bitSetSymDiff) +proc unionSets*(conf: ConfigRef; a, b: PNode): PNode = nodeSetOp(a, b, bitSetUnion) +proc diffSets*(conf: ConfigRef; a, b: PNode): PNode = nodeSetOp(a, b, bitSetDiff) +proc intersectSets*(conf: ConfigRef; a, b: PNode): PNode = nodeSetOp(a, b, bitSetIntersect) +proc symdiffSets*(conf: ConfigRef; a, b: PNode): PNode = nodeSetOp(a, b, bitSetSymDiff) -proc containsSets(a, b: PNode): bool = +proc containsSets*(conf: ConfigRef; a, b: PNode): bool = var x, y: TBitSet - toBitSet(a, x) - toBitSet(b, y) + toBitSet(conf, a, x) + toBitSet(conf, b, y) result = bitSetContains(x, y) -proc equalSets(a, b: PNode): bool = +proc equalSets*(conf: ConfigRef; a, b: PNode): bool = var x, y: TBitSet - toBitSet(a, x) - toBitSet(b, y) + toBitSet(conf, a, x) + toBitSet(conf, b, y) result = bitSetEquals(x, y) -proc complement*(a: PNode): PNode = +proc complement*(conf: ConfigRef; a: PNode): PNode = var x: TBitSet - toBitSet(a, x) + toBitSet(conf, a, x) for i in countup(0, high(x)): x[i] = not x[i] - result = toTreeSet(x, a.typ, a.info) + result = toTreeSet(conf, x, a.typ, a.info) -proc deduplicate*(a: PNode): PNode = +proc deduplicate*(conf: ConfigRef; a: PNode): PNode = var x: TBitSet - toBitSet(a, x) - result = toTreeSet(x, a.typ, a.info) + toBitSet(conf, a, x) + result = toTreeSet(conf, x, a.typ, a.info) -proc cardSet(s: PNode): BiggestInt = - # here we can do better than converting it into a compact set - # we just count the elements directly - result = 0 - for i in countup(0, sonsLen(s) - 1): - if s.sons[i].kind == nkRange: - result = result + getOrdValue(s.sons[i].sons[1]) - - getOrdValue(s.sons[i].sons[0]) + 1 - else: - inc(result) +proc cardSet*(conf: ConfigRef; a: PNode): BiggestInt = + var x: TBitSet + toBitSet(conf, a, x) + result = bitSetCard(x) -proc setHasRange(s: PNode): bool = +proc setHasRange*(s: PNode): bool = + assert s.kind == nkCurly if s.kind != nkCurly: - internalError(s.info, "SetHasRange") return false for i in countup(0, sonsLen(s) - 1): if s.sons[i].kind == nkRange: return true result = false -proc emptyRange(a, b: PNode): bool = +proc emptyRange*(a, b: PNode): bool = result = not leValue(a, b) # a > b iff not (a <= b) - diff --git a/compiler/nversion.nim b/compiler/nversion.nim index 85265a7c0..4b8cf7100 100644 --- a/compiler/nversion.nim +++ b/compiler/nversion.nim @@ -15,3 +15,6 @@ const VersionAsString* = system.NimVersion RodFileVersion* = "1223" # modify this if the rod-format changes! + NimCompilerApiVersion* = 2 ## Check for the existance of this before accessing it + ## as older versions of the compiler API do not + ## declare this. diff --git a/compiler/options.nim b/compiler/options.nim index 65f5a6245..c9334991a 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -8,16 +8,17 @@ # import - os, strutils, strtabs, osproc, sets + os, strutils, strtabs, osproc, sets, lineinfos, platform, + prefixmatches + +from terminal import isatty +from times import utc, fromUnix, local, getTime, format, DateTime const hasTinyCBackend* = defined(tinyc) useEffectSystem* = true useWriteTracking* = false hasFFI* = defined(useFFI) - newScopeForIf* = true - useCaas* = not defined(noCaas) - noTimeMachine* = defined(avoidTimeMachine) and defined(macosx) copyrightYear* = "2018" type # please make sure we have under 32 options @@ -25,7 +26,7 @@ type # please make sure we have under 32 options TOption* = enum # **keep binary compatible** optNone, optObjCheck, optFieldCheck, optRangeCheck, optBoundsCheck, optOverflowCheck, optNilCheck, - optNaNCheck, optInfCheck, + optNaNCheck, optInfCheck, optMoveCheck, optAssert, optLineDir, optWarns, optHints, optOptimizeSpeed, optOptimizeSize, optStackTrace, # stack tracing support optLineTrace, # line tracing support (includes stack tracing) @@ -36,11 +37,15 @@ type # please make sure we have under 32 options optImplicitStatic, # optimization: implicit at compile time # evaluation optPatterns, # en/disable pattern matching - optMemTracker + optMemTracker, + optHotCodeReloading, + optLaxStrings, + optNilSeqs TOptions* = set[TOption] TGlobalOption* = enum # **keep binary compatible** - gloptNone, optForceFullMake, optDeadCodeElim, + gloptNone, optForceFullMake, + optDeadCodeElimUnused, # deprecated, always on optListCmd, optCompileOnly, optNoLinking, optCDebug, # turn on debugging information optGenDynLib, # generate a dynamic library @@ -49,11 +54,11 @@ type # please make sure we have under 32 options optGenScript, # generate a script file to compile the *.c files optGenMapping, # generate a mapping file optRun, # run the compiled project - optCaasEnabled # compiler-as-a-service is running - optSkipConfigFile, # skip the general config file - optSkipProjConfigFile, # skip the project's config file - optSkipUserConfigFile, # skip the users's config file - optSkipParentConfigFiles, # skip parent dir's config files + optCheckNep1, # check that the names adhere to NEP-1 + optSkipSystemConfigFile, # skip the system's cfg/nims config file + optSkipProjConfigFile, # skip the project's cfg/nims config file + optSkipUserConfigFile, # skip the users's cfg/nims config file + optSkipParentConfigFiles, # skip parent dir's cfg/nims config files optNoMain, # do not generate a "main" proc optUseColors, # use colors for hints, warnings, and errors optThreads, # support for multi-threading @@ -68,6 +73,12 @@ type # please make sure we have under 32 options optIdeTerse # idetools: use terse descriptions optNoCppExceptions # use C exception handling even with CPP optExcessiveStackTrace # fully qualified module filenames + optWholeProject # for 'doc2': output any dependency + optMixedMode # true if some module triggered C++ codegen + optListFullPaths + optNoNimblePath + optDynlibOverrideAll + optUseNimNamespace TGlobalOptions* = set[TGlobalOption] @@ -80,7 +91,6 @@ type # **keep binary compatible** cmdNone, cmdCompileToC, cmdCompileToCpp, cmdCompileToOC, cmdCompileToJS, - cmdCompileToPHP, cmdCompileToLLVM, cmdInterpret, cmdPretty, cmdDoc, cmdGenDepend, cmdDump, cmdCheck, # semantic checking for whole project @@ -95,75 +105,300 @@ type cmdJsonScript # compile a .json build file TStringSeq* = seq[string] TGCMode* = enum # the selected GC - gcNone, gcBoehm, gcGo, gcRegions, gcMarkAndSweep, gcRefc, - gcV2, gcGenerational + gcNone, gcBoehm, gcGo, gcRegions, gcMarkAndSweep, gcDestructors, + gcRefc, gcV2 IdeCmd* = enum ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideMod, ideHighlight, ideOutline, ideKnown, ideMsg - ConfigRef* = ref object ## eventually all global configuration should be moved here - cppDefines*: HashSet[string] - headerFile*: string - -proc newConfigRef*(): ConfigRef = - result = ConfigRef(cppDefines: initSet[string](), - headerFile: "") - -proc cppDefine*(c: ConfigRef; define: string) = - c.cppDefines.incl define + Feature* = enum ## experimental features; DO NOT RENAME THESE! + implicitDeref, + dotOperators, + callOperator, + parallel, + destructor, + notnil, + dynamicBindSym, + forLoopMacros, + caseStmtMacros -var - gIdeCmd*: IdeCmd - gOldNewlines*: bool + SymbolFilesOption* = enum + disabledSf, writeOnlySf, readOnlySf, v2Sf + + TSystemCC* = enum + ccNone, ccGcc, ccNintendoSwitch, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc, + ccTcc, ccPcc, ccUcc, ccIcl, ccIcc + + CfileFlag* {.pure.} = enum + Cached, ## no need to recompile this time + External ## file was introduced via .compile pragma + + Cfile* = object + cname*, obj*: string + flags*: set[CFileFlag] + CfileList* = seq[Cfile] + + Suggest* = ref object + section*: IdeCmd + qualifiedPath*: seq[string] + name*: ptr string # not used beyond sorting purposes; name is also + # part of 'qualifiedPath' + filePath*: string + line*: int # Starts at 1 + column*: int # Starts at 0 + doc*: string # Not escaped (yet) + forth*: string # type + quality*: range[0..100] # matching quality + isGlobal*: bool # is a global variable + contextFits*: bool # type/non-type context matches + prefix*: PrefixMatch + symkind*: byte + scope*, localUsages*, globalUsages*: int # more usages is better + tokenLen*: int + version*: int + Suggestions* = seq[Suggest] + + ConfigRef* = ref object ## every global configuration + ## fields marked with '*' are subject to + ## the incremental compilation mechanisms + ## (+) means "part of the dependency" + target*: Target # (+) + linesCompiled*: int # all lines that have been compiled + options*: TOptions # (+) + globalOptions*: TGlobalOptions # (+) + m*: MsgConfig + evalTemplateCounter*: int + evalMacroCounter*: int + exitcode*: int8 + cmd*: TCommands # the command + selectedGC*: TGCMode # the selected GC (+) + verbosity*: int # how verbose the compiler is + numberOfProcessors*: int # number of processors + evalExpr*: string # expression for idetools --eval + lastCmdTime*: float # when caas is enabled, we measure each command + symbolFiles*: SymbolFilesOption + + cppDefines*: HashSet[string] # (*) + headerFile*: string + features*: set[Feature] + arguments*: string ## the arguments to be passed to the program that + ## should be run + helpWritten*: bool + ideCmd*: IdeCmd + oldNewlines*: bool + cCompiler*: TSystemCC + enableNotes*: TNoteKinds + disableNotes*: TNoteKinds + foreignPackageNotes*: TNoteKinds + notes*: TNoteKinds + mainPackageNotes*: TNoteKinds + mainPackageId*: int + errorCounter*: int + hintCounter*: int + warnCounter*: int + errorMax*: int + configVars*: StringTableRef + symbols*: StringTableRef ## We need to use a StringTableRef here as defined + ## symbols are always guaranteed to be style + ## insensitive. Otherwise hell would break lose. + packageCache*: StringTableRef + searchPaths*: seq[string] + lazyPaths*: seq[string] + outFile*, prefixDir*, libpath*, nimcacheDir*: string + dllOverrides, moduleOverrides*: StringTableRef + projectName*: string # holds a name like 'nim' + projectPath*: string # holds a path like /home/alice/projects/nim/compiler/ + projectFull*: string # projectPath/projectName + projectIsStdin*: bool # whether we're compiling from stdin + projectMainIdx*: FileIndex # the canonical path id of the main module + command*: string # the main command (e.g. cc, check, scan, etc) + commandArgs*: seq[string] # any arguments after the main command + keepComments*: bool # whether the parser needs to keep comments + implicitImports*: seq[string] # modules that are to be implicitly imported + implicitIncludes*: seq[string] # modules that are to be implicitly included + docSeeSrcUrl*: string # if empty, no seeSrc will be generated. \ + # The string uses the formatting variables `path` and `line`. + + # the used compiler + cIncludes*: seq[string] # directories to search for included files + cLibs*: seq[string] # directories to search for lib files + cLinkedLibs*: seq[string] # libraries to link + + externalToLink*: seq[string] # files to link in addition to the file + # we compiled (*) + linkOptionsCmd*: string + compileOptionsCmd*: seq[string] + linkOptions*: string # (*) + compileOptions*: string # (*) + ccompilerpath*: string + toCompile*: CfileList # (*) + suggestionResultHook*: proc (result: Suggest) {.closure.} + suggestVersion*: int + suggestMaxResults*: int + lastLineInfo*: TLineInfo + writelnHook*: proc (output: string) {.closure.} + structuredErrorHook*: proc (config: ConfigRef; info: TLineInfo; msg: string; + severity: Severity) {.closure.} + +template depConfigFields*(fn) {.dirty.} = + fn(target) + fn(options) + fn(globalOptions) + fn(selectedGC) + +const oldExperimentalFeatures* = {implicitDeref, dotOperators, callOperator, parallel} const ChecksOptions* = {optObjCheck, optFieldCheck, optRangeCheck, optNilCheck, - optOverflowCheck, optBoundsCheck, optAssert, optNaNCheck, optInfCheck} - -var - gOptions*: TOptions = {optObjCheck, optFieldCheck, optRangeCheck, - optBoundsCheck, optOverflowCheck, optAssert, optWarns, - optHints, optStackTrace, optLineTrace, - optPatterns, optNilCheck} - gGlobalOptions*: TGlobalOptions = {optThreadAnalysis} - gExitcode*: int8 - gCmd*: TCommands = cmdNone # the command - gSelectedGC* = gcRefc # the selected GC - searchPaths*: seq[string] = @[] - lazyPaths*: seq[string] = @[] - outFile*: string = "" - docSeeSrcUrl*: string = "" # if empty, no seeSrc will be generated. \ - # The string uses the formatting variables `path` and `line`. - #headerFile*: string = "" - gVerbosity* = 1 # how verbose the compiler is - gNumberOfProcessors*: int # number of processors - gWholeProject*: bool # for 'doc2': output any dependency - gEvalExpr* = "" # expression for idetools --eval - gLastCmdTime*: float # when caas is enabled, we measure each command - gListFullPaths*: bool - gPreciseStack*: bool = false - gNoNimblePath* = false - gExperimentalMode*: bool - newDestructors*: bool - gDynlibOverrideAll*: bool + optOverflowCheck, optBoundsCheck, optAssert, optNaNCheck, optInfCheck, + optMoveCheck} + + DefaultOptions* = {optObjCheck, optFieldCheck, optRangeCheck, + optBoundsCheck, optOverflowCheck, optAssert, optWarns, + optHints, optStackTrace, optLineTrace, + optPatterns, optNilCheck, optMoveCheck} + DefaultGlobalOptions* = {optThreadAnalysis} + +proc getSrcTimestamp(): DateTime = + try: + result = utc(fromUnix(parseInt(getEnv("SOURCE_DATE_EPOCH", + "not a number")))) + except ValueError: + # Environment variable malformed. + # https://reproducible-builds.org/specs/source-date-epoch/: "If the + # value is malformed, the build process SHOULD exit with a non-zero + # error code", which this doesn't do. This uses local time, because + # that maintains compatibility with existing usage. + result = utc getTime() + +proc getDateStr*(): string = + result = format(getSrcTimestamp(), "yyyy-MM-dd") + +proc getClockStr*(): string = + result = format(getSrcTimestamp(), "HH:mm:ss") + +template newPackageCache*(): untyped = + newStringTable(when FileSystemCaseSensitive: + modeCaseInsensitive + else: + modeCaseSensitive) -type - SymbolFilesOption* = enum - disabledSf, enabledSf, writeOnlySf, readOnlySf, v2Sf - -var gSymbolFiles*: SymbolFilesOption - -proc importantComments*(): bool {.inline.} = gCmd in {cmdDoc, cmdIdeTools} -proc usesNativeGC*(): bool {.inline.} = gSelectedGC >= gcRefc -template preciseStack*(): bool = gPreciseStack +proc newConfigRef*(): ConfigRef = + result = ConfigRef( + selectedGC: gcRefc, + cCompiler: ccGcc, + verbosity: 1, + options: DefaultOptions, + globalOptions: DefaultGlobalOptions, + m: initMsgConfig(), + evalExpr: "", + cppDefines: initSet[string](), + headerFile: "", features: {}, foreignPackageNotes: {hintProcessing, warnUnknownMagic, + hintQuitCalled, hintExecuting}, + notes: NotesVerbosity[1], mainPackageNotes: NotesVerbosity[1], + configVars: newStringTable(modeStyleInsensitive), + symbols: newStringTable(modeStyleInsensitive), + packageCache: newPackageCache(), + searchPaths: @[], + lazyPaths: @[], + outFile: "", prefixDir: "", libpath: "", nimcacheDir: "", + dllOverrides: newStringTable(modeCaseInsensitive), + moduleOverrides: newStringTable(modeStyleInsensitive), + projectName: "", # holds a name like 'nim' + projectPath: "", # holds a path like /home/alice/projects/nim/compiler/ + projectFull: "", # projectPath/projectName + projectIsStdin: false, # whether we're compiling from stdin + projectMainIdx: FileIndex(0'i32), # the canonical path id of the main module + command: "", # the main command (e.g. cc, check, scan, etc) + commandArgs: @[], # any arguments after the main command + keepComments: true, # whether the parser needs to keep comments + implicitImports: @[], # modules that are to be implicitly imported + implicitIncludes: @[], # modules that are to be implicitly included + docSeeSrcUrl: "", + cIncludes: @[], # directories to search for included files + cLibs: @[], # directories to search for lib files + cLinkedLibs: @[], # libraries to link + + externalToLink: @[], + linkOptionsCmd: "", + compileOptionsCmd: @[], + linkOptions: "", + compileOptions: "", + ccompilerpath: "", + toCompile: @[], + arguments: "", + suggestMaxResults: 10_000 + ) + setTargetFromSystem(result.target) + # enable colors by default on terminals + if terminal.isatty(stderr): + incl(result.globalOptions, optUseColors) + +proc newPartialConfigRef*(): ConfigRef = + ## create a new ConfigRef that is only good enough for error reporting. + result = ConfigRef( + selectedGC: gcRefc, + verbosity: 1, + options: DefaultOptions, + globalOptions: DefaultGlobalOptions, + foreignPackageNotes: {hintProcessing, warnUnknownMagic, + hintQuitCalled, hintExecuting}, + notes: NotesVerbosity[1], mainPackageNotes: NotesVerbosity[1]) -template compilationCachePresent*: untyped = - gSymbolFiles in {enabledSf, writeOnlySf} -# {optCaasEnabled, optSymbolFiles} * gGlobalOptions != {} +proc cppDefine*(c: ConfigRef; define: string) = + c.cppDefines.incl define -template optPreserveOrigSource*: untyped = - optEmbedOrigSrc in gGlobalOptions +proc isDefined*(conf: ConfigRef; symbol: string): bool = + if conf.symbols.hasKey(symbol): + result = conf.symbols[symbol] != "false" + elif cmpIgnoreStyle(symbol, CPU[conf.target.targetCPU].name) == 0: + result = true + elif cmpIgnoreStyle(symbol, platform.OS[conf.target.targetOS].name) == 0: + result = true + else: + case symbol.normalize + of "x86": result = conf.target.targetCPU == cpuI386 + of "itanium": result = conf.target.targetCPU == cpuIa64 + of "x8664": result = conf.target.targetCPU == cpuAmd64 + of "posix", "unix": + result = conf.target.targetOS in {osLinux, osMorphos, osSkyos, osIrix, osPalmos, + osQnx, osAtari, osAix, + osHaiku, osVxWorks, osSolaris, osNetbsd, + osFreebsd, osOpenbsd, osDragonfly, osMacosx, + osAndroid, osNintendoSwitch} + of "linux": + result = conf.target.targetOS in {osLinux, osAndroid} + of "bsd": + result = conf.target.targetOS in {osNetbsd, osFreebsd, osOpenbsd, osDragonfly} + of "emulatedthreadvars": + result = platform.OS[conf.target.targetOS].props.contains(ospLacksThreadVars) + of "msdos": result = conf.target.targetOS == osDos + of "mswindows", "win32": result = conf.target.targetOS == osWindows + of "macintosh": result = conf.target.targetOS in {osMacos, osMacosx} + of "osx": result = conf.target.targetOS == osMacosx + of "sunos": result = conf.target.targetOS == osSolaris + of "nintendoswitch": + result = conf.target.targetOS == osNintendoSwitch + of "littleendian": result = CPU[conf.target.targetCPU].endian == platform.littleEndian + of "bigendian": result = CPU[conf.target.targetCPU].endian == platform.bigEndian + of "cpu8": result = CPU[conf.target.targetCPU].bit == 8 + of "cpu16": result = CPU[conf.target.targetCPU].bit == 16 + of "cpu32": result = CPU[conf.target.targetCPU].bit == 32 + of "cpu64": result = CPU[conf.target.targetCPU].bit == 64 + of "nimrawsetjmp": + result = conf.target.targetOS in {osSolaris, osNetbsd, osFreebsd, osOpenbsd, + osDragonfly, osMacosx} + else: discard + +proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.cmd in {cmdDoc, cmdIdeTools} +proc usesWriteBarrier*(conf: ConfigRef): bool {.inline.} = conf.selectedGC >= gcRefc + +template compilationCachePresent*(conf: ConfigRef): untyped = + conf.symbolFiles in {v2Sf, writeOnlySf} + +template optPreserveOrigSource*(conf: ConfigRef): untyped = + optEmbedOrigSrc in conf.globalOptions const genSubDir* = "nimcache" @@ -175,85 +410,66 @@ const TexExt* = "tex" IniExt* = "ini" DefaultConfig* = "nim.cfg" + DefaultConfigNims* = "config.nims" DocConfig* = "nimdoc.cfg" DocTexConfig* = "nimdoc.tex.cfg" -# additional configuration variables: -var - gConfigVars* = newStringTable(modeStyleInsensitive) - gDllOverrides = newStringTable(modeCaseInsensitive) - gModuleOverrides* = newStringTable(modeStyleInsensitive) - gPrefixDir* = "" # Overrides the default prefix dir in getPrefixDir proc. - libpath* = "" - gProjectName* = "" # holds a name like 'nim' - gProjectPath* = "" # holds a path like /home/alice/projects/nim/compiler/ - gProjectFull* = "" # projectPath/projectName - gProjectIsStdin* = false # whether we're compiling from stdin - gProjectMainIdx*: int32 # the canonical path id of the main module - nimcacheDir* = "" - command* = "" # the main command (e.g. cc, check, scan, etc) - commandArgs*: seq[string] = @[] # any arguments after the main command - gKeepComments*: bool = true # whether the parser needs to keep comments - implicitImports*: seq[string] = @[] # modules that are to be implicitly imported - implicitIncludes*: seq[string] = @[] # modules that are to be implicitly included - const oKeepVariableNames* = true -template compilingLib*: bool = +template compilingLib*(conf: ConfigRef): bool = gGlobalOptions * {optGenGuiApp, optGenDynLib} != {} -proc mainCommandArg*: string = +proc mainCommandArg*(conf: ConfigRef): string = ## This is intended for commands like check or parse ## which will work on the main project file unless ## explicitly given a specific file argument - if commandArgs.len > 0: - result = commandArgs[0] + if conf.commandArgs.len > 0: + result = conf.commandArgs[0] else: - result = gProjectName + result = conf.projectName -proc existsConfigVar*(key: string): bool = - result = hasKey(gConfigVars, key) +proc existsConfigVar*(conf: ConfigRef; key: string): bool = + result = hasKey(conf.configVars, key) -proc getConfigVar*(key: string): string = - result = gConfigVars.getOrDefault key +proc getConfigVar*(conf: ConfigRef; key: string, default = ""): string = + result = conf.configVars.getOrDefault(key, default) -proc setConfigVar*(key, val: string) = - gConfigVars[key] = val +proc setConfigVar*(conf: ConfigRef; key, val: string) = + conf.configVars[key] = val -proc getOutFile*(filename, ext: string): string = - if options.outFile != "": result = options.outFile +proc getOutFile*(conf: ConfigRef; filename, ext: string): string = + if conf.outFile != "": result = conf.outFile else: result = changeFileExt(filename, ext) -proc getPrefixDir*(): string = +proc getPrefixDir*(conf: ConfigRef): string = ## Gets the prefix dir, usually the parent directory where the binary resides. ## - ## This is overridden by some tools (namely nimsuggest) via the ``gPrefixDir`` + ## This is overridden by some tools (namely nimsuggest) via the ``conf.prefixDir`` ## global. - if gPrefixDir != "": result = gPrefixDir - else: - result = splitPath(getAppDir()).head + if conf.prefixDir != "": result = conf.prefixDir + else: result = splitPath(getAppDir()).head -proc setDefaultLibpath*() = +proc setDefaultLibpath*(conf: ConfigRef) = # set default value (can be overwritten): - if libpath == "": + if conf.libpath == "": # choose default libpath: - var prefix = getPrefixDir() + var prefix = getPrefixDir(conf) when defined(posix): - if prefix == "/usr": libpath = "/usr/lib/nim" - elif prefix == "/usr/local": libpath = "/usr/local/lib/nim" - else: libpath = joinPath(prefix, "lib") - else: libpath = joinPath(prefix, "lib") + if prefix == "/usr": conf.libpath = "/usr/lib/nim" + elif prefix == "/usr/local": conf.libpath = "/usr/local/lib/nim" + else: conf.libpath = joinPath(prefix, "lib") + else: conf.libpath = joinPath(prefix, "lib") # Special rule to support other tools (nimble) which import the compiler # modules and make use of them. let realNimPath = findExe("nim") # Find out if $nim/../../lib/system.nim exists. - let parentNimLibPath = realNimPath.parentDir().parentDir() / "lib" - if not fileExists(libpath / "system.nim") and + let parentNimLibPath = realNimPath.parentDir.parentDir / "lib" + if not fileExists(conf.libpath / "system.nim") and fileExists(parentNimlibPath / "system.nim"): - libpath = parentNimLibPath + conf.libpath = parentNimLibPath -proc canonicalizePath*(path: string): string = +proc canonicalizePath*(conf: ConfigRef; path: string): string = # on Windows, 'expandFilename' calls getFullPathName which doesn't do # case corrections, so we have to use this convoluted way of retrieving # the true filename (see tests/modules and Nimble uses 'import Uri' instead @@ -265,12 +481,12 @@ proc canonicalizePath*(path: string): string = else: result = path.expandFilename -proc shortenDir*(dir: string): string = +proc shortenDir*(conf: ConfigRef; dir: string): string = ## returns the interesting part of a dir - var prefix = gProjectPath & DirSep + var prefix = conf.projectPath & DirSep if startsWith(dir, prefix): return substr(dir, len(prefix)) - prefix = getPrefixDir() & DirSep + prefix = getPrefixDir(conf) & DirSep if startsWith(dir, prefix): return substr(dir, len(prefix)) result = dir @@ -281,147 +497,148 @@ proc removeTrailingDirSep*(path: string): string = else: result = path -proc disableNimblePath*() = - gNoNimblePath = true - lazyPaths.setLen(0) +proc disableNimblePath*(conf: ConfigRef) = + incl conf.globalOptions, optNoNimblePath + conf.lazyPaths.setLen(0) include packagehandling -proc getNimcacheDir*: string = - result = if nimcacheDir.len > 0: nimcacheDir else: gProjectPath.shortenDir / - genSubDir - - -proc pathSubs*(p, config: string): string = +proc getOsCacheDir(): string = + when defined(posix): + result = getEnv("XDG_CACHE_HOME", getHomeDir() / ".cache") / "nim" + else: + result = getHomeDir() / genSubDir + +proc getNimcacheDir*(conf: ConfigRef): string = + # XXX projectName should always be without a file extension! + result = if conf.nimcacheDir.len > 0: + conf.nimcacheDir + elif conf.cmd == cmdCompileToJS: + shortenDir(conf, conf.projectPath) / genSubDir + else: getOsCacheDir() / splitFile(conf.projectName).name & + (if isDefined(conf, "release"): "_r" else: "_d") + +proc pathSubs*(conf: ConfigRef; p, config: string): string = let home = removeTrailingDirSep(os.getHomeDir()) result = unixToNativePath(p % [ - "nim", getPrefixDir(), - "lib", libpath, + "nim", getPrefixDir(conf), + "lib", conf.libpath, "home", home, "config", config, - "projectname", options.gProjectName, - "projectpath", options.gProjectPath, - "projectdir", options.gProjectPath, - "nimcache", getNimcacheDir()]) + "projectname", conf.projectName, + "projectpath", conf.projectPath, + "projectdir", conf.projectPath, + "nimcache", getNimcacheDir(conf)]) if "~/" in result: result = result.replace("~/", home & '/') -proc toGeneratedFile*(path, ext: string): string = +proc toGeneratedFile*(conf: ConfigRef; path, ext: string): string = ## converts "/home/a/mymodule.nim", "rod" to "/home/a/nimcache/mymodule.rod" var (head, tail) = splitPath(path) #if len(head) > 0: head = shortenDir(head & dirSep) - result = joinPath([getNimcacheDir(), changeFileExt(tail, ext)]) + result = joinPath([getNimcacheDir(conf), changeFileExt(tail, ext)]) #echo "toGeneratedFile(", path, ", ", ext, ") = ", result -when noTimeMachine: - var alreadyExcludedDirs = initSet[string]() - proc excludeDirFromTimeMachine(dir: string) {.raises: [].} = - ## Calls a macosx command on the directory to exclude it from backups. - ## - ## The macosx tmutil command is invoked to mark the specified path as an - ## item to be excluded from time machine backups. If a path already exists - ## with files before excluding it, newer files won't be added to the - ## directory, but previous files won't be removed from the backup until the - ## user deletes that directory. - ## - ## The whole proc is optional and will ignore all kinds of errors. The only - ## way to be sure that it works is to call ``tmutil isexcluded path``. - if alreadyExcludedDirs.contains(dir): return - alreadyExcludedDirs.incl(dir) - try: - var p = startProcess("/usr/bin/tmutil", args = ["addexclusion", dir]) - discard p.waitForExit - p.close - except Exception: - discard - -proc completeGeneratedFilePath*(f: string, createSubDir: bool = true): string = +proc completeGeneratedFilePath*(conf: ConfigRef; f: string, createSubDir: bool = true): string = var (head, tail) = splitPath(f) #if len(head) > 0: head = removeTrailingDirSep(shortenDir(head & dirSep)) - var subdir = getNimcacheDir() # / head + var subdir = getNimcacheDir(conf) # / head if createSubDir: try: createDir(subdir) - when noTimeMachine: - excludeDirFromTimeMachine(subdir) except OSError: writeLine(stdout, "cannot create directory: " & subdir) quit(1) result = joinPath(subdir, tail) #echo "completeGeneratedFilePath(", f, ") = ", result -proc rawFindFile(f: string): string = - for it in searchPaths: +proc rawFindFile(conf: ConfigRef; f: string; suppressStdlib: bool): string = + for it in conf.searchPaths: + if suppressStdlib and it.startsWith(conf.libpath): + continue result = joinPath(it, f) if existsFile(result): - return result.canonicalizePath + return canonicalizePath(conf, result) result = "" -proc rawFindFile2(f: string): string = - for i, it in lazyPaths: +proc rawFindFile2(conf: ConfigRef; f: string): string = + for i, it in conf.lazyPaths: result = joinPath(it, f) if existsFile(result): # bring to front for j in countDown(i,1): - swap(lazyPaths[j], lazyPaths[j-1]) + swap(conf.lazyPaths[j], conf.lazyPaths[j-1]) - return result.canonicalizePath + return canonicalizePath(conf, result) result = "" -template patchModule() {.dirty.} = - if result.len > 0 and gModuleOverrides.len > 0: - let key = getPackageName(result) & "_" & splitFile(result).name - if gModuleOverrides.hasKey(key): - let ov = gModuleOverrides[key] +template patchModule(conf: ConfigRef) {.dirty.} = + if result.len > 0 and conf.moduleOverrides.len > 0: + let key = getPackageName(conf, result) & "_" & splitFile(result).name + if conf.moduleOverrides.hasKey(key): + let ov = conf.moduleOverrides[key] if ov.len > 0: result = ov -proc findFile*(f: string): string {.procvar.} = +proc findFile*(conf: ConfigRef; f: string; suppressStdlib = false): string {.procvar.} = if f.isAbsolute: result = if f.existsFile: f else: "" else: - result = f.rawFindFile + result = rawFindFile(conf, f, suppressStdlib) if result.len == 0: - result = f.toLowerAscii.rawFindFile + result = rawFindFile(conf, f.toLowerAscii, suppressStdlib) if result.len == 0: - result = f.rawFindFile2 + result = rawFindFile2(conf, f) if result.len == 0: - result = f.toLowerAscii.rawFindFile2 - patchModule() + result = rawFindFile2(conf, f.toLowerAscii) + patchModule(conf) -proc findModule*(modulename, currentModule: string): string = +const stdlibDirs = [ + "pure", "core", "arch", + "pure/collections", + "pure/concurrency", "impure", + "wrappers", "wrappers/linenoise", + "windows", "posix", "js"] + +proc findModule*(conf: ConfigRef; modulename, currentModule: string): string = # returns path to module - when defined(nimfix): - # '.nimfix' modules are preferred over '.nim' modules so that specialized - # versions can be kept for 'nimfix'. - block: - let m = addFileExt(modulename, "nimfix") - let currentPath = currentModule.splitFile.dir - result = currentPath / m - if not existsFile(result): - result = findFile(m) - if existsFile(result): return result - let m = addFileExt(modulename, NimExt) - let currentPath = currentModule.splitFile.dir - result = currentPath / m - if not existsFile(result): - result = findFile(m) - patchModule() - -proc findProjectNimFile*(pkg: string): string = + const pkgPrefix = "pkg/" + const stdPrefix = "std/" + var m = addFileExt(modulename, NimExt) + if m.startsWith(pkgPrefix): + result = findFile(conf, m.substr(pkgPrefix.len), suppressStdlib = true) + else: + if m.startsWith(stdPrefix): + let stripped = m.substr(stdPrefix.len) + for candidate in stdlibDirs: + let path = (conf.libpath / candidate / stripped) + if fileExists(path): + m = path + break + let currentPath = currentModule.splitFile.dir + result = currentPath / m + if not existsFile(result): + result = findFile(conf, m) + patchModule(conf) + +proc findProjectNimFile*(conf: ConfigRef; pkg: string): string = const extensions = [".nims", ".cfg", ".nimcfg", ".nimble"] var candidates: seq[string] = @[] - for k, f in os.walkDir(pkg, relative=true): - if k == pcFile and f != "config.nims": - let (_, name, ext) = splitFile(f) - if ext in extensions: - let x = changeFileExt(pkg / name, ".nim") - if fileExists(x): - candidates.add x - for c in candidates: - # nim-foo foo or foo nfoo - if (pkg in c) or (c in pkg): return c - if candidates.len >= 1: - return candidates[0] + var dir = pkg + while true: + for k, f in os.walkDir(dir, relative=true): + if k == pcFile and f != "config.nims": + let (_, name, ext) = splitFile(f) + if ext in extensions: + let x = changeFileExt(dir / name, ".nim") + if fileExists(x): + candidates.add x + for c in candidates: + # nim-foo foo or foo nfoo + if (pkg in c) or (c in pkg): return c + if candidates.len >= 1: + return candidates[0] + dir = parentDir(dir) + if dir == "": break return "" proc canonDynlibName(s: string): string = @@ -432,25 +649,12 @@ proc canonDynlibName(s: string): string = else: result = s.substr(start) -proc inclDynlibOverride*(lib: string) = - gDllOverrides[lib.canonDynlibName] = "true" - -proc isDynlibOverride*(lib: string): bool = - result = gDynlibOverrideAll or gDllOverrides.hasKey(lib.canonDynlibName) - -proc binaryStrSearch*(x: openArray[string], y: string): int = - var a = 0 - var b = len(x) - 1 - while a <= b: - var mid = (a + b) div 2 - var c = cmpIgnoreCase(x[mid], y) - if c < 0: - a = mid + 1 - elif c > 0: - b = mid - 1 - else: - return mid - result = - 1 +proc inclDynlibOverride*(conf: ConfigRef; lib: string) = + conf.dllOverrides[lib.canonDynlibName] = "true" + +proc isDynlibOverride*(conf: ConfigRef; lib: string): bool = + result = optDynlibOverrideAll in conf.globalOptions or + conf.dllOverrides.hasKey(lib.canonDynlibName) proc parseIdeCmd*(s: string): IdeCmd = case s: diff --git a/compiler/packagehandling.nim b/compiler/packagehandling.nim index 758411e39..7414aeb71 100644 --- a/compiler/packagehandling.nim +++ b/compiler/packagehandling.nim @@ -15,23 +15,16 @@ iterator myParentDirs(p: string): string = if current.len == 0: break yield current -template newPackageCache(): untyped = - newStringTable(when FileSystemCaseSensitive: - modeCaseInsensitive - else: - modeCaseSensitive) +proc resetPackageCache*(conf: ConfigRef) = + conf.packageCache = newPackageCache() -var packageCache = newPackageCache() - -proc resetPackageCache*() = packageCache = newPackageCache() - -proc getPackageName*(path: string): string = +proc getPackageName*(conf: ConfigRef; path: string): string = var parents = 0 block packageSearch: for d in myParentDirs(path): - if packageCache.hasKey(d): + if conf.packageCache.hasKey(d): #echo "from cache ", d, " |", packageCache[d], "|", path.splitFile.name - return packageCache[d] + return conf.packageCache[d] inc parents for file in walkFiles(d / "*.nimble"): result = file.splitFile.name @@ -40,15 +33,16 @@ proc getPackageName*(path: string): string = result = file.splitFile.name break packageSearch # we also store if we didn't find anything: - if result.isNil: result = "" + when not defined(nimNoNilSeqs): + if result.isNil: result = "" for d in myParentDirs(path): #echo "set cache ", d, " |", result, "|", parents - packageCache[d] = result + conf.packageCache[d] = result dec parents if parents <= 0: break -proc withPackageName*(path: string): string = - let x = path.getPackageName +proc withPackageName*(conf: ConfigRef; path: string): string = + let x = getPackageName(conf, path) if x.len == 0: result = path else: diff --git a/compiler/parampatterns.nim b/compiler/parampatterns.nim index aacd25795..bbaf7a069 100644 --- a/compiler/parampatterns.nim +++ b/compiler/parampatterns.nim @@ -10,7 +10,8 @@ ## This module implements the pattern matching features for term rewriting ## macro support. -import strutils, ast, astalgo, types, msgs, idents, renderer, wordrecg, trees +import strutils, ast, astalgo, types, msgs, idents, renderer, wordrecg, trees, + options # we precompile the pattern here for efficiency into some internal # stack based VM :-) Why? Because it's fun; I did no benchmarks to see if that @@ -41,8 +42,8 @@ type const MaxStackSize* = 64 ## max required stack size by the VM -proc patternError(n: PNode) = - localError(n.info, errIllFormedAstX, renderTree(n, {renderNoComments})) +proc patternError(n: PNode; conf: ConfigRef) = + localError(conf, n.info, "illformed AST: " & renderTree(n, {renderNoComments})) proc add(code: var TPatternCode, op: TOpcode) {.inline.} = add(code, chr(ord(op))) @@ -53,42 +54,42 @@ proc whichAlias*(p: PSym): TAliasRequest = else: result = aqNone -proc compileConstraints(p: PNode, result: var TPatternCode) = +proc compileConstraints(p: PNode, result: var TPatternCode; conf: ConfigRef) = case p.kind of nkCallKinds: if p.sons[0].kind != nkIdent: - patternError(p.sons[0]) + patternError(p.sons[0], conf) return let op = p.sons[0].ident if p.len == 3: if op.s == "|" or op.id == ord(wOr): - compileConstraints(p.sons[1], result) - compileConstraints(p.sons[2], result) + compileConstraints(p.sons[1], result, conf) + compileConstraints(p.sons[2], result, conf) result.add(ppOr) elif op.s == "&" or op.id == ord(wAnd): - compileConstraints(p.sons[1], result) - compileConstraints(p.sons[2], result) + compileConstraints(p.sons[1], result, conf) + compileConstraints(p.sons[2], result, conf) result.add(ppAnd) else: - patternError(p) + patternError(p, conf) elif p.len == 2 and (op.s == "~" or op.id == ord(wNot)): - compileConstraints(p.sons[1], result) + compileConstraints(p.sons[1], result, conf) result.add(ppNot) else: - patternError(p) + patternError(p, conf) of nkAccQuoted, nkPar: if p.len == 1: - compileConstraints(p.sons[0], result) + compileConstraints(p.sons[0], result, conf) else: - patternError(p) + patternError(p, conf) of nkIdent: let spec = p.ident.s.normalize case spec - of "atom": result.add(ppAtom) - of "lit": result.add(ppLit) - of "sym": result.add(ppSym) + of "atom": result.add(ppAtom) + of "lit": result.add(ppLit) + of "sym": result.add(ppSym) of "ident": result.add(ppIdent) - of "call": result.add(ppCall) + of "call": result.add(ppCall) of "alias": result[0] = chr(aqShouldAlias.ord) of "noalias": result[0] = chr(aqNoAlias.ord) of "lvalue": result.add(ppLValue) @@ -97,24 +98,24 @@ proc compileConstraints(p: PNode, result: var TPatternCode) = of "nosideeffect": result.add(ppNoSideEffect) else: # check all symkinds: - internalAssert int(high(TSymKind)) < 255 + internalAssert conf, int(high(TSymKind)) < 255 for i in low(TSymKind)..high(TSymKind): if cmpIgnoreStyle(($i).substr(2), spec) == 0: result.add(ppSymKind) result.add(chr(i.ord)) return # check all nodekinds: - internalAssert int(high(TNodeKind)) < 255 + internalAssert conf, int(high(TNodeKind)) < 255 for i in low(TNodeKind)..high(TNodeKind): if cmpIgnoreStyle($i, spec) == 0: result.add(ppNodeKind) result.add(chr(i.ord)) return - patternError(p) + patternError(p, conf) else: - patternError(p) + patternError(p, conf) -proc semNodeKindConstraints*(p: PNode): PNode = +proc semNodeKindConstraints*(p: PNode; conf: ConfigRef): PNode = ## does semantic checking for a node kind pattern and compiles it into an ## efficient internal format. assert p.kind == nkCurlyExpr @@ -123,11 +124,11 @@ proc semNodeKindConstraints*(p: PNode): PNode = result.strVal.add(chr(aqNone.ord)) if p.len >= 2: for i in 1..<p.len: - compileConstraints(p.sons[i], result.strVal) + compileConstraints(p.sons[i], result.strVal, conf) if result.strVal.len > MaxStackSize-1: - internalError(p.info, "parameter pattern too complex") + internalError(conf, p.info, "parameter pattern too complex") else: - patternError(p) + patternError(p, conf) result.strVal.add(ppEof) type @@ -178,6 +179,36 @@ type arDiscriminant, # is a discriminant arStrange # it is a strange beast like 'typedesc[var T]' +proc exprRoot*(n: PNode): PSym = + var it = n + while true: + case it.kind + of nkSym: return it.sym + of nkHiddenDeref, nkDerefExpr: + if it[0].typ.skipTypes(abstractInst).kind in {tyPtr, tyRef}: + # 'ptr' is unsafe anyway and 'ref' is always on the heap, + # so allow these derefs: + break + else: + it = it[0] + of nkDotExpr, nkBracketExpr, nkHiddenAddr, + nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr: + it = it[0] + of nkHiddenStdConv, nkHiddenSubConv, nkConv: + it = it[1] + of nkStmtList, nkStmtListExpr: + if it.len > 0 and it.typ != nil: it = it.lastSon + else: break + of nkCallKinds: + if it.typ != nil and it.typ.kind == tyVar and it.len > 1: + # See RFC #7373, calls returning 'var T' are assumed to + # return a view into the first argument (if there is one): + it = it[1] + else: + break + else: + break + proc isAssignable*(owner: PSym, n: PNode; isUnsafeAddr=false): TAssignableResult = ## 'owner' can be nil! result = arNone @@ -186,10 +217,10 @@ proc isAssignable*(owner: PSym, n: PNode; isUnsafeAddr=false): TAssignableResult if n.typ != nil and n.typ.kind == tyVar: result = arLValue of nkSym: - let kinds = if isUnsafeAddr: {skVar, skResult, skTemp, skParam, skLet} + let kinds = if isUnsafeAddr: {skVar, skResult, skTemp, skParam, skLet, skForVar} else: {skVar, skResult, skTemp} if n.sym.kind in kinds: - if owner != nil and owner.id == n.sym.owner.id and + if owner != nil and owner == n.sym.owner and sfGlobal notin n.sym.flags: result = arLocalLValue else: @@ -205,7 +236,8 @@ proc isAssignable*(owner: PSym, n: PNode; isUnsafeAddr=false): TAssignableResult result = arLValue else: result = isAssignable(owner, n.sons[0], isUnsafeAddr) - if result != arNone and sfDiscriminant in n.sons[1].sym.flags: + if result != arNone and n[1].kind == nkSym and + sfDiscriminant in n[1].sym.flags: result = arDiscriminant of nkBracketExpr: if skipTypes(n.sons[0].typ, abstractInst-{tyTypeDesc}).kind in diff --git a/compiler/parser.nim b/compiler/parser.nim index a5428a229..9f1b947f6 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -17,6 +17,8 @@ # In fact the grammar is generated from this file: when isMainModule: + # Leave a note in grammar.txt that it is generated: + #| # This file is generated by compiler/parser.nim. import pegs var outp = open("doc/grammar.txt", fmWrite) for line in lines("compiler/parser.nim"): @@ -25,7 +27,10 @@ when isMainModule: outp.close import - llstream, lexer, idents, strutils, ast, astalgo, msgs + llstream, lexer, idents, strutils, ast, astalgo, msgs, options, lineinfos + +when defined(nimpretty2): + import layouter type TParser* = object # A TParser object represents a file that @@ -38,10 +43,16 @@ type tok*: TToken # The current token inPragma*: int # Pragma level inSemiStmtList*: int + emptyNode: PNode + when defined(nimpretty2): + em: Emitter SymbolMode = enum smNormal, smAllowNil, smAfterDot + TPrimaryMode = enum + pmNormal, pmTypeDesc, pmTypeDef, pmSkipSuffix + proc parseAll*(p: var TParser): PNode proc closeParser*(p: var TParser) proc parseTopLevelStmt*(p: var TParser): PNode @@ -73,6 +84,9 @@ proc parsePragma(p: var TParser): PNode proc postExprBlocks(p: var TParser, x: PNode): PNode proc parseExprStmt(p: var TParser): PNode proc parseBlock(p: var TParser): PNode +proc primary(p: var TParser, mode: TPrimaryMode): PNode +proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode + # implementation proc getTok(p: var TParser) = @@ -80,34 +94,48 @@ proc getTok(p: var TParser) = ## `tok` member. rawGetTok(p.lex, p.tok) p.hasProgress = true - -proc openParser*(p: var TParser, fileIdx: int32, inputStream: PLLStream, - cache: IdentCache; + when defined(nimpretty2): + emitTok(p.em, p.lex, p.tok) + while p.tok.tokType == tkComment: + rawGetTok(p.lex, p.tok) + emitTok(p.em, p.lex, p.tok) + +proc openParser*(p: var TParser, fileIdx: FileIndex, inputStream: PLLStream, + cache: IdentCache; config: ConfigRef; strongSpaces=false) = ## Open a parser, using the given arguments to set up its internal state. ## initToken(p.tok) - openLexer(p.lex, fileIdx, inputStream, cache) + openLexer(p.lex, fileIdx, inputStream, cache, config) + when defined(nimpretty2): + openEmitter(p.em, cache, config, fileIdx) getTok(p) # read the first token p.firstTok = true p.strongSpaces = strongSpaces + p.emptyNode = newNode(nkEmpty) proc openParser*(p: var TParser, filename: string, inputStream: PLLStream, - cache: IdentCache; + cache: IdentCache; config: ConfigRef; strongSpaces=false) = - openParser(p, filename.fileInfoIdx, inputStream, cache, strongSpaces) + openParser(p, fileInfoIdx(config, filename), inputStream, cache, config, strongSpaces) proc closeParser(p: var TParser) = ## Close a parser, freeing up its resources. closeLexer(p.lex) + when defined(nimpretty2): + closeEmitter(p.em) proc parMessage(p: TParser, msg: TMsgKind, arg = "") = ## Produce and emit the parser message `arg` to output. lexMessageTok(p.lex, msg, p.tok, arg) -proc parMessage(p: TParser, msg: TMsgKind, tok: TToken) = +proc parMessage(p: TParser, msg: string, tok: TToken) = ## Produce and emit a parser message to output about the token `tok` - parMessage(p, msg, prettyTok(tok)) + parMessage(p, errGenerated, msg % prettyTok(tok)) + +proc parMessage(p: TParser, arg: string) = + ## Produce and emit the parser message `arg` to output. + lexMessageTok(p.lex, errGenerated, p.tok, arg) template withInd(p, body: untyped) = let oldInd = p.currInd @@ -122,8 +150,15 @@ template sameOrNoInd(p): bool = p.tok.indent == p.currInd or p.tok.indent < 0 proc rawSkipComment(p: var TParser, node: PNode) = if p.tok.tokType == tkComment: if node != nil: - if node.comment == nil: node.comment = "" - add(node.comment, p.tok.literal) + when not defined(nimNoNilSeqs): + if node.comment == nil: node.comment = "" + when defined(nimpretty): + if p.tok.commentOffsetB > p.tok.commentOffsetA: + add node.comment, fileSection(p.lex.config, p.lex.fileIdx, p.tok.commentOffsetA, p.tok.commentOffsetB) + else: + add node.comment, p.tok.literal + else: + add(node.comment, p.tok.literal) else: parMessage(p, errInternal, "skipComment") getTok(p) @@ -134,6 +169,12 @@ proc skipComment(p: var TParser, node: PNode) = proc flexComment(p: var TParser, node: PNode) = if p.tok.indent < 0 or realInd(p): rawSkipComment(p, node) +const + errInvalidIndentation = "invalid indentation" + errIdentifierExpected = "identifier expected, but got '$1'" + errExprExpected = "expression expected, but found '$1'" + errTokenExpected = "'$1' expected" + proc skipInd(p: var TParser) = if p.tok.indent >= 0: if not realInd(p): parMessage(p, errInvalidIndentation) @@ -152,11 +193,11 @@ proc getTokNoInd(p: var TParser) = proc expectIdentOrKeyw(p: TParser) = if p.tok.tokType != tkSymbol and not isKeyword(p.tok.tokType): - lexMessage(p.lex, errIdentifierExpected, prettyTok(p.tok)) + lexMessage(p.lex, errGenerated, errIdentifierExpected % prettyTok(p.tok)) proc expectIdent(p: TParser) = if p.tok.tokType != tkSymbol: - lexMessage(p.lex, errIdentifierExpected, prettyTok(p.tok)) + lexMessage(p.lex, errGenerated, errIdentifierExpected % prettyTok(p.tok)) proc eat(p: var TParser, tokType: TTokType) = ## Move the parser to the next token if the current token is of type @@ -164,7 +205,8 @@ proc eat(p: var TParser, tokType: TTokType) = if p.tok.tokType == tokType: getTok(p) else: - lexMessageTok(p.lex, errTokenExpected, p.tok, TokTypeToStr[tokType]) + lexMessage(p.lex, errGenerated, + "expected: '" & TokTypeToStr[tokType] & "', but got: '" & prettyTok(p.tok) & "'") proc parLineInfo(p: TParser): TLineInfo = ## Retrieve the line information associated with the parser's current state. @@ -210,41 +252,6 @@ proc isRightAssociative(tok: TToken): bool {.inline.} = result = tok.tokType == tkOpr and tok.ident.s[0] == '^' # or (let L = tok.ident.s.len; L > 1 and tok.ident.s[L-1] == '>')) -proc getPrecedence(tok: TToken, strongSpaces: bool): int = - ## Calculates the precedence of the given token. - template considerStrongSpaces(x): untyped = - x + (if strongSpaces: 100 - tok.strongSpaceA.int*10 else: 0) - - case tok.tokType - of tkOpr: - let L = tok.ident.s.len - let relevantChar = tok.ident.s[0] - - # arrow like? - if L > 1 and tok.ident.s[L-1] == '>' and - tok.ident.s[L-2] in {'-', '~', '='}: return considerStrongSpaces(1) - - template considerAsgn(value: untyped) = - result = if tok.ident.s[L-1] == '=': 1 else: value - - case relevantChar - of '$', '^': considerAsgn(10) - of '*', '%', '/', '\\': considerAsgn(9) - of '~': result = 8 - of '+', '-', '|': considerAsgn(8) - of '&': considerAsgn(7) - of '=', '<', '>', '!': result = 5 - of '.': considerAsgn(6) - of '?': result = 2 - else: considerAsgn(2) - of tkDiv, tkMod, tkShl, tkShr: result = 9 - of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5 - of tkDotDot: result = 6 - of tkAnd: result = 4 - of tkOr, tkXor, tkPtr, tkRef: result = 3 - else: return -10 - result = considerStrongSpaces(result) - proc isOperator(tok: TToken): bool = ## Determines if the given token is an operator type token. tok.tokType in {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs, @@ -260,13 +267,9 @@ proc isUnary(p: TParser): bool = proc checkBinary(p: TParser) {.inline.} = ## Check if the current parser token is a binary operator. # we don't check '..' here as that's too annoying - if p.strongSpaces and p.tok.tokType == tkOpr: + if p.tok.tokType == tkOpr: if p.tok.strongSpaceB > 0 and p.tok.strongSpaceA != p.tok.strongSpaceB: - parMessage(p, errGenerated, - "Number of spaces around '$#' not consistent" % - prettyTok(p.tok)) - elif p.tok.strongSpaceA notin {0,1,2,4,8}: - parMessage(p, errGenerated, "Number of spaces must be 0,1,2,4 or 8") + parMessage(p, warnInconsistentSpacing, prettyTok(p.tok)) #| module = stmt ^* (';' / IND{=}) #| @@ -301,6 +304,8 @@ proc colcom(p: var TParser, n: PNode) = eat(p, tkColon) skipComment(p, n) +const tkBuiltInMagics = {tkType, tkStatic, tkAddr} + proc parseSymbol(p: var TParser, mode = smNormal): PNode = #| symbol = '`' (KEYW|IDENT|literal|(operator|'('|')'|'['|']'|'{'|'}'|'=')+)+ '`' #| | IDENT | KEYW @@ -309,7 +314,7 @@ proc parseSymbol(p: var TParser, mode = smNormal): PNode = result = newIdentNodeP(p.tok.ident, p) getTok(p) of tokKeywordLow..tokKeywordHigh: - if p.tok.tokType == tkAddr or p.tok.tokType == tkType or mode == smAfterDot: + if p.tok.tokType in tkBuiltInMagics or mode == smAfterDot: # for backwards compatibility these 2 are always valid: result = newIdentNodeP(p.tok.ident, p) getTok(p) @@ -318,7 +323,7 @@ proc parseSymbol(p: var TParser, mode = smNormal): PNode = getTok(p) else: parMessage(p, errIdentifierExpected, p.tok) - result = ast.emptyNode + result = p.emptyNode of tkAccent: result = newNodeP(nkAccQuoted, p) getTok(p) @@ -349,7 +354,7 @@ proc parseSymbol(p: var TParser, mode = smNormal): PNode = # But: this really sucks for idetools and keywords, so we don't do it # if it is a keyword: #if not isKeyword(p.tok.tokType): getTok(p) - result = ast.emptyNode + result = p.emptyNode proc colonOrEquals(p: var TParser, a: PNode): PNode = if p.tok.tokType == tkColon: @@ -377,6 +382,8 @@ proc exprColonEqExpr(p: var TParser): PNode = proc exprList(p: var TParser, endTok: TTokType, result: PNode) = #| exprList = expr ^+ comma + when defined(nimpretty2): + inc p.em.doIndentMore getTok(p) optInd(p, result) # progress guaranteed @@ -386,20 +393,8 @@ proc exprList(p: var TParser, endTok: TTokType, result: PNode) = if p.tok.tokType != tkComma: break getTok(p) optInd(p, a) - -proc dotExpr(p: var TParser, a: PNode): PNode = - #| dotExpr = expr '.' optInd symbol - var info = p.parLineInfo - getTok(p) - result = newNodeI(nkDotExpr, info) - optInd(p, result) - addSon(result, a) - addSon(result, parseSymbol(p, smAfterDot)) - -proc qualifiedIdent(p: var TParser): PNode = - #| qualifiedIdent = symbol ('.' optInd symbol)? - result = parseSymbol(p) - if p.tok.tokType == tkDot: result = dotExpr(p, result) + when defined(nimpretty2): + dec p.em.doIndentMore proc exprColonEqExprListAux(p: var TParser, endTok: TTokType, result: PNode) = assert(endTok in {tkCurlyRi, tkCurlyDotRi, tkBracketRi, tkParRi}) @@ -411,6 +406,9 @@ proc exprColonEqExprListAux(p: var TParser, endTok: TTokType, result: PNode) = addSon(result, a) if p.tok.tokType != tkComma: break getTok(p) + # (1,) produces a tuple expression + if endTok == tkParRi and p.tok.tokType == tkParRi and result.kind == nkPar: + result.kind = nkTupleConstr skipComment(p, a) optPar(p) eat(p, endTok) @@ -421,6 +419,33 @@ proc exprColonEqExprList(p: var TParser, kind: TNodeKind, result = newNodeP(kind, p) exprColonEqExprListAux(p, endTok, result) +proc dotExpr(p: var TParser, a: PNode): PNode = + #| dotExpr = expr '.' optInd (symbol | '[:' exprList ']') + #| explicitGenericInstantiation = '[:' exprList ']' ( '(' exprColonEqExpr ')' )? + var info = p.parLineInfo + getTok(p) + result = newNodeI(nkDotExpr, info) + optInd(p, result) + addSon(result, a) + addSon(result, parseSymbol(p, smAfterDot)) + if p.tok.tokType == tkBracketLeColon and p.tok.strongSpaceA <= 0: + var x = newNodeI(nkBracketExpr, p.parLineInfo) + # rewrite 'x.y[:z]()' to 'y[z](x)' + x.add result[1] + exprList(p, tkBracketRi, x) + eat(p, tkBracketRi) + var y = newNodeI(nkCall, p.parLineInfo) + y.add x + y.add result[0] + if p.tok.tokType == tkParLe and p.tok.strongSpaceA <= 0: + exprColonEqExprListAux(p, tkParRi, y) + result = y + +proc qualifiedIdent(p: var TParser): PNode = + #| qualifiedIdent = symbol ('.' optInd symbol)? + result = parseSymbol(p) + if p.tok.tokType == tkDot: result = dotExpr(p, result) + proc setOrTableConstr(p: var TParser): PNode = #| setOrTableConstr = '{' ((exprColonEqExpr comma)* | ':' ) '}' result = newNodeP(nkCurly, p) @@ -478,9 +503,6 @@ proc parseGStrLit(p: var TParser, a: PNode): PNode = else: result = a -type - TPrimaryMode = enum pmNormal, pmTypeDesc, pmTypeDef, pmSkipSuffix - proc complexOrSimpleStmt(p: var TParser): PNode proc simpleExpr(p: var TParser, mode = pmNormal): PNode @@ -512,6 +534,7 @@ proc parsePar(p: var TParser): PNode = result = newNodeP(nkPar, p) getTok(p) optInd(p, result) + flexComment(p, result) if p.tok.tokType in {tkDiscard, tkInclude, tkIf, tkWhile, tkCase, tkTry, tkDefer, tkFinally, tkExcept, tkFor, tkBlock, tkConst, tkLet, tkWhen, tkVar, tkFor, @@ -551,6 +574,9 @@ proc parsePar(p: var TParser): PNode = if p.tok.tokType == tkComma: getTok(p) skipComment(p, a) + # (1,) produces a tuple expression: + if p.tok.tokType == tkParRi: + result.kind = nkTupleConstr # progress guaranteed while p.tok.tokType != tkParRi and p.tok.tokType != tkEof: var a = exprColonEqExpr(p) @@ -575,7 +601,7 @@ proc identOrLiteral(p: var TParser, mode: TPrimaryMode): PNode = #| tupleConstr = '(' optInd (exprColonEqExpr comma?)* optPar ')' #| arrayConstr = '[' optInd (exprColonEqExpr comma?)* optPar ']' case p.tok.tokType - of tkSymbol, tkType, tkAddr: + of tkSymbol, tkBuiltInMagics: result = newIdentNodeP(p.tok.ident, p) getTok(p) result = parseGStrLit(p, result) @@ -669,7 +695,7 @@ proc identOrLiteral(p: var TParser, mode: TPrimaryMode): PNode = else: parMessage(p, errExprExpected, p.tok) getTok(p) # we must consume a token here to prevend endless loops! - result = ast.emptyNode + result = p.emptyNode proc namedParams(p: var TParser, callee: PNode, kind: TNodeKind, endTok: TTokType): PNode = @@ -679,12 +705,24 @@ proc namedParams(p: var TParser, callee: PNode, # progress guaranteed exprColonEqExprListAux(p, endTok, result) -proc commandParam(p: var TParser): PNode = +proc commandParam(p: var TParser, isFirstParam: var bool): PNode = result = parseExpr(p) if p.tok.tokType == tkDo: result = postExprBlocks(p, result) + elif p.tok.tokType == tkEquals and not isFirstParam: + let lhs = result + result = newNodeP(nkExprEqExpr, p) + getTok(p) + addSon(result, lhs) + addSon(result, parseExpr(p)) + isFirstParam = false -proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode = +const + tkTypeClasses = {tkRef, tkPtr, tkVar, tkStatic, tkType, + tkEnum, tkTuple, tkObject, tkProc} + +proc primarySuffix(p: var TParser, r: PNode, + baseIndent: int, mode: TPrimaryMode): PNode = #| primarySuffix = '(' (exprColonEqExpr comma?)* ')' doBlocks? #| | doBlocks #| | '.' optInd symbol generalizedLit? @@ -701,7 +739,14 @@ proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode = case p.tok.tokType of tkParLe: # progress guaranteed - somePar() + if p.tok.strongSpaceA > 0: + # inside type sections, expressions such as `ref (int, bar)` + # are parsed as a nkCommand with a single tuple argument (nkPar) + if mode == pmTypeDef: + result = newNodeP(nkCommand, p) + result.addSon r + result.addSon primary(p, pmNormal) + break result = namedParams(p, result, nkCall, tkParRi) if result.len > 1 and result.sons[1].kind == nkExprColonExpr: result.kind = nkObjConstr @@ -717,17 +762,24 @@ proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode = # progress guaranteed somePar() result = namedParams(p, result, nkCurlyExpr, tkCurlyRi) - of tkSymbol, tkAccent, tkIntLit..tkCharLit, tkNil, tkCast, tkAddr, tkType: - if p.inPragma == 0: + of tkSymbol, tkAccent, tkIntLit..tkCharLit, tkNil, tkCast, + tkOpr, tkDotDot, tkTypeClasses - {tkRef, tkPtr}: + # XXX: In type sections we allow the free application of the + # command syntax, with the exception of expressions such as + # `foo ref` or `foo ptr`. Unfortunately, these two are also + # used as infix operators for the memory regions feature and + # the current parsing rules don't play well here. + if p.inPragma == 0 and (isUnary(p) or p.tok.tokType notin {tkOpr, tkDotDot}): # actually parsing {.push hints:off.} as {.push(hints:off).} is a sweet # solution, but pragmas.nim can't handle that let a = result result = newNodeP(nkCommand, p) addSon(result, a) + var isFirstParam = true when true: # progress NOT guaranteed p.hasProgress = false - addSon result, commandParam(p) + addSon result, commandParam(p, isFirstParam) if not p.hasProgress: break else: while p.tok.tokType != tkEof: @@ -741,9 +793,6 @@ proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode = else: break -proc primary(p: var TParser, mode: TPrimaryMode): PNode -proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode - proc parseOperators(p: var TParser, headNode: PNode, limit: int, mode: TPrimaryMode): PNode = result = headNode @@ -778,7 +827,11 @@ proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode = result = parseOperators(p, result, limit, mode) proc simpleExpr(p: var TParser, mode = pmNormal): PNode = + when defined(nimpretty2): + inc p.em.doIndentMore result = simpleExprAux(p, -1, mode) + when defined(nimpretty2): + dec p.em.doIndentMore proc parseIfExpr(p: var TParser, kind: TNodeKind): PNode = #| condExpr = expr colcom expr optInd @@ -818,7 +871,6 @@ proc parseIfExpr(p: var TParser, kind: TNodeKind): PNode = if realInd(p): p.currInd = p.tok.indent wasIndented = true - echo result.info, " yes ", p.currInd addSon(branch, parseExpr(p)) result.add branch while sameInd(p) or not wasIndented: @@ -854,8 +906,12 @@ proc parsePragma(p: var TParser): PNode = getTok(p) skipComment(p, a) optPar(p) - if p.tok.tokType in {tkCurlyDotRi, tkCurlyRi}: getTok(p) - else: parMessage(p, errTokenExpected, ".}") + if p.tok.tokType in {tkCurlyDotRi, tkCurlyRi}: + when defined(nimpretty2): + if p.tok.tokType == tkCurlyRi: curlyRiWasPragma(p.em) + getTok(p) + else: + parMessage(p, "expected '.}'") dec p.inPragma proc identVis(p: var TParser; allowDot=false): PNode = @@ -863,6 +919,8 @@ proc identVis(p: var TParser; allowDot=false): PNode = #| identVisDot = symbol '.' optInd symbol opr? var a = parseSymbol(p) if p.tok.tokType == tkOpr: + when defined(nimpretty2): + starWasExportMarker(p.em) result = newNodeP(nkPostfix, p) addSon(result, newIdentNodeP(p.tok.ident, p)) addSon(result, a) @@ -916,7 +974,7 @@ proc parseIdentColonEquals(p: var TParser, flags: TDeclaredIdentFlags): PNode = else: addSon(result, newNodeP(nkEmpty, p)) if p.tok.tokType != tkEquals and withBothOptional notin flags: - parMessage(p, errColonOrEqualsExpected, p.tok) + parMessage(p, "':' or '=' expected, but got '$1'", p.tok) if p.tok.tokType == tkEquals: getTok(p) optInd(p, result) @@ -940,6 +998,8 @@ proc parseTuple(p: var TParser, indentAllowed = false): PNode = var a = parseIdentColonEquals(p, {}) addSon(result, a) if p.tok.tokType notin {tkComma, tkSemiColon}: break + when defined(nimpretty2): + commaWasSemicolon(p.em) getTok(p) skipComment(p, a) optPar(p) @@ -962,6 +1022,8 @@ proc parseTuple(p: var TParser, indentAllowed = false): PNode = parMessage(p, errIdentifierExpected, p.tok) break if not sameInd(p): break + elif p.tok.tokType == tkParLe: + parMessage(p, errGenerated, "the syntax for tuple types is 'tuple[...]', not 'tuple(...)'") else: result = newNodeP(nkTupleClassTy, p) @@ -971,7 +1033,9 @@ proc parseParamList(p: var TParser, retColon = true): PNode = #| paramListColon = paramList? (':' optInd typeDesc)? var a: PNode result = newNodeP(nkFormalParams, p) - addSon(result, ast.emptyNode) # return type + addSon(result, p.emptyNode) # return type + when defined(nimpretty2): + inc p.em.doIndentMore let hasParLe = p.tok.tokType == tkParLe and p.tok.indent < 0 if hasParLe: getTok(p) @@ -983,11 +1047,16 @@ proc parseParamList(p: var TParser, retColon = true): PNode = a = parseIdentColonEquals(p, {withBothOptional, withPragma}) of tkParRi: break + of tkVar: + parMessage(p, errGenerated, "the syntax is 'parameter: var T', not 'var parameter: T'") + break else: - parMessage(p, errTokenExpected, ")") + parMessage(p, "expected closing ')'") break addSon(result, a) if p.tok.tokType notin {tkComma, tkSemiColon}: break + when defined(nimpretty2): + commaWasSemicolon(p.em) getTok(p) skipComment(p, a) optPar(p) @@ -1000,13 +1069,15 @@ proc parseParamList(p: var TParser, retColon = true): PNode = result.sons[0] = parseTypeDesc(p) elif not retColon and not hasParle: # Mark as "not there" in order to mark for deprecation in the semantic pass: - result = ast.emptyNode + result = p.emptyNode + when defined(nimpretty2): + dec p.em.doIndentMore proc optPragmas(p: var TParser): PNode = if p.tok.tokType == tkCurlyDotLe and (p.tok.indent < 0 or realInd(p)): result = parsePragma(p) else: - result = ast.emptyNode + result = p.emptyNode proc parseDoBlock(p: var TParser; info: TLineInfo): PNode = #| doBlock = 'do' paramListArrow pragmas? colcom stmt @@ -1015,7 +1086,9 @@ proc parseDoBlock(p: var TParser; info: TLineInfo): PNode = colcom(p, result) result = parseStmt(p) if params.kind != nkEmpty: - result = newProcNode(nkDo, info, result, params = params, pragmas = pragmas) + result = newProcNode(nkDo, info, + body = result, params = params, name = p.emptyNode, pattern = p.emptyNode, + genericParams = p.emptyNode, pragmas = pragmas, exceptions = p.emptyNode) proc parseProcExpr(p: var TParser; isExpr: bool; kind: TNodeKind): PNode = #| procExpr = 'proc' paramListColon pragmas? ('=' COMMENT? stmt)? @@ -1028,9 +1101,9 @@ proc parseProcExpr(p: var TParser; isExpr: bool; kind: TNodeKind): PNode = if p.tok.tokType == tkEquals and isExpr: getTok(p) skipComment(p, result) - result = newProcNode(kind, info, parseStmt(p), - params = params, - pragmas = pragmas) + result = newProcNode(kind, info, body = parseStmt(p), + params = params, name = p.emptyNode, pattern = p.emptyNode, + genericParams = p.emptyNode, pragmas = pragmas, exceptions = p.emptyNode) else: result = newNodeI(nkProcTy, info) if hasSignature: @@ -1040,9 +1113,9 @@ proc parseProcExpr(p: var TParser; isExpr: bool; kind: TNodeKind): PNode = proc isExprStart(p: TParser): bool = case p.tok.tokType of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf, tkFor, - tkProc, tkFunc, tkIterator, tkBind, tkAddr, + tkProc, tkFunc, tkIterator, tkBind, tkBuiltInMagics, tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCharLit, tkVar, tkRef, tkPtr, - tkTuple, tkObject, tkType, tkWhen, tkCase, tkOut: + tkTuple, tkObject, tkWhen, tkCase, tkOut: result = true else: result = false @@ -1061,6 +1134,7 @@ proc parseTypeDescKAux(p: var TParser, kind: TNodeKind, #| distinct = 'distinct' optInd typeDesc result = newNodeP(kind, p) getTok(p) + if p.tok.indent != -1 and p.tok.indent <= p.currInd: return optInd(p, result) if not isOperator(p.tok) and isExprStart(p): addSon(result, primary(p, mode)) @@ -1121,7 +1195,6 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = #| | 'proc' | 'iterator' | 'distinct' | 'object' | 'enum' #| primary = typeKeyw typeDescK #| / prefixOperator* identOrLiteral primarySuffix* - #| / 'static' primary #| / 'bind' primary if isOperator(p.tok): let isSigil = isSigilLike(p.tok) @@ -1134,7 +1207,7 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = #XXX prefix operators let baseInd = p.lex.currLineIndent addSon(result, primary(p, pmSkipSuffix)) - result = primarySuffix(p, result, baseInd) + result = primarySuffix(p, result, baseInd, mode) else: addSon(result, primary(p, pmNormal)) return @@ -1163,15 +1236,7 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = if mode == pmTypeDef: result = parseTypeClass(p) else: - parMessage(p, errInvalidToken, p.tok) - of tkStatic: - let info = parLineInfo(p) - getTokNoInd(p) - let next = primary(p, pmNormal) - if next.kind == nkBracket and next.sonsLen == 1: - result = newNode(nkStaticTy, info, @[next.sons[0]]) - else: - result = newNode(nkStaticExpr, info, @[next]) + parMessage(p, "the 'concept' keyword is only valid in 'type' sections") of tkBind: result = newNodeP(nkBind, p) getTok(p) @@ -1186,7 +1251,7 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = let baseInd = p.lex.currLineIndent result = identOrLiteral(p, mode) if mode != pmSkipSuffix: - result = primarySuffix(p, result, baseInd) + result = primarySuffix(p, result, baseInd, mode) proc parseTypeDesc(p: var TParser): PNode = #| typeDesc = simpleExpr @@ -1215,8 +1280,8 @@ proc postExprBlocks(p: var TParser, x: PNode): PNode = if p.tok.indent >= 0: return var - openingParams = emptyNode - openingPragmas = emptyNode + openingParams = p.emptyNode + openingPragmas = p.emptyNode if p.tok.tokType == tkDo: getTok(p) @@ -1235,8 +1300,12 @@ proc postExprBlocks(p: var TParser, x: PNode): PNode = stmtList.flags.incl nfBlockArg if openingParams.kind != nkEmpty: - result.add newProcNode(nkDo, stmtList.info, stmtList, - params = openingParams, pragmas = openingPragmas) + result.add newProcNode(nkDo, stmtList.info, body = stmtList, + params = openingParams, + name = p.emptyNode, pattern = p.emptyNode, + genericParams = p.emptyNode, + pragmas = openingPragmas, + exceptions = p.emptyNode) else: result.add stmtList @@ -1273,7 +1342,7 @@ proc postExprBlocks(p: var TParser, x: PNode): PNode = if nextBlock.kind == nkElse: break else: if openingParams.kind != nkEmpty: - parMessage(p, errTokenExpected, ":") + parMessage(p, "expected ':'") proc parseExprStmt(p: var TParser): PNode = #| exprStmt = simpleExpr @@ -1293,17 +1362,18 @@ proc parseExprStmt(p: var TParser): PNode = addSon(result, b) else: # simpleExpr parsed 'p a' from 'p a, b'? + var isFirstParam = false if p.tok.indent < 0 and p.tok.tokType == tkComma and a.kind == nkCommand: result = a while true: getTok(p) optInd(p, result) - addSon(result, commandParam(p)) + addSon(result, commandParam(p, isFirstParam)) if p.tok.tokType != tkComma: break elif p.tok.indent < 0 and isExprStart(p): result = newNode(nkCommand, a.info, @[a]) while true: - addSon(result, commandParam(p)) + addSon(result, commandParam(p, isFirstParam)) if p.tok.tokType != tkComma: break getTok(p) optInd(p, result) @@ -1394,10 +1464,10 @@ proc parseReturnOrRaise(p: var TParser, kind: TNodeKind): PNode = getTok(p) if p.tok.tokType == tkComment: skipComment(p, result) - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) elif p.tok.indent >= 0 and p.tok.indent <= p.currInd or not isExprStart(p): # NL terminates: - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) else: var e = parseExpr(p) e = postExprBlocks(p, e) @@ -1508,7 +1578,7 @@ proc parseTry(p: var TParser; isExpr: bool): PNode = addSon(b, parseStmt(p)) addSon(result, b) if b.kind == nkFinally: break - if b == nil: parMessage(p, errTokenExpected, "except") + if b == nil: parMessage(p, "expected 'except'") proc parseExceptBlock(p: var TParser, kind: TNodeKind): PNode = #| exceptBlock = 'except' colcom stmt @@ -1522,7 +1592,7 @@ proc parseBlock(p: var TParser): PNode = #| blockExpr = 'block' symbol? colcom stmt result = newNodeP(nkBlockStmt, p) getTokNoInd(p) - if p.tok.tokType == tkColon: addSon(result, ast.emptyNode) + if p.tok.tokType == tkColon: addSon(result, p.emptyNode) else: addSon(result, parseSymbol(p)) colcom(p, result) addSon(result, parseStmt(p)) @@ -1536,19 +1606,19 @@ proc parseStaticOrDefer(p: var TParser; k: TNodeKind): PNode = addSon(result, parseStmt(p)) proc parseAsm(p: var TParser): PNode = - #| asmStmt = 'asm' pragma? (STR_LIT | RSTR_LIT | TRIPLE_STR_LIT) + #| asmStmt = 'asm' pragma? (STR_LIT | RSTR_LIT | TRIPLESTR_LIT) result = newNodeP(nkAsmStmt, p) getTokNoInd(p) if p.tok.tokType == tkCurlyDotLe: addSon(result, parsePragma(p)) - else: addSon(result, ast.emptyNode) + else: addSon(result, p.emptyNode) case p.tok.tokType of tkStrLit: addSon(result, newStrNodeP(nkStrLit, p.tok.literal, p)) of tkRStrLit: addSon(result, newStrNodeP(nkRStrLit, p.tok.literal, p)) of tkTripleStrLit: addSon(result, newStrNodeP(nkTripleStrLit, p.tok.literal, p)) else: - parMessage(p, errStringLiteralExpected) - addSon(result, ast.emptyNode) + parMessage(p, "the 'asm' statement takes a string literal") + addSon(result, p.emptyNode) return getTok(p) @@ -1579,13 +1649,13 @@ proc parseGenericParam(p: var TParser): PNode = optInd(p, result) addSon(result, parseExpr(p)) else: - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) if p.tok.tokType == tkEquals: getTok(p) optInd(p, result) addSon(result, parseExpr(p)) else: - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) proc parseGenericParamList(p: var TParser): PNode = #| genericParamList = '[' optInd @@ -1598,6 +1668,8 @@ proc parseGenericParamList(p: var TParser): PNode = var a = parseGenericParam(p) addSon(result, a) if p.tok.tokType notin {tkComma, tkSemiColon}: break + when defined(nimpretty2): + commaWasSemicolon(p.em) getTok(p) skipComment(p, a) optPar(p) @@ -1621,22 +1693,22 @@ proc parseRoutine(p: var TParser, kind: TNodeKind): PNode = optInd(p, result) addSon(result, identVis(p)) if p.tok.tokType == tkCurlyLe and p.validInd: addSon(result, p.parsePattern) - else: addSon(result, ast.emptyNode) + else: addSon(result, p.emptyNode) if p.tok.tokType == tkBracketLe and p.validInd: result.add(p.parseGenericParamList) else: - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) addSon(result, p.parseParamList) if p.tok.tokType == tkCurlyDotLe and p.validInd: addSon(result, p.parsePragma) - else: addSon(result, ast.emptyNode) + else: addSon(result, p.emptyNode) # empty exception tracking: - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) if p.tok.tokType == tkEquals and p.validInd: getTok(p) skipComment(p, result) addSon(result, parseStmt(p)) else: - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) indAndComment(p, result) proc newCommentStmt(p: var TParser): PNode = @@ -1678,7 +1750,7 @@ proc parseSection(p: var TParser, kind: TNodeKind, parMessage(p, errIdentifierExpected, p.tok) proc parseConstant(p: var TParser): PNode = - #| constant = identWithPragma (colon typedesc)? '=' optInd expr indAndComment + #| constant = identWithPragma (colon typeDesc)? '=' optInd expr indAndComment result = newNodeP(nkConstDef, p) addSon(result, identWithPragma(p)) if p.tok.tokType == tkColon: @@ -1686,7 +1758,7 @@ proc parseConstant(p: var TParser): PNode = optInd(p, result) addSon(result, parseTypeDesc(p)) else: - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) eat(p, tkEquals) optInd(p, result) addSon(result, parseExpr(p)) @@ -1696,7 +1768,7 @@ proc parseEnum(p: var TParser): PNode = #| enum = 'enum' optInd (symbol optInd ('=' optInd expr COMMENT?)? comma?)+ result = newNodeP(nkEnumTy, p) getTok(p) - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) optInd(p, result) flexComment(p, result) # progress guaranteed @@ -1726,7 +1798,7 @@ proc parseEnum(p: var TParser): PNode = p.tok.tokType == tkEof: break if result.len <= 1: - lexMessageTok(p.lex, errIdentifierExpected, p.tok, prettyTok(p.tok)) + parMessage(p, errIdentifierExpected, p.tok) proc parseObjectPart(p: var TParser): PNode proc parseObjectWhen(p: var TParser): PNode = @@ -1767,7 +1839,7 @@ proc parseObjectCase(p: var TParser): PNode = addSon(a, identWithPragma(p)) eat(p, tkColon) addSon(a, parseTypeDesc(p)) - addSon(a, ast.emptyNode) + addSon(a, p.emptyNode) addSon(result, a) if p.tok.tokType == tkColon: getTok(p) flexComment(p, result) @@ -1826,7 +1898,7 @@ proc parseObjectPart(p: var TParser): PNode = result = newNodeP(nkNilLit, p) getTok(p) else: - result = ast.emptyNode + result = p.emptyNode proc parseObject(p: var TParser): PNode = #| object = 'object' pragma? ('of' typeDesc)? COMMENT? objectPart @@ -1835,19 +1907,19 @@ proc parseObject(p: var TParser): PNode = if p.tok.tokType == tkCurlyDotLe and p.validInd: addSon(result, parsePragma(p)) else: - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) if p.tok.tokType == tkOf and p.tok.indent < 0: var a = newNodeP(nkOfInherit, p) getTok(p) addSon(a, parseTypeDesc(p)) addSon(result, a) else: - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) if p.tok.tokType == tkComment: skipComment(p, result) # an initial IND{>} HAS to follow: if not realInd(p): - addSon(result, emptyNode) + addSon(result, p.emptyNode) return addSon(result, parseObjectPart(p)) @@ -1882,7 +1954,7 @@ proc parseTypeClass(p: var TParser): PNode = if p.tok.tokType == tkCurlyDotLe and p.validInd: addSon(result, parsePragma(p)) else: - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) if p.tok.tokType == tkOf and p.tok.indent < 0: var a = newNodeP(nkOfInherit, p) getTok(p) @@ -1893,12 +1965,12 @@ proc parseTypeClass(p: var TParser): PNode = getTok(p) addSon(result, a) else: - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) if p.tok.tokType == tkComment: skipComment(p, result) # an initial IND{>} HAS to follow: if not realInd(p): - addSon(result, emptyNode) + addSon(result, p.emptyNode) else: addSon(result, parseStmt(p)) @@ -1911,14 +1983,14 @@ proc parseTypeDef(p: var TParser): PNode = if p.tok.tokType == tkBracketLe and p.validInd: addSon(result, parseGenericParamList(p)) else: - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) if p.tok.tokType == tkEquals: result.info = parLineInfo(p) getTok(p) optInd(p, result) addSon(result, parseTypeDefAux(p)) else: - addSon(result, ast.emptyNode) + addSon(result, p.emptyNode) indAndComment(p, result) # special extension! proc parseVarTuple(p: var TParser): PNode = @@ -1933,7 +2005,7 @@ proc parseVarTuple(p: var TParser): PNode = if p.tok.tokType != tkComma: break getTok(p) skipComment(p, a) - addSon(result, ast.emptyNode) # no type desc + addSon(result, p.emptyNode) # no type desc optPar(p) eat(p, tkParRi) eat(p, tkEquals) @@ -1994,7 +2066,7 @@ proc simpleStmt(p: var TParser): PNode = of tkComment: result = newCommentStmt(p) else: if isExprStart(p): result = parseExprStmt(p) - else: result = ast.emptyNode + else: result = p.emptyNode if result.kind notin {nkEmpty, nkCommentStmt}: skipComment(p, result) proc complexOrSimpleStmt(p: var TParser): PNode = @@ -2089,8 +2161,8 @@ proc parseStmt(p: var TParser): PNode = case p.tok.tokType of tkIf, tkWhile, tkCase, tkTry, tkFor, tkBlock, tkAsm, tkProc, tkFunc, tkIterator, tkMacro, tkType, tkConst, tkWhen, tkVar: - parMessage(p, errComplexStmtRequiresInd) - result = ast.emptyNode + parMessage(p, "complex statement requires indentation") + result = p.emptyNode else: if p.inSemiStmtList > 0: result = simpleStmt(p) @@ -2127,13 +2199,18 @@ proc parseAll(p: var TParser): PNode = proc parseTopLevelStmt(p: var TParser): PNode = ## Implements an iterator which, when called repeatedly, returns the next ## top-level statement or emptyNode if end of stream. - result = ast.emptyNode + result = p.emptyNode # progress guaranteed while true: if p.tok.indent != 0: if p.firstTok and p.tok.indent < 0: discard elif p.tok.tokType != tkSemiColon: - parMessage(p, errInvalidIndentation) + # special casing for better error messages: + if p.tok.tokType == tkOpr and p.tok.ident.s == "*": + parMessage(p, errGenerated, + "invalid indentation; an export marker '*' follows the declared identifier") + else: + parMessage(p, errInvalidIndentation) p.firstTok = false case p.tok.tokType of tkSemiColon: @@ -2147,8 +2224,8 @@ proc parseTopLevelStmt(p: var TParser): PNode = if result.kind == nkEmpty: parMessage(p, errExprExpected, p.tok) break -proc parseString*(s: string; cache: IdentCache; filename: string = ""; - line: int = 0; +proc parseString*(s: string; cache: IdentCache; config: ConfigRef; + filename: string = ""; line: int = 0; errorHandler: TErrorHandler = nil): PNode = ## Parses a string into an AST, returning the top node. ## `filename` and `line`, although optional, provide info so that the @@ -2161,7 +2238,7 @@ proc parseString*(s: string; cache: IdentCache; filename: string = ""; # XXX for now the builtin 'parseStmt/Expr' functions do not know about strong # spaces... parser.lex.errorHandler = errorHandler - openParser(parser, filename, stream, cache, false) + openParser(parser, filename, stream, cache, config, false) result = parser.parseAll closeParser(parser) diff --git a/compiler/passaux.nim b/compiler/passaux.nim index 2065d5893..eabce8822 100644 --- a/compiler/passaux.nim +++ b/compiler/passaux.nim @@ -10,22 +10,26 @@ ## implements some little helper passes import - strutils, ast, astalgo, passes, idents, msgs, options, idgen + strutils, ast, astalgo, passes, idents, msgs, options, idgen, lineinfos from modulegraphs import ModuleGraph -proc verboseOpen(graph: ModuleGraph; s: PSym; cache: IdentCache): PPassContext = +type + VerboseRef = ref object of TPassContext + config: ConfigRef + +proc verboseOpen(graph: ModuleGraph; s: PSym): PPassContext = #MessageOut('compiling ' + s.name.s); - result = nil # we don't need a context - rawMessage(hintProcessing, s.name.s) + result = VerboseRef(config: graph.config) + rawMessage(graph.config, hintProcessing, s.name.s) proc verboseProcess(context: PPassContext, n: PNode): PNode = result = n - if context != nil: internalError("logpass: context is not nil") - if gVerbosity == 3: + let v = VerboseRef(context) + if v.config.verbosity == 3: # system.nim deactivates all hints, for verbosity:3 we want the processing # messages nonetheless, so we activate them again unconditionally: - incl(msgs.gNotes, hintProcessing) - message(n.info, hintProcessing, $idgen.gFrontendId) + incl(v.config.notes, hintProcessing) + message(v.config, n.info, hintProcessing, $idgen.gFrontendId) const verbosePass* = makePass(open = verboseOpen, process = verboseProcess) diff --git a/compiler/passes.nim b/compiler/passes.nim index f079100ea..45c726f2a 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -13,23 +13,20 @@ import strutils, options, ast, astalgo, llstream, msgs, platform, os, condsyms, idents, renderer, types, extccomp, math, magicsys, nversion, - nimsets, syntaxes, times, rodread, idgen, modulegraphs, reorder, rod + nimsets, syntaxes, times, idgen, modulegraphs, reorder, rod, + lineinfos type TPassContext* = object of RootObj # the pass's context - rd*: PRodReader # != nil if created by "openCached" PPassContext* = ref TPassContext - TPassOpen* = proc (graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext {.nimcall.} - TPassOpenCached* = - proc (graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext {.nimcall.} + TPassOpen* = proc (graph: ModuleGraph; module: PSym): PPassContext {.nimcall.} TPassClose* = proc (graph: ModuleGraph; p: PPassContext, n: PNode): PNode {.nimcall.} TPassProcess* = proc (p: PPassContext, topLevelStmt: PNode): PNode {.nimcall.} - TPass* = tuple[open: TPassOpen, openCached: TPassOpenCached, - process: TPassProcess, close: TPassClose, + TPass* = tuple[open: TPassOpen, process: TPassProcess, close: TPassClose, isFrontend: bool] TPassData* = tuple[input: PNode, closeOutput: PNode] @@ -40,42 +37,19 @@ type # This mechanism used to be used for the instantiation of generics. proc makePass*(open: TPassOpen = nil, - openCached: TPassOpenCached = nil, process: TPassProcess = nil, close: TPassClose = nil, isFrontend = false): TPass = result.open = open - result.openCached = openCached result.close = close result.process = process result.isFrontend = isFrontend -# the semantic checker needs these: -var - gImportModule*: proc (graph: ModuleGraph; m: PSym, fileIdx: int32; cache: IdentCache): PSym {.nimcall.} - gIncludeFile*: proc (graph: ModuleGraph; m: PSym, fileIdx: int32; cache: IdentCache): PNode {.nimcall.} - -# implementation - -proc skipCodegen*(n: PNode): bool {.inline.} = +proc skipCodegen*(config: ConfigRef; n: PNode): bool {.inline.} = # can be used by codegen passes to determine whether they should do # something with `n`. Currently, this ignores `n` and uses the global # error count instead. - result = msgs.gErrorCounter > 0 - -proc astNeeded*(s: PSym): bool = - # The ``rodwrite`` module uses this to determine if the body of a proc - # needs to be stored. The passes manager frees s.sons[codePos] when - # appropriate to free the procedure body's memory. This is important - # to keep memory usage down. - if (s.kind in {skMethod, skProc, skFunc}) and - ({sfCompilerProc, sfCompileTime} * s.flags == {}) and - (s.typ.callConv != ccInline) and - (s.ast.sons[genericParamsPos].kind == nkEmpty): - result = false - # XXX this doesn't really make sense with excessive CTFE - else: - result = true + result = config.errorCounter > 0 const maxPasses = 10 @@ -87,44 +61,34 @@ var gPasses: array[0..maxPasses - 1, TPass] gPassesLen*: int -proc clearPasses* = +proc clearPasses*(g: ModuleGraph) = gPassesLen = 0 -proc registerPass*(p: TPass) = +proc registerPass*(g: ModuleGraph; p: TPass) = gPasses[gPassesLen] = p inc(gPassesLen) -proc carryPass*(g: ModuleGraph; p: TPass, module: PSym; cache: IdentCache; +proc carryPass*(g: ModuleGraph; p: TPass, module: PSym; m: TPassData): TPassData = - var c = p.open(g, module, cache) + var c = p.open(g, module) result.input = p.process(c, m.input) result.closeOutput = if p.close != nil: p.close(g, c, m.closeOutput) else: m.closeOutput proc carryPasses*(g: ModuleGraph; nodes: PNode, module: PSym; - cache: IdentCache; passes: TPasses) = + passes: TPasses) = var passdata: TPassData passdata.input = nodes for pass in passes: - passdata = carryPass(g, pass, module, cache, passdata) + passdata = carryPass(g, pass, module, passdata) proc openPasses(g: ModuleGraph; a: var TPassContextArray; - module: PSym; cache: IdentCache) = + module: PSym) = for i in countup(0, gPassesLen - 1): if not isNil(gPasses[i].open): - a[i] = gPasses[i].open(g, module, cache) + a[i] = gPasses[i].open(g, module) else: a[i] = nil -proc openPassesCached(g: ModuleGraph; a: var TPassContextArray, module: PSym, - rd: PRodReader) = - for i in countup(0, gPassesLen - 1): - if not isNil(gPasses[i].openCached): - a[i] = gPasses[i].openCached(g, module, rd) - if a[i] != nil: - a[i].rd = rd - else: - a[i] = nil - proc closePasses(graph: ModuleGraph; a: var TPassContextArray) = var m: PNode = nil for i in countup(0, gPassesLen - 1): @@ -140,41 +104,28 @@ proc processTopLevelStmt(n: PNode, a: var TPassContextArray): bool = if isNil(m): return false result = true -proc processTopLevelStmtCached(n: PNode, a: var TPassContextArray) = - # this implements the code transformation pipeline - var m = n - for i in countup(0, gPassesLen - 1): - if not isNil(gPasses[i].openCached): m = gPasses[i].process(a[i], m) - -proc closePassesCached(graph: ModuleGraph; a: var TPassContextArray) = - var m: PNode = nil - for i in countup(0, gPassesLen - 1): - if not isNil(gPasses[i].openCached) and not isNil(gPasses[i].close): - m = gPasses[i].close(graph, a[i], m) - a[i] = nil # free the memory here - -proc resolveMod(module, relativeTo: string): int32 = - let fullPath = findModule(module, relativeTo) +proc resolveMod(conf: ConfigRef; module, relativeTo: string): FileIndex = + let fullPath = findModule(conf, module, relativeTo) if fullPath.len == 0: result = InvalidFileIDX else: - result = fullPath.fileInfoIdx + result = fileInfoIdx(conf, fullPath) -proc processImplicits(implicits: seq[string], nodeKind: TNodeKind, +proc processImplicits(conf: ConfigRef; implicits: seq[string], nodeKind: TNodeKind, a: var TPassContextArray; m: PSym) = # XXX fixme this should actually be relative to the config file! - let relativeTo = m.info.toFullPath + let gCmdLineInfo = newLineInfo(FileIndex(0), 1, 1) + let relativeTo = toFullPath(conf, m.info) for module in items(implicits): # implicit imports should not lead to a module importing itself - if m.position != resolveMod(module, relativeTo): + if m.position != resolveMod(conf, module, relativeTo).int32: var importStmt = newNodeI(nodeKind, gCmdLineInfo) var str = newStrNode(nkStrLit, module) str.info = gCmdLineInfo importStmt.addSon str if not processTopLevelStmt(importStmt, a): break -proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream, - rd: PRodReader; cache: IdentCache): bool {.discardable.} = +proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool {.discardable.} = if graph.stopCompile(): return true var p: TParsers @@ -185,24 +136,17 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream, # new module caching mechanism: for i in 0..<gPassesLen: if not isNil(gPasses[i].open) and not gPasses[i].isFrontend: - a[i] = gPasses[i].open(graph, module, cache) + a[i] = gPasses[i].open(graph, module) else: a[i] = nil - var stmtIndex = 0 - var doContinue = true - while doContinue: - let n = loadNode(module, stmtIndex) - if n == nil or graph.stopCompile(): break - #if n.kind == nkImportStmt: - # echo "yes and it's ", n - inc stmtIndex + if not graph.stopCompile(): + let n = loadNode(graph, module) var m = n for i in 0..<gPassesLen: if not isNil(gPasses[i].process) and not gPasses[i].isFrontend: m = gPasses[i].process(a[i], m) if isNil(m): - doContinue = false break var m: PNode = nil @@ -210,26 +154,26 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream, if not isNil(gPasses[i].close) and not gPasses[i].isFrontend: m = gPasses[i].close(graph, a[i], m) a[i] = nil - elif rd == nil: - openPasses(graph, a, module, cache) + else: + openPasses(graph, a, module) if stream == nil: - let filename = fileIdx.toFullPathConsiderDirty + let filename = toFullPathConsiderDirty(graph.config, fileIdx) s = llStreamOpen(filename, fmRead) if s == nil: - rawMessage(errCannotOpenFile, filename) + rawMessage(graph.config, errCannotOpenFile, filename) return false else: s = stream while true: - openParsers(p, fileIdx, s, cache) + openParsers(p, fileIdx, s, graph.cache, graph.config) if sfSystemModule notin module.flags: # XXX what about caching? no processing then? what if I change the # modules to include between compilation runs? we'd need to track that # in ROD files. I think we should enable this feature only # for the interactive mode. - processImplicits implicitImports, nkImportStmt, a, module - processImplicits implicitIncludes, nkIncludeStmt, a, module + processImplicits graph.config, graph.config.implicitImports, nkImportStmt, a, module + processImplicits graph.config, graph.config.implicitIncludes, nkIncludeStmt, a, module while true: if graph.stopCompile(): break @@ -244,7 +188,7 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream, if n.kind == nkEmpty: break sl.add n if sfReorder in module.flags: - sl = reorder(graph, sl, module, cache) + sl = reorder(graph, sl, module) discard processTopLevelStmt(sl, a) break elif not processTopLevelStmt(n, a): break @@ -253,11 +197,4 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream, closePasses(graph, a) # id synchronization point for more consistent code generation: idSynchronizationPoint(1000) - else: - openPassesCached(graph, a, module, rd) - var n = loadInitSection(rd) - for i in countup(0, sonsLen(n) - 1): - if graph.stopCompile(): break - processTopLevelStmtCached(n.sons[i], a) - closePassesCached(graph, a) result = true diff --git a/compiler/patterns.nim b/compiler/patterns.nim index 31b76743e..ebb3a7c1d 100644 --- a/compiler/patterns.nim +++ b/compiler/patterns.nim @@ -21,14 +21,17 @@ type formals: int c: PContext subMatch: bool # subnode matches are special + mappingIsFull: bool PPatternContext = var TPatternContext proc getLazy(c: PPatternContext, sym: PSym): PNode = - if not isNil(c.mapping): + if c.mappingIsFull: result = c.mapping[sym.position] proc putLazy(c: PPatternContext, sym: PSym, n: PNode) = - if isNil(c.mapping): newSeq(c.mapping, c.formals) + if not c.mappingIsFull: + newSeq(c.mapping, c.formals) + c.mappingIsFull = true c.mapping[sym.position] = n proc matches(c: PPatternContext, p, n: PNode): bool @@ -77,7 +80,7 @@ proc checkTypes(c: PPatternContext, p: PSym, n: PNode): bool = if isNil(n.typ): result = p.typ.kind in {tyVoid, tyStmt} else: - result = sigmatch.argtypeMatches(c.c, p.typ, n.typ) + result = sigmatch.argtypeMatches(c.c, p.typ, n.typ, fromHlo = true) proc isPatternParam(c: PPatternContext, p: PNode): bool {.inline.} = result = p.kind == nkSym and p.sym.kind == skParam and p.sym.owner == c.owner @@ -150,7 +153,7 @@ proc matches(c: PPatternContext, p, n: PNode): bool = of "*": result = matchNested(c, p, n, rpn=false) of "**": result = matchNested(c, p, n, rpn=true) of "~": result = not matches(c, p.sons[1], n) - else: internalError(p.info, "invalid pattern") + else: doAssert(false, "invalid pattern") # template {add(a, `&` * b)}(a: string{noalias}, b: varargs[string]) = # add(a, b) elif p.kind == nkCurlyExpr: @@ -209,7 +212,11 @@ proc matchStmtList(c: PPatternContext, p, n: PNode): PNode = for j in 0 ..< p.len: if not matches(c, p.sons[j], n.sons[i+j]): # we need to undo any bindings: - if not isNil(c.mapping): c.mapping = nil + when defined(nimNoNilSeqs): + c.mapping = @[] + c.mappingIsFull = false + else: + if not isNil(c.mapping): c.mapping = nil return false result = true @@ -289,7 +296,7 @@ proc applyRule*(c: PContext, s: PSym, n: PNode): PNode = # constraint not fulfilled: if not ok: return nil - markUsed(n.info, s, c.graph.usageSym) + markUsed(c.config, n.info, s, c.graph.usageSym) if ctx.subMatch: assert m.len == 3 m.sons[1] = result diff --git a/compiler/pbraces.nim b/compiler/pbraces.nim deleted file mode 100644 index eba6f0b62..000000000 --- a/compiler/pbraces.nim +++ /dev/null @@ -1,1790 +0,0 @@ -# -# -# The Nim Compiler -# (c) Copyright 2017 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -# This module implements the parser of the braces Nim syntax. - -import - llstream, lexer, idents, strutils, ast, astalgo, msgs - -from parser import TParser - -proc getTok(p: var TParser) = - ## Get the next token from the parser's lexer, and store it in the parser's - ## `tok` member. - rawGetTok(p.lex, p.tok) - -proc openParser*(p: var TParser, fileIdx: int32, inputStream: PLLStream; - cache: IdentCache) = - ## Open a parser, using the given arguments to set up its internal state. - ## - initToken(p.tok) - openLexer(p.lex, fileIdx, inputStream, cache) - getTok(p) # read the first token - p.lex.allowTabs = true - -proc openParser*(p: var TParser, filename: string, inputStream: PLLStream; - cache: IdentCache) = - openParser(p, filename.fileInfoIdx, inputStream, cache) - -proc closeParser*(p: var TParser) = - ## Close a parser, freeing up its resources. - closeLexer(p.lex) - -proc parMessage(p: TParser, msg: TMsgKind, arg = "") = - ## Produce and emit the parser message `arg` to output. - lexMessageTok(p.lex, msg, p.tok, arg) - -proc parMessage(p: TParser, msg: TMsgKind, tok: TToken) = - ## Produce and emit a parser message to output about the token `tok` - parMessage(p, msg, prettyTok(tok)) - -proc rawSkipComment(p: var TParser, node: PNode) = - if p.tok.tokType == tkComment: - if node != nil: - if node.comment == nil: node.comment = "" - add(node.comment, p.tok.literal) - else: - parMessage(p, errInternal, "skipComment") - getTok(p) - -proc skipComment(p: var TParser, node: PNode) = - rawSkipComment(p, node) - -proc flexComment(p: var TParser, node: PNode) = - rawSkipComment(p, node) - -proc skipInd(p: var TParser) = discard -proc optPar(p: var TParser) = discard - -proc optInd(p: var TParser, n: PNode) = - skipComment(p, n) - -proc getTokNoInd(p: var TParser) = - getTok(p) - -proc expectIdentOrKeyw(p: TParser) = - if p.tok.tokType != tkSymbol and not isKeyword(p.tok.tokType): - lexMessage(p.lex, errIdentifierExpected, prettyTok(p.tok)) - -proc expectIdent(p: TParser) = - if p.tok.tokType != tkSymbol: - lexMessage(p.lex, errIdentifierExpected, prettyTok(p.tok)) - -proc eat(p: var TParser, tokType: TTokType) = - ## Move the parser to the next token if the current token is of type - ## `tokType`, otherwise error. - if p.tok.tokType == tokType: - getTok(p) - else: - lexMessageTok(p.lex, errTokenExpected, p.tok, TokTypeToStr[tokType]) - -proc parLineInfo(p: TParser): TLineInfo = - ## Retrieve the line information associated with the parser's current state. - result = getLineInfo(p.lex, p.tok) - -proc indAndComment(p: var TParser, n: PNode) = - rawSkipComment(p, n) - -proc newNodeP(kind: TNodeKind, p: TParser): PNode = - result = newNodeI(kind, parLineInfo(p)) - -proc newIntNodeP(kind: TNodeKind, intVal: BiggestInt, p: TParser): PNode = - result = newNodeP(kind, p) - result.intVal = intVal - -proc newFloatNodeP(kind: TNodeKind, floatVal: BiggestFloat, - p: TParser): PNode = - result = newNodeP(kind, p) - result.floatVal = floatVal - -proc newStrNodeP(kind: TNodeKind, strVal: string, p: TParser): PNode = - result = newNodeP(kind, p) - result.strVal = strVal - -proc newIdentNodeP(ident: PIdent, p: TParser): PNode = - result = newNodeP(nkIdent, p) - result.ident = ident - -proc parseExpr(p: var TParser): PNode -proc parseStmt(p: var TParser): PNode -proc parseTypeDesc(p: var TParser): PNode -proc parseDoBlocks(p: var TParser, call: PNode) -proc parseParamList(p: var TParser, retColon = true): PNode -proc parseStmtPragma(p: var TParser): PNode -proc parseCase(p: var TParser): PNode -proc parseTry(p: var TParser): PNode - -proc isSigilLike(tok: TToken): bool {.inline.} = - result = tok.tokType == tkOpr and tok.ident.s[0] == '@' - -proc isAt(tok: TToken): bool {.inline.} = - tok.tokType == tkOpr and tok.ident.s == "@" and tok.strongSpaceB == 0 - -proc isRightAssociative(tok: TToken): bool {.inline.} = - ## Determines whether the token is right assocative. - result = tok.tokType == tkOpr and tok.ident.s[0] == '^' - # or (let L = tok.ident.s.len; L > 1 and tok.ident.s[L-1] == '>')) - -proc getPrecedence(tok: TToken): int = - ## Calculates the precedence of the given token. - template considerStrongSpaces(x): untyped = x - - case tok.tokType - of tkOpr: - let L = tok.ident.s.len - let relevantChar = tok.ident.s[0] - - # arrow like? - if L > 1 and tok.ident.s[L-1] == '>' and - tok.ident.s[L-2] in {'-', '~', '='}: return considerStrongSpaces(1) - - template considerAsgn(value: untyped) = - result = if tok.ident.s[L-1] == '=': 1 else: value - - case relevantChar - of '$', '^': considerAsgn(10) - of '*', '%', '/', '\\': considerAsgn(9) - of '~': result = 8 - of '+', '-', '|': considerAsgn(8) - of '&': considerAsgn(7) - of '=', '<', '>', '!': result = 5 - of '.': considerAsgn(6) - of '?': result = 2 - else: considerAsgn(2) - of tkDiv, tkMod, tkShl, tkShr: result = 9 - of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5 - of tkDotDot: result = 6 - of tkAnd: result = 4 - of tkOr, tkXor, tkPtr, tkRef: result = 3 - else: return -10 - result = considerStrongSpaces(result) - -proc isOperator(tok: TToken): bool = - ## Determines if the given token is an operator type token. - tok.tokType in {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs, - tkIsnot, tkNot, tkOf, tkAs, tkDotDot, tkAnd, tkOr, tkXor} - -proc isUnary(p: TParser): bool = - ## Check if the current parser token is a unary operator - if p.tok.tokType in {tkOpr, tkDotDot}: - result = true - -proc checkBinary(p: TParser) {.inline.} = - ## Check if the current parser token is a binary operator. - # we don't check '..' here as that's too annoying - discard - -#| module = stmt ^* (';' / IND{=}) -#| -#| comma = ',' COMMENT? -#| semicolon = ';' COMMENT? -#| colon = ':' COMMENT? -#| colcom = ':' COMMENT? -#| -#| operator = OP0 | OP1 | OP2 | OP3 | OP4 | OP5 | OP6 | OP7 | OP8 | OP9 -#| | 'or' | 'xor' | 'and' -#| | 'is' | 'isnot' | 'in' | 'notin' | 'of' -#| | 'div' | 'mod' | 'shl' | 'shr' | 'not' | 'static' | '..' -#| -#| prefixOperator = operator -#| -#| optInd = COMMENT? -#| optPar = (IND{>} | IND{=})? -#| -#| simpleExpr = arrowExpr (OP0 optInd arrowExpr)* -#| arrowExpr = assignExpr (OP1 optInd assignExpr)* -#| assignExpr = orExpr (OP2 optInd orExpr)* -#| orExpr = andExpr (OP3 optInd andExpr)* -#| andExpr = cmpExpr (OP4 optInd cmpExpr)* -#| cmpExpr = sliceExpr (OP5 optInd sliceExpr)* -#| sliceExpr = ampExpr (OP6 optInd ampExpr)* -#| ampExpr = plusExpr (OP7 optInd plusExpr)* -#| plusExpr = mulExpr (OP8 optInd mulExpr)* -#| mulExpr = dollarExpr (OP9 optInd dollarExpr)* -#| dollarExpr = primary (OP10 optInd primary)* - -proc colcom(p: var TParser, n: PNode) = - skipComment(p, n) - -proc parseSymbol(p: var TParser, allowNil = false): PNode = - #| symbol = '`' (KEYW|IDENT|literal|(operator|'('|')'|'['|']'|'{'|'}'|'=')+)+ '`' - #| | IDENT | 'addr' | 'type' - case p.tok.tokType - of tkSymbol, tkAddr, tkType: - result = newIdentNodeP(p.tok.ident, p) - getTok(p) - of tkAccent: - result = newNodeP(nkAccQuoted, p) - getTok(p) - while true: - case p.tok.tokType - of tkAccent: - if result.len == 0: - parMessage(p, errIdentifierExpected, p.tok) - break - of tkOpr, tkDot, tkDotDot, tkEquals, tkParLe..tkParDotRi: - var accm = "" - while p.tok.tokType in {tkOpr, tkDot, tkDotDot, tkEquals, - tkParLe..tkParDotRi}: - accm.add(tokToStr(p.tok)) - getTok(p) - result.add(newIdentNodeP(p.lex.cache.getIdent(accm), p)) - of tokKeywordLow..tokKeywordHigh, tkSymbol, tkIntLit..tkCharLit: - result.add(newIdentNodeP(p.lex.cache.getIdent(tokToStr(p.tok)), p)) - getTok(p) - else: - parMessage(p, errIdentifierExpected, p.tok) - break - eat(p, tkAccent) - else: - if allowNil and p.tok.tokType == tkNil: - result = newNodeP(nkNilLit, p) - getTok(p) - else: - parMessage(p, errIdentifierExpected, p.tok) - # BUGFIX: We must consume a token here to prevent endless loops! - # But: this really sucks for idetools and keywords, so we don't do it - # if it is a keyword: - if not isKeyword(p.tok.tokType): getTok(p) - result = ast.emptyNode - -proc colonOrEquals(p: var TParser, a: PNode): PNode = - if p.tok.tokType == tkColon: - result = newNodeP(nkExprColonExpr, p) - getTok(p) - #optInd(p, result) - addSon(result, a) - addSon(result, parseExpr(p)) - elif p.tok.tokType == tkEquals: - result = newNodeP(nkExprEqExpr, p) - getTok(p) - #optInd(p, result) - addSon(result, a) - addSon(result, parseExpr(p)) - else: - result = a - -proc exprColonEqExpr(p: var TParser): PNode = - #| exprColonEqExpr = expr (':'|'=' expr)? - var a = parseExpr(p) - result = colonOrEquals(p, a) - -proc exprList(p: var TParser, endTok: TTokType, result: PNode) = - #| exprList = expr ^+ comma - getTok(p) - optInd(p, result) - while (p.tok.tokType != endTok) and (p.tok.tokType != tkEof): - var a = parseExpr(p) - addSon(result, a) - if p.tok.tokType != tkComma: break - getTok(p) - optInd(p, a) - -proc dotExpr(p: var TParser, a: PNode): PNode = - #| dotExpr = expr '.' optInd symbol - var info = p.parLineInfo - getTok(p) - result = newNodeI(nkDotExpr, info) - optInd(p, result) - addSon(result, a) - addSon(result, parseSymbol(p)) - -proc qualifiedIdent(p: var TParser): PNode = - #| qualifiedIdent = symbol ('.' optInd symbol)? - result = parseSymbol(p) - if p.tok.tokType == tkDot: result = dotExpr(p, result) - -proc exprColonEqExprListAux(p: var TParser, endTok: TTokType, result: PNode) = - assert(endTok in {tkCurlyLe, tkCurlyRi, tkCurlyDotRi, tkBracketRi, tkParRi}) - getTok(p) - optInd(p, result) - while p.tok.tokType != endTok and p.tok.tokType != tkEof: - var a = exprColonEqExpr(p) - addSon(result, a) - if p.tok.tokType != tkComma: break - getTok(p) - skipComment(p, a) - optPar(p) - eat(p, endTok) - -proc exprColonEqExprList(p: var TParser, kind: TNodeKind, - endTok: TTokType): PNode = - #| exprColonEqExprList = exprColonEqExpr (comma exprColonEqExpr)* (comma)? - result = newNodeP(kind, p) - exprColonEqExprListAux(p, endTok, result) - -proc setOrTableConstr(p: var TParser): PNode = - result = newNodeP(nkCurly, p) - getTok(p) - optInd(p, result) - if p.tok.tokType == tkColon: - getTok(p) # skip ':' - result.kind = nkTableConstr - else: - while p.tok.tokType notin {tkBracketDotRi, tkEof}: - var a = exprColonEqExpr(p) - if a.kind == nkExprColonExpr: result.kind = nkTableConstr - addSon(result, a) - if p.tok.tokType != tkComma: break - getTok(p) - skipComment(p, a) - optPar(p) - eat(p, tkBracketDotRi) - -proc parseCast(p: var TParser): PNode = - #| castExpr = 'cast' '[' optInd typeDesc optPar ']' '(' optInd expr optPar ')' - result = newNodeP(nkCast, p) - getTok(p) - eat(p, tkBracketLe) - optInd(p, result) - addSon(result, parseTypeDesc(p)) - optPar(p) - eat(p, tkBracketRi) - eat(p, tkParLe) - optInd(p, result) - addSon(result, parseExpr(p)) - optPar(p) - eat(p, tkParRi) - -proc setBaseFlags(n: PNode, base: TNumericalBase) = - case base - of base10: discard - of base2: incl(n.flags, nfBase2) - of base8: incl(n.flags, nfBase8) - of base16: incl(n.flags, nfBase16) - -proc parseGStrLit(p: var TParser, a: PNode): PNode = - case p.tok.tokType - of tkGStrLit: - result = newNodeP(nkCallStrLit, p) - addSon(result, a) - addSon(result, newStrNodeP(nkRStrLit, p.tok.literal, p)) - getTok(p) - of tkGTripleStrLit: - result = newNodeP(nkCallStrLit, p) - addSon(result, a) - addSon(result, newStrNodeP(nkTripleStrLit, p.tok.literal, p)) - getTok(p) - else: - result = a - -type - TPrimaryMode = enum pmNormal, pmTypeDesc, pmTypeDef, pmSkipSuffix - -proc complexOrSimpleStmt(p: var TParser): PNode -proc simpleExpr(p: var TParser, mode = pmNormal): PNode - -proc semiStmtList(p: var TParser, result: PNode) = - inc p.inSemiStmtList - result.add(complexOrSimpleStmt(p)) - while p.tok.tokType == tkSemiColon: - getTok(p) - optInd(p, result) - result.add(complexOrSimpleStmt(p)) - dec p.inSemiStmtList - result.kind = nkStmtListExpr - -proc parsePar(p: var TParser): PNode = - #| parKeyw = 'discard' | 'include' | 'if' | 'while' | 'case' | 'try' - #| | 'finally' | 'except' | 'for' | 'block' | 'const' | 'let' - #| | 'when' | 'var' | 'mixin' - #| par = '(' optInd - #| ( &parKeyw complexOrSimpleStmt ^+ ';' - #| | ';' complexOrSimpleStmt ^+ ';' - #| | pragmaStmt - #| | simpleExpr ( ('=' expr (';' complexOrSimpleStmt ^+ ';' )? ) - #| | (':' expr (',' exprColonEqExpr ^+ ',' )? ) ) ) - #| optPar ')' - # - # unfortunately it's ambiguous: (expr: expr) vs (exprStmt); however a - # leading ';' could be used to enforce a 'stmt' context ... - result = newNodeP(nkPar, p) - getTok(p) - optInd(p, result) - if p.tok.tokType in {tkDiscard, tkInclude, tkIf, tkWhile, tkCase, - tkTry, tkDefer, tkFinally, tkExcept, tkFor, tkBlock, - tkConst, tkLet, tkWhen, tkVar, - tkMixin}: - # XXX 'bind' used to be an expression, so we exclude it here; - # tests/reject/tbind2 fails otherwise. - semiStmtList(p, result) - elif p.tok.tokType == tkSemiColon: - # '(;' enforces 'stmt' context: - getTok(p) - optInd(p, result) - semiStmtList(p, result) - elif p.tok.tokType == tkCurlyDotLe: - result.add(parseStmtPragma(p)) - elif p.tok.tokType != tkParRi: - var a = simpleExpr(p) - if p.tok.tokType == tkEquals: - # special case: allow assignments - getTok(p) - optInd(p, result) - let b = parseExpr(p) - let asgn = newNodeI(nkAsgn, a.info, 2) - asgn.sons[0] = a - asgn.sons[1] = b - result.add(asgn) - if p.tok.tokType == tkSemiColon: - semiStmtList(p, result) - elif p.tok.tokType == tkSemiColon: - # stmt context: - result.add(a) - semiStmtList(p, result) - else: - a = colonOrEquals(p, a) - result.add(a) - if p.tok.tokType == tkComma: - getTok(p) - skipComment(p, a) - while p.tok.tokType != tkParRi and p.tok.tokType != tkEof: - var a = exprColonEqExpr(p) - addSon(result, a) - if p.tok.tokType != tkComma: break - getTok(p) - skipComment(p, a) - optPar(p) - eat(p, tkParRi) - -proc identOrLiteral(p: var TParser, mode: TPrimaryMode): PNode = - #| literal = | INT_LIT | INT8_LIT | INT16_LIT | INT32_LIT | INT64_LIT - #| | UINT_LIT | UINT8_LIT | UINT16_LIT | UINT32_LIT | UINT64_LIT - #| | FLOAT_LIT | FLOAT32_LIT | FLOAT64_LIT - #| | STR_LIT | RSTR_LIT | TRIPLESTR_LIT - #| | CHAR_LIT - #| | NIL - #| generalizedLit = GENERALIZED_STR_LIT | GENERALIZED_TRIPLESTR_LIT - #| identOrLiteral = generalizedLit | symbol | literal - #| | par | arrayConstr | setOrTableConstr - #| | castExpr - #| tupleConstr = '(' optInd (exprColonEqExpr comma?)* optPar ')' - #| arrayConstr = '[' optInd (exprColonEqExpr comma?)* optPar ']' - case p.tok.tokType - of tkSymbol, tkType, tkAddr: - result = newIdentNodeP(p.tok.ident, p) - getTok(p) - result = parseGStrLit(p, result) - of tkAccent: - result = parseSymbol(p) # literals - of tkIntLit: - result = newIntNodeP(nkIntLit, p.tok.iNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkInt8Lit: - result = newIntNodeP(nkInt8Lit, p.tok.iNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkInt16Lit: - result = newIntNodeP(nkInt16Lit, p.tok.iNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkInt32Lit: - result = newIntNodeP(nkInt32Lit, p.tok.iNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkInt64Lit: - result = newIntNodeP(nkInt64Lit, p.tok.iNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkUIntLit: - result = newIntNodeP(nkUIntLit, p.tok.iNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkUInt8Lit: - result = newIntNodeP(nkUInt8Lit, p.tok.iNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkUInt16Lit: - result = newIntNodeP(nkUInt16Lit, p.tok.iNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkUInt32Lit: - result = newIntNodeP(nkUInt32Lit, p.tok.iNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkUInt64Lit: - result = newIntNodeP(nkUInt64Lit, p.tok.iNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkFloatLit: - result = newFloatNodeP(nkFloatLit, p.tok.fNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkFloat32Lit: - result = newFloatNodeP(nkFloat32Lit, p.tok.fNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkFloat64Lit: - result = newFloatNodeP(nkFloat64Lit, p.tok.fNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkFloat128Lit: - result = newFloatNodeP(nkFloat128Lit, p.tok.fNumber, p) - setBaseFlags(result, p.tok.base) - getTok(p) - of tkStrLit: - result = newStrNodeP(nkStrLit, p.tok.literal, p) - getTok(p) - of tkRStrLit: - result = newStrNodeP(nkRStrLit, p.tok.literal, p) - getTok(p) - of tkTripleStrLit: - result = newStrNodeP(nkTripleStrLit, p.tok.literal, p) - getTok(p) - of tkCharLit: - result = newIntNodeP(nkCharLit, ord(p.tok.literal[0]), p) - getTok(p) - of tkNil: - result = newNodeP(nkNilLit, p) - getTok(p) - of tkParLe: - # () constructor - if mode in {pmTypeDesc, pmTypeDef}: - result = exprColonEqExprList(p, nkPar, tkParRi) - else: - result = parsePar(p) - of tkBracketDotLe: - # {} constructor - result = setOrTableConstr(p) - of tkBracketLe: - # [] constructor - result = exprColonEqExprList(p, nkBracket, tkBracketRi) - of tkCast: - result = parseCast(p) - else: - parMessage(p, errExprExpected, p.tok) - getTok(p) # we must consume a token here to prevend endless loops! - result = ast.emptyNode - -proc namedParams(p: var TParser, callee: PNode, - kind: TNodeKind, endTok: TTokType): PNode = - let a = callee - result = newNodeP(kind, p) - addSon(result, a) - exprColonEqExprListAux(p, endTok, result) - -proc parseMacroColon(p: var TParser, x: PNode): PNode -proc primarySuffix(p: var TParser, r: PNode): PNode = - #| primarySuffix = '(' (exprColonEqExpr comma?)* ')' doBlocks? - #| | doBlocks - #| | '.' optInd symbol generalizedLit? - #| | '[' optInd indexExprList optPar ']' - #| | '{' optInd indexExprList optPar '}' - #| | &( '`'|IDENT|literal|'cast'|'addr'|'type') expr # command syntax - result = r - - template somePar() = discard - while p.tok.indent < 0: - case p.tok.tokType - of tkParLe: - somePar() - result = namedParams(p, result, nkCall, tkParRi) - if result.len > 1 and result.sons[1].kind == nkExprColonExpr: - result.kind = nkObjConstr - else: - parseDoBlocks(p, result) - of tkDo: - var a = result - result = newNodeP(nkCall, p) - addSon(result, a) - parseDoBlocks(p, result) - of tkDot: - result = dotExpr(p, result) - result = parseGStrLit(p, result) - of tkBracketLe: - somePar() - result = namedParams(p, result, nkBracketExpr, tkBracketRi) - of tkBracketDotLe: - somePar() - result = namedParams(p, result, nkCurlyExpr, tkBracketDotRi) - of tkSymbol, tkAccent, tkIntLit..tkCharLit, tkNil, tkCast, tkAddr, tkType: - if p.inPragma == 0: - # actually parsing {.push hints:off.} as {.push(hints:off).} is a sweet - # solution, but pragmas.nim can't handle that - let a = result - result = newNodeP(nkCommand, p) - addSon(result, a) - when true: - addSon result, parseExpr(p) - else: - while p.tok.tokType != tkEof: - let x = parseExpr(p) - addSon(result, x) - if p.tok.tokType != tkComma: break - getTok(p) - optInd(p, x) - if p.tok.tokType == tkDo: - parseDoBlocks(p, result) - else: - result = parseMacroColon(p, result) - break - else: - break - -proc primary(p: var TParser, mode: TPrimaryMode): PNode -proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode - -proc parseOperators(p: var TParser, headNode: PNode, - limit: int, mode: TPrimaryMode): PNode = - result = headNode - # expand while operators have priorities higher than 'limit' - var opPrec = getPrecedence(p.tok) - let modeB = if mode == pmTypeDef: pmTypeDesc else: mode - # the operator itself must not start on a new line: - while opPrec >= limit and p.tok.indent < 0 and not isAt(p.tok): - checkBinary(p) - var leftAssoc = 1-ord(isRightAssociative(p.tok)) - var a = newNodeP(nkInfix, p) - var opNode = newIdentNodeP(p.tok.ident, p) # skip operator: - getTok(p) - optInd(p, a) - # read sub-expression with higher priority: - var b = simpleExprAux(p, opPrec + leftAssoc, modeB) - addSon(a, opNode) - addSon(a, result) - addSon(a, b) - result = a - opPrec = getPrecedence(p.tok) - -proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode = - result = primary(p, mode) - result = parseOperators(p, result, limit, mode) - -proc simpleExpr(p: var TParser, mode = pmNormal): PNode = - result = simpleExprAux(p, -1, mode) - -proc parseIfExpr(p: var TParser, kind: TNodeKind): PNode = - #| condExpr = expr colcom expr optInd - #| ('elif' expr colcom expr optInd)* - #| 'else' colcom expr - #| ifExpr = 'if' condExpr - #| whenExpr = 'when' condExpr - result = newNodeP(kind, p) - while true: - getTok(p) # skip `if`, `elif` - var branch = newNodeP(nkElifExpr, p) - addSon(branch, parseExpr(p)) - colcom(p, branch) - addSon(branch, parseExpr(p)) - optInd(p, branch) - addSon(result, branch) - if p.tok.tokType != tkElif: break - var branch = newNodeP(nkElseExpr, p) - eat(p, tkElse) - colcom(p, branch) - addSon(branch, parseExpr(p)) - addSon(result, branch) - -proc parsePragma(p: var TParser): PNode = - result = newNodeP(nkPragma, p) - inc p.inPragma - if isAt(p.tok): - while isAt(p.tok): - getTok(p) - var a = parseExpr(p) - optInd(p, a) - if a.kind in nkCallKinds and a.len == 2: - let repaired = newNodeI(nkExprColonExpr, a.info) - repaired.add a[0] - repaired.add a[1] - a = repaired - addSon(result, a) - skipComment(p, a) - else: - getTok(p) - optInd(p, result) - while p.tok.tokType notin {tkCurlyDotRi, tkCurlyRi, tkEof}: - var a = exprColonEqExpr(p) - addSon(result, a) - if p.tok.tokType == tkComma: - getTok(p) - skipComment(p, a) - optPar(p) - if p.tok.tokType in {tkCurlyDotRi, tkCurlyRi}: getTok(p) - else: parMessage(p, errTokenExpected, ".}") - dec p.inPragma - -proc identVis(p: var TParser; allowDot=false): PNode = - #| identVis = symbol opr? # postfix position - #| identVisDot = symbol '.' optInd symbol opr? - var a = parseSymbol(p) - if p.tok.tokType == tkOpr: - result = newNodeP(nkPostfix, p) - addSon(result, newIdentNodeP(p.tok.ident, p)) - addSon(result, a) - getTok(p) - elif p.tok.tokType == tkDot and allowDot: - result = dotExpr(p, a) - else: - result = a - -proc identWithPragma(p: var TParser; allowDot=false): PNode = - #| identWithPragma = identVis pragma? - #| identWithPragmaDot = identVisDot pragma? - var a = identVis(p, allowDot) - if p.tok.tokType == tkCurlyDotLe or isAt(p.tok): - result = newNodeP(nkPragmaExpr, p) - addSon(result, a) - addSon(result, parsePragma(p)) - else: - result = a - -type - TDeclaredIdentFlag = enum - withPragma, # identifier may have pragma - withBothOptional # both ':' and '=' parts are optional - TDeclaredIdentFlags = set[TDeclaredIdentFlag] - -proc parseIdentColonEquals(p: var TParser, flags: TDeclaredIdentFlags): PNode = - #| declColonEquals = identWithPragma (comma identWithPragma)* comma? - #| (':' optInd typeDesc)? ('=' optInd expr)? - #| identColonEquals = ident (comma ident)* comma? - #| (':' optInd typeDesc)? ('=' optInd expr)?) - var a: PNode - result = newNodeP(nkIdentDefs, p) - while true: - case p.tok.tokType - of tkSymbol, tkAccent: - if withPragma in flags: a = identWithPragma(p) - else: a = parseSymbol(p) - if a.kind == nkEmpty: return - else: break - addSon(result, a) - if p.tok.tokType != tkComma: break - getTok(p) - optInd(p, a) - if p.tok.tokType == tkColon: - getTok(p) - optInd(p, result) - addSon(result, parseTypeDesc(p)) - else: - addSon(result, ast.emptyNode) - if p.tok.tokType != tkEquals and withBothOptional notin flags: - parMessage(p, errColonOrEqualsExpected, p.tok) - if p.tok.tokType == tkEquals: - getTok(p) - optInd(p, result) - addSon(result, parseExpr(p)) - else: - addSon(result, ast.emptyNode) - -proc parseTuple(p: var TParser): PNode = - result = newNodeP(nkTupleTy, p) - getTok(p) - if p.tok.tokType in {tkBracketLe, tkCurlyLe}: - let usedCurly = p.tok.tokType == tkCurlyLe - getTok(p) - optInd(p, result) - while p.tok.tokType in {tkSymbol, tkAccent}: - var a = parseIdentColonEquals(p, {}) - addSon(result, a) - if p.tok.tokType notin {tkComma, tkSemiColon}: break - getTok(p) - skipComment(p, a) - optPar(p) - if usedCurly: eat(p, tkCurlyRi) - else: eat(p, tkBracketRi) - else: - result = newNodeP(nkTupleClassTy, p) - -proc parseParamList(p: var TParser, retColon = true): PNode = - #| paramList = '(' declColonEquals ^* (comma/semicolon) ')' - #| paramListArrow = paramList? ('->' optInd typeDesc)? - #| paramListColon = paramList? (':' optInd typeDesc)? - var a: PNode - result = newNodeP(nkFormalParams, p) - addSon(result, ast.emptyNode) # return type - let hasParLe = p.tok.tokType == tkParLe and p.tok.indent < 0 - if hasParLe: - getTok(p) - optInd(p, result) - while true: - case p.tok.tokType - of tkSymbol, tkAccent: - a = parseIdentColonEquals(p, {withBothOptional, withPragma}) - of tkParRi: - break - else: - parMessage(p, errTokenExpected, ")") - break - addSon(result, a) - if p.tok.tokType notin {tkComma, tkSemiColon}: break - getTok(p) - skipComment(p, a) - optPar(p) - eat(p, tkParRi) - let hasRet = if retColon: p.tok.tokType == tkColon - else: p.tok.tokType == tkOpr and p.tok.ident.s == "->" - if hasRet and p.tok.indent < 0: - getTok(p) - optInd(p, result) - result.sons[0] = parseTypeDesc(p) - elif not retColon and not hasParle: - # Mark as "not there" in order to mark for deprecation in the semantic pass: - result = ast.emptyNode - -proc optPragmas(p: var TParser): PNode = - if p.tok.tokType == tkCurlyDotLe or isAt(p.tok): - result = parsePragma(p) - else: - result = ast.emptyNode - -proc parseDoBlock(p: var TParser): PNode = - #| doBlock = 'do' paramListArrow pragmas? colcom stmt - let info = parLineInfo(p) - getTok(p) - let params = parseParamList(p, retColon=false) - let pragmas = optPragmas(p) - colcom(p, result) - result = newProcNode(nkDo, info, parseStmt(p), - params = params, - pragmas = pragmas) - -proc parseDoBlocks(p: var TParser, call: PNode) = - #| doBlocks = doBlock ^* IND{=} - while p.tok.tokType == tkDo: - addSon(call, parseDoBlock(p)) - -proc parseCurlyStmt(p: var TParser): PNode = - result = newNodeP(nkStmtList, p) - eat(p, tkCurlyLe) - result.add parseStmt(p) - while p.tok.tokType notin {tkEof, tkCurlyRi}: - if p.tok.tokType == tkSemicolon: getTok(p) - elif p.tok.indent < 0: break - result.add parseStmt(p) - eat(p, tkCurlyRi) - -proc parseProcExpr(p: var TParser, isExpr: bool): PNode = - #| procExpr = 'proc' paramListColon pragmas? ('=' COMMENT? stmt)? - # either a proc type or a anonymous proc - let info = parLineInfo(p) - getTok(p) - let hasSignature = p.tok.tokType in {tkParLe, tkColon} and p.tok.indent < 0 - let params = parseParamList(p) - let pragmas = optPragmas(p) - if p.tok.tokType == tkCurlyLe and isExpr: - result = newProcNode(nkLambda, info, parseCurlyStmt(p), - params = params, - pragmas = pragmas) - else: - result = newNodeI(nkProcTy, info) - if hasSignature: - addSon(result, params) - addSon(result, pragmas) - -proc isExprStart(p: TParser): bool = - case p.tok.tokType - of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf, - tkProc, tkIterator, tkBind, tkAddr, - tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCharLit, tkVar, tkRef, tkPtr, - tkTuple, tkObject, tkType, tkWhen, tkCase, tkOut: - result = true - else: result = false - -proc parseSymbolList(p: var TParser, result: PNode, allowNil = false) = - while true: - var s = parseSymbol(p, allowNil) - if s.kind == nkEmpty: break - addSon(result, s) - if p.tok.tokType != tkComma: break - getTok(p) - optInd(p, s) - -proc parseTypeDescKAux(p: var TParser, kind: TNodeKind, - mode: TPrimaryMode): PNode = - #| distinct = 'distinct' optInd typeDesc - result = newNodeP(kind, p) - getTok(p) - optInd(p, result) - if not isOperator(p.tok) and isExprStart(p): - addSon(result, primary(p, mode)) - if kind == nkDistinctTy and p.tok.tokType == tkSymbol: - var nodeKind: TNodeKind - if p.tok.ident.s == "with": - nodeKind = nkWith - elif p.tok.ident.s == "without": - nodeKind = nkWithout - else: - return result - getTok(p) - let list = newNodeP(nodeKind, p) - result.addSon list - parseSymbolList(p, list, allowNil = true) - -proc parseExpr(p: var TParser): PNode = - #| expr = (ifExpr - #| | whenExpr - #| | caseExpr - #| | tryExpr) - #| / simpleExpr - case p.tok.tokType: - of tkIf: result = parseIfExpr(p, nkIfExpr) - of tkWhen: result = parseIfExpr(p, nkWhenExpr) - of tkCase: result = parseCase(p) - of tkTry: result = parseTry(p) - else: result = simpleExpr(p) - -proc parseEnum(p: var TParser): PNode -proc parseObject(p: var TParser): PNode -proc parseTypeClass(p: var TParser): PNode - -proc primary(p: var TParser, mode: TPrimaryMode): PNode = - #| typeKeyw = 'var' | 'out' | 'ref' | 'ptr' | 'shared' | 'tuple' - #| | 'proc' | 'iterator' | 'distinct' | 'object' | 'enum' - #| primary = typeKeyw typeDescK - #| / prefixOperator* identOrLiteral primarySuffix* - #| / 'static' primary - #| / 'bind' primary - if isOperator(p.tok): - let isSigil = isSigilLike(p.tok) - result = newNodeP(nkPrefix, p) - var a = newIdentNodeP(p.tok.ident, p) - addSon(result, a) - getTok(p) - optInd(p, a) - if isSigil: - #XXX prefix operators - addSon(result, primary(p, pmSkipSuffix)) - result = primarySuffix(p, result) - else: - addSon(result, primary(p, pmNormal)) - return - - case p.tok.tokType: - of tkTuple: result = parseTuple(p) - of tkProc: result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef}) - of tkIterator: - result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef}) - if result.kind == nkLambda: result.kind = nkIteratorDef - else: result.kind = nkIteratorTy - of tkEnum: - if mode == pmTypeDef: - result = parseEnum(p) - else: - result = newNodeP(nkEnumTy, p) - getTok(p) - of tkObject: - if mode == pmTypeDef: - result = parseObject(p) - else: - result = newNodeP(nkObjectTy, p) - getTok(p) - of tkConcept: - if mode == pmTypeDef: - result = parseTypeClass(p) - else: - parMessage(p, errInvalidToken, p.tok) - of tkStatic: - let info = parLineInfo(p) - getTokNoInd(p) - let next = primary(p, pmNormal) - if next.kind == nkBracket and next.sonsLen == 1: - result = newNode(nkStaticTy, info, @[next.sons[0]]) - else: - result = newNode(nkStaticExpr, info, @[next]) - of tkBind: - result = newNodeP(nkBind, p) - getTok(p) - optInd(p, result) - addSon(result, primary(p, pmNormal)) - of tkVar: result = parseTypeDescKAux(p, nkVarTy, mode) - of tkOut: result = parseTypeDescKAux(p, nkVarTy, mode) - of tkRef: result = parseTypeDescKAux(p, nkRefTy, mode) - of tkPtr: result = parseTypeDescKAux(p, nkPtrTy, mode) - of tkDistinct: result = parseTypeDescKAux(p, nkDistinctTy, mode) - else: - result = identOrLiteral(p, mode) - if mode != pmSkipSuffix: - result = primarySuffix(p, result) - -proc parseTypeDesc(p: var TParser): PNode = - #| typeDesc = simpleExpr - result = simpleExpr(p, pmTypeDesc) - -proc parseTypeDefAux(p: var TParser): PNode = - #| typeDefAux = simpleExpr - #| | 'concept' typeClass - result = simpleExpr(p, pmTypeDef) - -proc makeCall(n: PNode): PNode = - ## Creates a call if the given node isn't already a call. - if n.kind in nkCallKinds: - result = n - else: - result = newNodeI(nkCall, n.info) - result.add n - -proc parseMacroColon(p: var TParser, x: PNode): PNode = - #| macroColon = ':' stmt? ( IND{=} 'of' exprList ':' stmt - #| | IND{=} 'elif' expr ':' stmt - #| | IND{=} 'except' exprList ':' stmt - #| | IND{=} 'else' ':' stmt )* - result = x - if p.tok.tokType == tkColon and p.tok.indent < 0: - result = makeCall(result) - getTok(p) - skipComment(p, result) - let stmtList = newNodeP(nkStmtList, p) - if p.tok.tokType notin {tkOf, tkElif, tkElse, tkExcept}: - let body = parseStmt(p) - stmtList.add body - #addSon(result, makeStmtList(body)) - while true: - var b: PNode - case p.tok.tokType - of tkOf: - b = newNodeP(nkOfBranch, p) - exprList(p, tkCurlyLe, b) - of tkElif: - b = newNodeP(nkElifBranch, p) - getTok(p) - optInd(p, b) - addSon(b, parseExpr(p)) - of tkExcept: - b = newNodeP(nkExceptBranch, p) - exprList(p, tkCurlyLe, b) - of tkElse: - b = newNodeP(nkElse, p) - getTok(p) - else: break - addSon(b, parseCurlyStmt(p)) - addSon(stmtList, b) - if b.kind == nkElse: break - if stmtList.len == 1 and stmtList[0].kind == nkStmtList: - # to keep backwards compatibility (see tests/vm/tstringnil) - result.add stmtList[0] - else: - result.add stmtList - -proc parseExprStmt(p: var TParser): PNode = - #| exprStmt = simpleExpr - #| (( '=' optInd expr ) - #| / ( expr ^+ comma - #| doBlocks - #| / macroColon - #| ))? - var a = simpleExpr(p) - if p.tok.tokType == tkEquals: - getTok(p) - optInd(p, result) - var b = parseExpr(p) - result = newNodeI(nkAsgn, a.info) - addSon(result, a) - addSon(result, b) - else: - # simpleExpr parsed 'p a' from 'p a, b'? - if p.tok.indent < 0 and p.tok.tokType == tkComma and a.kind == nkCommand: - result = a - while true: - getTok(p) - optInd(p, result) - var e = parseExpr(p) - addSon(result, e) - if p.tok.tokType != tkComma: break - elif p.tok.indent < 0 and isExprStart(p): - if a.kind == nkCommand: - result = a - else: - result = newNode(nkCommand, a.info, @[a]) - while true: - var e = parseExpr(p) - addSon(result, e) - if p.tok.tokType != tkComma: break - getTok(p) - optInd(p, result) - else: - result = a - if p.tok.tokType == tkDo and p.tok.indent < 0: - result = makeCall(result) - parseDoBlocks(p, result) - return result - result = parseMacroColon(p, result) - -proc parseModuleName(p: var TParser, kind: TNodeKind): PNode = - result = parseExpr(p) - -proc parseImport(p: var TParser, kind: TNodeKind): PNode = - #| importStmt = 'import' optInd expr - #| ((comma expr)* - #| / 'except' optInd (expr ^+ comma)) - result = newNodeP(kind, p) - getTok(p) # skip `import` or `export` - optInd(p, result) - var a = parseModuleName(p, kind) - addSon(result, a) - if p.tok.tokType in {tkComma, tkExcept}: - if p.tok.tokType == tkExcept: - result.kind = succ(kind) - getTok(p) - optInd(p, result) - while true: - # was: while p.tok.tokType notin {tkEof, tkSad, tkDed}: - a = parseModuleName(p, kind) - if a.kind == nkEmpty: break - addSon(result, a) - if p.tok.tokType != tkComma: break - getTok(p) - optInd(p, a) - #expectNl(p) - -proc parseIncludeStmt(p: var TParser): PNode = - #| includeStmt = 'include' optInd expr ^+ comma - result = newNodeP(nkIncludeStmt, p) - getTok(p) # skip `import` or `include` - optInd(p, result) - while true: - # was: while p.tok.tokType notin {tkEof, tkSad, tkDed}: - var a = parseExpr(p) - if a.kind == nkEmpty: break - addSon(result, a) - if p.tok.tokType != tkComma: break - getTok(p) - optInd(p, a) - #expectNl(p) - -proc parseFromStmt(p: var TParser): PNode = - #| fromStmt = 'from' moduleName 'import' optInd expr (comma expr)* - result = newNodeP(nkFromStmt, p) - getTok(p) # skip `from` - optInd(p, result) - var a = parseModuleName(p, nkImportStmt) - addSon(result, a) #optInd(p, a); - eat(p, tkImport) - optInd(p, result) - while true: - # p.tok.tokType notin {tkEof, tkSad, tkDed}: - a = parseExpr(p) - if a.kind == nkEmpty: break - addSon(result, a) - if p.tok.tokType != tkComma: break - getTok(p) - optInd(p, a) - #expectNl(p) - -proc parseReturnOrRaise(p: var TParser, kind: TNodeKind): PNode = - #| returnStmt = 'return' optInd expr? - #| raiseStmt = 'raise' optInd expr? - #| yieldStmt = 'yield' optInd expr? - #| discardStmt = 'discard' optInd expr? - #| breakStmt = 'break' optInd expr? - #| continueStmt = 'break' optInd expr? - result = newNodeP(kind, p) - getTok(p) - if p.tok.tokType == tkComment: - skipComment(p, result) - addSon(result, ast.emptyNode) - elif p.tok.indent >= 0 or not isExprStart(p): - # NL terminates: - addSon(result, ast.emptyNode) - else: - addSon(result, parseExpr(p)) - -proc parseIfOrWhen(p: var TParser, kind: TNodeKind): PNode = - #| condStmt = expr colcom stmt COMMENT? - #| (IND{=} 'elif' expr colcom stmt)* - #| (IND{=} 'else' colcom stmt)? - #| ifStmt = 'if' condStmt - #| whenStmt = 'when' condStmt - result = newNodeP(kind, p) - while true: - getTok(p) # skip `if`, `when`, `elif` - var branch = newNodeP(nkElifBranch, p) - optInd(p, branch) - addSon(branch, parseExpr(p)) - colcom(p, branch) - addSon(branch, parseCurlyStmt(p)) - skipComment(p, branch) - addSon(result, branch) - if p.tok.tokType != tkElif: break - if p.tok.tokType == tkElse: - var branch = newNodeP(nkElse, p) - eat(p, tkElse) - addSon(branch, parseCurlyStmt(p)) - addSon(result, branch) - -proc parseWhile(p: var TParser): PNode = - #| whileStmt = 'while' expr colcom stmt - result = newNodeP(nkWhileStmt, p) - getTok(p) - optInd(p, result) - addSon(result, parseExpr(p)) - colcom(p, result) - addSon(result, parseCurlyStmt(p)) - -proc parseCase(p: var TParser): PNode = - #| ofBranch = 'of' exprList colcom stmt - #| ofBranches = ofBranch (IND{=} ofBranch)* - #| (IND{=} 'elif' expr colcom stmt)* - #| (IND{=} 'else' colcom stmt)? - #| caseStmt = 'case' expr ':'? COMMENT? - #| (IND{>} ofBranches DED - #| | IND{=} ofBranches) - var - b: PNode - inElif= false - result = newNodeP(nkCaseStmt, p) - getTok(p) - addSon(result, parseExpr(p)) - eat(p, tkCurlyLe) - skipComment(p, result) - - while true: - case p.tok.tokType - of tkOf: - if inElif: break - b = newNodeP(nkOfBranch, p) - exprList(p, tkCurlyLe, b) - of tkElif: - inElif = true - b = newNodeP(nkElifBranch, p) - getTok(p) - optInd(p, b) - addSon(b, parseExpr(p)) - of tkElse: - b = newNodeP(nkElse, p) - getTok(p) - else: break - skipComment(p, b) - addSon(b, parseCurlyStmt(p)) - addSon(result, b) - if b.kind == nkElse: break - eat(p, tkCurlyRi) - -proc parseTry(p: var TParser): PNode = - #| tryStmt = 'try' colcom stmt &(IND{=}? 'except'|'finally') - #| (IND{=}? 'except' exprList colcom stmt)* - #| (IND{=}? 'finally' colcom stmt)? - #| tryExpr = 'try' colcom stmt &(optInd 'except'|'finally') - #| (optInd 'except' exprList colcom stmt)* - #| (optInd 'finally' colcom stmt)? - result = newNodeP(nkTryStmt, p) - getTok(p) - colcom(p, result) - addSon(result, parseCurlyStmt(p)) - var b: PNode = nil - while true: - case p.tok.tokType - of tkExcept: - b = newNodeP(nkExceptBranch, p) - exprList(p, tkCurlyLe, b) - of tkFinally: - b = newNodeP(nkFinally, p) - getTok(p) - else: break - skipComment(p, b) - addSon(b, parseCurlyStmt(p)) - addSon(result, b) - if b.kind == nkFinally: break - if b == nil: parMessage(p, errTokenExpected, "except") - -proc parseFor(p: var TParser): PNode = - #| forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt - result = newNodeP(nkForStmt, p) - getTokNoInd(p) - var a = identWithPragma(p) - addSon(result, a) - while p.tok.tokType == tkComma: - getTok(p) - optInd(p, a) - a = identWithPragma(p) - addSon(result, a) - eat(p, tkIn) - addSon(result, parseExpr(p)) - colcom(p, result) - addSon(result, parseCurlyStmt(p)) - -proc parseBlock(p: var TParser): PNode = - #| blockStmt = 'block' symbol? colcom stmt - result = newNodeP(nkBlockStmt, p) - getTokNoInd(p) - if p.tok.tokType == tkCurlyLe: addSon(result, ast.emptyNode) - else: addSon(result, parseSymbol(p)) - colcom(p, result) - addSon(result, parseCurlyStmt(p)) - -proc parseStaticOrDefer(p: var TParser; k: TNodeKind): PNode = - #| staticStmt = 'static' colcom stmt - #| deferStmt = 'defer' colcom stmt - result = newNodeP(k, p) - getTok(p) - colcom(p, result) - addSon(result, parseCurlyStmt(p)) - -proc parseAsm(p: var TParser): PNode = - #| asmStmt = 'asm' pragma? (STR_LIT | RSTR_LIT | TRIPLE_STR_LIT) - result = newNodeP(nkAsmStmt, p) - getTokNoInd(p) - if p.tok.tokType == tkCurlyDotLe or isAt(p.tok): addSon(result, parsePragma(p)) - else: addSon(result, ast.emptyNode) - case p.tok.tokType - of tkStrLit: addSon(result, newStrNodeP(nkStrLit, p.tok.literal, p)) - of tkRStrLit: addSon(result, newStrNodeP(nkRStrLit, p.tok.literal, p)) - of tkTripleStrLit: addSon(result, - newStrNodeP(nkTripleStrLit, p.tok.literal, p)) - else: - parMessage(p, errStringLiteralExpected) - addSon(result, ast.emptyNode) - return - getTok(p) - -proc parseGenericParam(p: var TParser): PNode = - #| genericParam = symbol (comma symbol)* (colon expr)? ('=' optInd expr)? - var a: PNode - result = newNodeP(nkIdentDefs, p) - while true: - case p.tok.tokType - of tkIn, tkOut: - let t = p.tok.tokType - getTok(p) - expectIdent(p) - a = parseSymbol(p) - of tkSymbol, tkAccent: - a = parseSymbol(p) - if a.kind == nkEmpty: return - else: break - addSon(result, a) - if p.tok.tokType != tkComma: break - getTok(p) - optInd(p, a) - if p.tok.tokType == tkColon: - getTok(p) - optInd(p, result) - addSon(result, parseExpr(p)) - else: - addSon(result, ast.emptyNode) - if p.tok.tokType == tkEquals: - getTok(p) - optInd(p, result) - addSon(result, parseExpr(p)) - else: - addSon(result, ast.emptyNode) - -proc parseGenericParamList(p: var TParser): PNode = - #| genericParamList = '[' optInd - #| genericParam ^* (comma/semicolon) optPar ']' - result = newNodeP(nkGenericParams, p) - getTok(p) - optInd(p, result) - while p.tok.tokType in {tkSymbol, tkAccent}: - var a = parseGenericParam(p) - addSon(result, a) - if p.tok.tokType notin {tkComma, tkSemiColon}: break - getTok(p) - skipComment(p, a) - optPar(p) - eat(p, tkBracketRi) - -proc parsePattern(p: var TParser): PNode = - eat(p, tkBracketDotLe) - result = parseStmt(p) - eat(p, tkBracketDotRi) - -proc validInd(p: TParser): bool = p.tok.indent < 0 - -proc parseRoutine(p: var TParser, kind: TNodeKind): PNode = - #| indAndComment = (IND{>} COMMENT)? | COMMENT? - #| routine = optInd identVis pattern? genericParamList? - #| paramListColon pragma? ('=' COMMENT? stmt)? indAndComment - result = newNodeP(kind, p) - getTok(p) - optInd(p, result) - addSon(result, identVis(p)) - if p.tok.tokType == tkBracketDotLe and p.validInd: - addSon(result, p.parsePattern) - else: - addSon(result, ast.emptyNode) - if p.tok.tokType == tkBracketLe and p.validInd: - result.add(p.parseGenericParamList) - else: - addSon(result, ast.emptyNode) - addSon(result, p.parseParamList) - if (p.tok.tokType == tkCurlyDotLe or isAt(p.tok)) and p.validInd: - addSon(result, p.parsePragma) - else: - addSon(result, ast.emptyNode) - # empty exception tracking: - addSon(result, ast.emptyNode) - if p.tok.tokType == tkCurlyLe: - addSon(result, parseCurlyStmt(p)) - else: - addSon(result, ast.emptyNode) - indAndComment(p, result) - -proc newCommentStmt(p: var TParser): PNode = - #| commentStmt = COMMENT - result = newNodeP(nkCommentStmt, p) - result.comment = p.tok.literal - getTok(p) - -type - TDefParser = proc (p: var TParser): PNode {.nimcall.} - -proc parseSection(p: var TParser, kind: TNodeKind, - defparser: TDefParser): PNode = - #| section(p) = COMMENT? p / (IND{>} (p / COMMENT)^+IND{=} DED) - result = newNodeP(kind, p) - if kind != nkTypeSection: getTok(p) - skipComment(p, result) - if p.tok.tokType == tkParLe: - getTok(p) - skipComment(p, result) - while true: - case p.tok.tokType - of tkSymbol, tkAccent, tkParLe: - var a = defparser(p) - skipComment(p, a) - addSon(result, a) - of tkComment: - var a = newCommentStmt(p) - addSon(result, a) - of tkParRi: break - else: - parMessage(p, errIdentifierExpected, p.tok) - break - eat(p, tkParRi) - if result.len == 0: parMessage(p, errIdentifierExpected, p.tok) - elif p.tok.tokType in {tkSymbol, tkAccent, tkBracketLe}: - # tkBracketLe is allowed for ``var [x, y] = ...`` tuple parsing - addSon(result, defparser(p)) - else: - parMessage(p, errIdentifierExpected, p.tok) - -proc parseConstant(p: var TParser): PNode = - #| constant = identWithPragma (colon typedesc)? '=' optInd expr indAndComment - result = newNodeP(nkConstDef, p) - addSon(result, identWithPragma(p)) - if p.tok.tokType == tkColon: - getTok(p) - optInd(p, result) - addSon(result, parseTypeDesc(p)) - else: - addSon(result, ast.emptyNode) - eat(p, tkEquals) - optInd(p, result) - addSon(result, parseExpr(p)) - indAndComment(p, result) - -proc parseEnum(p: var TParser): PNode = - #| enum = 'enum' optInd (symbol optInd ('=' optInd expr COMMENT?)? comma?)+ - result = newNodeP(nkEnumTy, p) - getTok(p) - addSon(result, ast.emptyNode) - optInd(p, result) - flexComment(p, result) - eat(p, tkCurlyLe) - optInd(p, result) - while p.tok.tokType notin {tkEof, tkCurlyRi}: - var a = parseSymbol(p) - if a.kind == nkEmpty: return - if p.tok.tokType == tkEquals: - getTok(p) - optInd(p, a) - var b = a - a = newNodeP(nkEnumFieldDef, p) - addSon(a, b) - addSon(a, parseExpr(p)) - if p.tok.indent < 0: - rawSkipComment(p, a) - if p.tok.tokType == tkComma: - getTok(p) - rawSkipComment(p, a) - addSon(result, a) - eat(p, tkCurlyRi) - if result.len <= 1: - lexMessageTok(p.lex, errIdentifierExpected, p.tok, prettyTok(p.tok)) - -proc parseObjectPart(p: var TParser; needsCurly: bool): PNode -proc parseObjectWhen(p: var TParser): PNode = - result = newNodeP(nkRecWhen, p) - while true: - getTok(p) # skip `when`, `elif` - var branch = newNodeP(nkElifBranch, p) - optInd(p, branch) - addSon(branch, parseExpr(p)) - colcom(p, branch) - addSon(branch, parseObjectPart(p, true)) - flexComment(p, branch) - addSon(result, branch) - if p.tok.tokType != tkElif: break - if p.tok.tokType == tkElse: - var branch = newNodeP(nkElse, p) - eat(p, tkElse) - colcom(p, branch) - addSon(branch, parseObjectPart(p, true)) - flexComment(p, branch) - addSon(result, branch) - -proc parseObjectCase(p: var TParser): PNode = - result = newNodeP(nkRecCase, p) - getTokNoInd(p) - var a = newNodeP(nkIdentDefs, p) - addSon(a, identWithPragma(p)) - eat(p, tkColon) - addSon(a, parseTypeDesc(p)) - addSon(a, ast.emptyNode) - addSon(result, a) - eat(p, tkCurlyLe) - flexComment(p, result) - while true: - var b: PNode - case p.tok.tokType - of tkOf: - b = newNodeP(nkOfBranch, p) - exprList(p, tkColon, b) - of tkElse: - b = newNodeP(nkElse, p) - getTok(p) - else: break - colcom(p, b) - var fields = parseObjectPart(p, true) - if fields.kind == nkEmpty: - parMessage(p, errIdentifierExpected, p.tok) - fields = newNodeP(nkNilLit, p) # don't break further semantic checking - addSon(b, fields) - addSon(result, b) - if b.kind == nkElse: break - eat(p, tkCurlyRi) - -proc parseObjectPart(p: var TParser; needsCurly: bool): PNode = - if p.tok.tokType == tkCurlyLe: - result = newNodeP(nkRecList, p) - getTok(p) - rawSkipComment(p, result) - while true: - case p.tok.tokType - of tkCase, tkWhen, tkSymbol, tkAccent, tkNil, tkDiscard: - addSon(result, parseObjectPart(p, false)) - of tkCurlyRi: break - else: - parMessage(p, errIdentifierExpected, p.tok) - break - eat(p, tkCurlyRi) - else: - if needsCurly: - parMessage(p, errTokenExpected, "{") - case p.tok.tokType - of tkWhen: - result = parseObjectWhen(p) - of tkCase: - result = parseObjectCase(p) - of tkSymbol, tkAccent: - result = parseIdentColonEquals(p, {withPragma}) - if p.tok.indent < 0: rawSkipComment(p, result) - of tkNil, tkDiscard: - result = newNodeP(nkNilLit, p) - getTok(p) - else: - result = ast.emptyNode - -proc parseObject(p: var TParser): PNode = - result = newNodeP(nkObjectTy, p) - getTok(p) - if (p.tok.tokType == tkCurlyDotLe or isAt(p.tok)) and p.validInd: - addSon(result, parsePragma(p)) - else: - addSon(result, ast.emptyNode) - if p.tok.tokType == tkOf and p.tok.indent < 0: - var a = newNodeP(nkOfInherit, p) - getTok(p) - addSon(a, parseTypeDesc(p)) - addSon(result, a) - else: - addSon(result, ast.emptyNode) - skipComment(p, result) - # an initial IND{>} HAS to follow: - addSon(result, parseObjectPart(p, true)) - -proc parseTypeClassParam(p: var TParser): PNode = - if p.tok.tokType in {tkOut, tkVar}: - result = newNodeP(nkVarTy, p) - getTok(p) - result.addSon(p.parseSymbol) - else: - result = p.parseSymbol - -proc parseTypeClass(p: var TParser): PNode = - #| typeClassParam = ('var' | 'out')? symbol - #| typeClass = typeClassParam ^* ',' (pragma)? ('of' typeDesc ^* ',')? - #| &IND{>} stmt - result = newNodeP(nkTypeClassTy, p) - getTok(p) - var args = newNodeP(nkArgList, p) - addSon(result, args) - addSon(args, p.parseTypeClassParam) - while p.tok.tokType == tkComma: - getTok(p) - addSon(args, p.parseTypeClassParam) - if (p.tok.tokType == tkCurlyDotLe or isAt(p.tok)) and p.validInd: - addSon(result, parsePragma(p)) - else: - addSon(result, ast.emptyNode) - if p.tok.tokType == tkOf and p.tok.indent < 0: - var a = newNodeP(nkOfInherit, p) - getTok(p) - while true: - addSon(a, parseTypeDesc(p)) - if p.tok.tokType != tkComma: break - getTok(p) - addSon(result, a) - else: - addSon(result, ast.emptyNode) - if p.tok.tokType == tkComment: - skipComment(p, result) - addSon(result, parseCurlyStmt(p)) - -proc parseTypeDef(p: var TParser): PNode = - #| - #| typeDef = identWithPragmaDot genericParamList? '=' optInd typeDefAux - #| indAndComment? - result = newNodeP(nkTypeDef, p) - addSon(result, identWithPragma(p, allowDot=true)) - if p.tok.tokType == tkBracketLe and p.validInd: - addSon(result, parseGenericParamList(p)) - else: - addSon(result, ast.emptyNode) - if p.tok.tokType == tkEquals: - getTok(p) - optInd(p, result) - addSon(result, parseTypeDefAux(p)) - else: - addSon(result, ast.emptyNode) - indAndComment(p, result) # special extension! - -proc parseVarTuple(p: var TParser): PNode = - #| varTuple = '(' optInd identWithPragma ^+ comma optPar ')' '=' optInd expr - result = newNodeP(nkVarTuple, p) - getTok(p) # skip '(' - optInd(p, result) - while p.tok.tokType in {tkSymbol, tkAccent}: - var a = identWithPragma(p) - addSon(result, a) - if p.tok.tokType != tkComma: break - getTok(p) - skipComment(p, a) - addSon(result, ast.emptyNode) # no type desc - optPar(p) - eat(p, tkBracketRi) - eat(p, tkEquals) - optInd(p, result) - addSon(result, parseExpr(p)) - -proc parseVariable(p: var TParser): PNode = - #| variable = (varTuple / identColonEquals) indAndComment - if p.tok.tokType == tkBracketLe: result = parseVarTuple(p) - else: result = parseIdentColonEquals(p, {withPragma}) - indAndComment(p, result) - -proc parseBind(p: var TParser, k: TNodeKind): PNode = - #| bindStmt = 'bind' optInd qualifiedIdent ^+ comma - #| mixinStmt = 'mixin' optInd qualifiedIdent ^+ comma - result = newNodeP(k, p) - getTok(p) - optInd(p, result) - while true: - var a = qualifiedIdent(p) - addSon(result, a) - if p.tok.tokType != tkComma: break - getTok(p) - optInd(p, a) - -proc parseStmtPragma(p: var TParser): PNode = - result = parsePragma(p) - if p.tok.tokType == tkCurlyLe: - let a = result - result = newNodeI(nkPragmaBlock, a.info) - getTok(p) - skipComment(p, result) - result.add a - result.add parseStmt(p) - eat(p, tkCurlyRi) - -proc simpleStmt(p: var TParser): PNode = - case p.tok.tokType - of tkReturn: result = parseReturnOrRaise(p, nkReturnStmt) - of tkRaise: result = parseReturnOrRaise(p, nkRaiseStmt) - of tkYield: result = parseReturnOrRaise(p, nkYieldStmt) - of tkDiscard: result = parseReturnOrRaise(p, nkDiscardStmt) - of tkBreak: result = parseReturnOrRaise(p, nkBreakStmt) - of tkContinue: result = parseReturnOrRaise(p, nkContinueStmt) - of tkCurlyDotLe: result = parseStmtPragma(p) - of tkImport: result = parseImport(p, nkImportStmt) - of tkExport: result = parseImport(p, nkExportStmt) - of tkFrom: result = parseFromStmt(p) - of tkInclude: result = parseIncludeStmt(p) - of tkComment: result = newCommentStmt(p) - else: - if isExprStart(p): result = parseExprStmt(p) - else: result = ast.emptyNode - if result.kind notin {nkEmpty, nkCommentStmt}: skipComment(p, result) - -proc complexOrSimpleStmt(p: var TParser): PNode = - case p.tok.tokType - of tkIf: result = parseIfOrWhen(p, nkIfStmt) - of tkWhile: result = parseWhile(p) - of tkCase: result = parseCase(p) - of tkTry: result = parseTry(p) - of tkFor: result = parseFor(p) - of tkBlock: result = parseBlock(p) - of tkStatic: result = parseStaticOrDefer(p, nkStaticStmt) - of tkDefer: result = parseStaticOrDefer(p, nkDefer) - of tkAsm: result = parseAsm(p) - of tkProc: result = parseRoutine(p, nkProcDef) - of tkMethod: result = parseRoutine(p, nkMethodDef) - of tkIterator: result = parseRoutine(p, nkIteratorDef) - of tkMacro: result = parseRoutine(p, nkMacroDef) - of tkTemplate: result = parseRoutine(p, nkTemplateDef) - of tkConverter: result = parseRoutine(p, nkConverterDef) - of tkType: - getTok(p) - if p.tok.tokType == tkBracketLe: - getTok(p) - result = newNodeP(nkTypeOfExpr, p) - result.addSon(primary(p, pmTypeDesc)) - eat(p, tkBracketRi) - result = parseOperators(p, result, -1, pmNormal) - else: - result = parseSection(p, nkTypeSection, parseTypeDef) - of tkConst: result = parseSection(p, nkConstSection, parseConstant) - of tkLet: result = parseSection(p, nkLetSection, parseVariable) - of tkWhen: result = parseIfOrWhen(p, nkWhenStmt) - of tkVar: result = parseSection(p, nkVarSection, parseVariable) - of tkBind: result = parseBind(p, nkBindStmt) - of tkMixin: result = parseBind(p, nkMixinStmt) - of tkUsing: result = parseSection(p, nkUsingStmt, parseVariable) - else: result = simpleStmt(p) - -proc parseStmt(p: var TParser): PNode = - result = complexOrSimpleStmt(p) - -proc parseAll*(p: var TParser): PNode = - ## Parses the rest of the input stream held by the parser into a PNode. - result = newNodeP(nkStmtList, p) - while p.tok.tokType != tkEof: - var a = complexOrSimpleStmt(p) - if a.kind != nkEmpty: - addSon(result, a) - else: - parMessage(p, errExprExpected, p.tok) - # bugfix: consume a token here to prevent an endless loop: - getTok(p) - -proc parseTopLevelStmt*(p: var TParser): PNode = - ## Implements an iterator which, when called repeatedly, returns the next - ## top-level statement or emptyNode if end of stream. - result = ast.emptyNode - while true: - case p.tok.tokType - of tkSemiColon: getTok(p) - of tkEof: break - else: - result = complexOrSimpleStmt(p) - if result.kind == nkEmpty: parMessage(p, errExprExpected, p.tok) - break diff --git a/compiler/platform.nim b/compiler/platform.nim index 01ddba23e..7eb897816 100644 --- a/compiler/platform.nim +++ b/compiler/platform.nim @@ -22,7 +22,7 @@ type osNone, osDos, osWindows, osOs2, osLinux, osMorphos, osSkyos, osSolaris, osIrix, osNetbsd, osFreebsd, osOpenbsd, osDragonfly, osAix, osPalmos, osQnx, osAmiga, osAtari, osNetware, osMacos, osMacosx, osHaiku, osAndroid, osVxworks - osGenode, osJS, osNimrodVM, osStandalone + osGenode, osJS, osNimVM, osStandalone, osNintendoSwitch type TInfoOSProp* = enum @@ -162,21 +162,26 @@ const pathSep: ":", dirSep: "/", scriptExt: ".sh", curDir: ".", exeExt: "", extSep: ".", props: {}), - (name: "NimrodVM", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/", + (name: "NimVM", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/", objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/", scriptExt: ".sh", curDir: ".", exeExt: "", extSep: ".", props: {}), (name: "Standalone", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/", objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/", scriptExt: ".sh", curDir: ".", exeExt: "", extSep: ".", - props: {})] + props: {}), + (name: "NintendoSwitch", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/", + objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/", + scriptExt: ".sh", curDir: ".", exeExt: ".elf", extSep: ".", + props: {ospNeedsPIC, ospPosix}), + ] type TSystemCPU* = enum # Also add CPU for in initialization section and # alias conditionals to condsyms (end of module). cpuNone, cpuI386, cpuM68k, cpuAlpha, cpuPowerpc, cpuPowerpc64, cpuPowerpc64el, cpuSparc, cpuVm, cpuIa64, cpuAmd64, cpuMips, cpuMipsel, - cpuArm, cpuArm64, cpuJS, cpuNimrodVM, cpuAVR, cpuMSP430, cpuSparc64, - cpuMips64, cpuMips64el + cpuArm, cpuArm64, cpuJS, cpuNimVM, cpuAVR, cpuMSP430, cpuSparc64, + cpuMips64, cpuMips64el, cpuRiscV64 type TEndian* = enum @@ -202,51 +207,48 @@ const (name: "arm", intSize: 32, endian: littleEndian, floatSize: 64, bit: 32), (name: "arm64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64), (name: "js", intSize: 32, endian: bigEndian,floatSize: 64,bit: 32), - (name: "nimrodvm", intSize: 32, endian: bigEndian, floatSize: 64, bit: 32), + (name: "nimvm", intSize: 32, endian: bigEndian, floatSize: 64, bit: 32), (name: "avr", intSize: 16, endian: littleEndian, floatSize: 32, bit: 16), (name: "msp430", intSize: 16, endian: littleEndian, floatSize: 32, bit: 16), (name: "sparc64", intSize: 64, endian: bigEndian, floatSize: 64, bit: 64), (name: "mips64", intSize: 64, endian: bigEndian, floatSize: 64, bit: 64), - (name: "mips64el", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64)] - -var - targetCPU*, hostCPU*: TSystemCPU - targetOS*, hostOS*: TSystemOS - -proc nameToOS*(name: string): TSystemOS -proc nameToCPU*(name: string): TSystemCPU - -var - intSize*: int - floatSize*: int - ptrSize*: int - tnl*: string # target newline + (name: "mips64el", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64), + (name: "riscv64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64)] -proc setTarget*(o: TSystemOS, c: TSystemCPU) = +type + Target* = object + targetCPU*, hostCPU*: TSystemCPU + targetOS*, hostOS*: TSystemOS + intSize*: int + floatSize*: int + ptrSize*: int + tnl*: string # target newline + +proc setTarget*(t: var Target; o: TSystemOS, c: TSystemCPU) = assert(c != cpuNone) assert(o != osNone) #echo "new Target: OS: ", o, " CPU: ", c - targetCPU = c - targetOS = o - intSize = CPU[c].intSize div 8 - floatSize = CPU[c].floatSize div 8 - ptrSize = CPU[c].bit div 8 - tnl = OS[o].newLine - -proc nameToOS(name: string): TSystemOS = + t.targetCPU = c + t.targetOS = o + # assume no cross-compiling + t.hostCPU = c + t.hostOS = o + t.intSize = CPU[c].intSize div 8 + t.floatSize = CPU[c].floatSize div 8 + t.ptrSize = CPU[c].bit div 8 + t.tnl = OS[o].newLine + +proc nameToOS*(name: string): TSystemOS = for i in countup(succ(osNone), high(TSystemOS)): if cmpIgnoreStyle(name, OS[i].name) == 0: return i result = osNone -proc nameToCPU(name: string): TSystemCPU = +proc nameToCPU*(name: string): TSystemCPU = for i in countup(succ(cpuNone), high(TSystemCPU)): if cmpIgnoreStyle(name, CPU[i].name) == 0: return i result = cpuNone -hostCPU = nameToCPU(system.hostCPU) -hostOS = nameToOS(system.hostOS) - -setTarget(hostOS, hostCPU) # assume no cross-compiling - +proc setTargetFromSystem*(t: var Target) = + t.setTarget(nameToOS(system.hostOS), nameToCPU(system.hostCPU)) diff --git a/compiler/plugins/active.nim b/compiler/plugins/active.nim index 7b6411178..7b5306f9c 100644 --- a/compiler/plugins/active.nim +++ b/compiler/plugins/active.nim @@ -10,4 +10,15 @@ ## Include file that imports all plugins that are active. import - locals.locals, itersgen + "../compiler" / [pluginsupport, idents, ast], locals, itersgen + +const + plugins: array[2, Plugin] = [ + ("stdlib", "system", "iterToProc", iterToProcImpl), + ("stdlib", "system", "locals", semLocals) + ] + +proc getPlugin*(ic: IdentCache; fn: PSym): Transformation = + for p in plugins: + if pluginMatches(ic, p, fn): return p.t + return nil diff --git a/compiler/plugins/itersgen.nim b/compiler/plugins/itersgen.nim index f44735b77..440d2e081 100644 --- a/compiler/plugins/itersgen.nim +++ b/compiler/plugins/itersgen.nim @@ -9,30 +9,29 @@ ## Plugin to transform an inline iterator into a data structure. -import compiler/pluginsupport, compiler/ast, compiler/astalgo, - compiler/magicsys, compiler/lookups, compiler/semdata, - compiler/lambdalifting, compiler/rodread, compiler/msgs +import ".." / [ast, astalgo, + magicsys, lookups, semdata, + lambdalifting, msgs] - -proc iterToProcImpl(c: PContext, n: PNode): PNode = +proc iterToProcImpl*(c: PContext, n: PNode): PNode = result = newNodeI(nkStmtList, n.info) let iter = n[1] if iter.kind != nkSym or iter.sym.kind != skIterator: - localError(iter.info, "first argument needs to be an iterator") + localError(c.config, iter.info, "first argument needs to be an iterator") return if n[2].typ.isNil: - localError(n[2].info, "second argument needs to be a type") + localError(c.config, n[2].info, "second argument needs to be a type") return if n[3].kind != nkIdent: - localError(n[3].info, "third argument needs to be an identifier") + localError(c.config, n[3].info, "third argument needs to be an identifier") return let t = n[2].typ.skipTypes({tyTypeDesc, tyGenericInst}) if t.kind notin {tyRef, tyPtr} or t.lastSon.kind != tyObject: - localError(n[2].info, + localError(c.config, n[2].info, "type must be a non-generic ref|ptr to object with state field") return - let body = liftIterToProc(iter.sym, iter.sym.getBody, t) + let body = liftIterToProc(c.graph, iter.sym, iter.sym.getBody, t) let prc = newSym(skProc, n[3].ident, iter.sym.owner, iter.sym.info) prc.typ = copyType(iter.sym.typ, prc, false) @@ -41,11 +40,9 @@ proc iterToProcImpl(c: PContext, n: PNode): PNode = prc.typ.rawAddSon t let orig = iter.sym.ast prc.ast = newProcNode(nkProcDef, n.info, - name = newSymNode(prc), - params = orig[paramsPos], - pragmas = orig[pragmasPos], - body = body) + body = body, params = orig[paramsPos], name = newSymNode(prc), + pattern = c.graph.emptyNode, genericParams = c.graph.emptyNode, + pragmas = orig[pragmasPos], exceptions = c.graph.emptyNode) + prc.ast.add iter.sym.ast.sons[resultPos] addInterfaceDecl(c, prc) - -registerPlugin("stdlib", "system", "iterToProc", iterToProcImpl) diff --git a/compiler/plugins/locals/locals.nim b/compiler/plugins/locals.nim index 338e7bcac..0048ff985 100644 --- a/compiler/plugins/locals/locals.nim +++ b/compiler/plugins/locals.nim @@ -9,10 +9,10 @@ ## The builtin 'system.locals' implemented as a plugin. -import compiler/pluginsupport, compiler/ast, compiler/astalgo, - compiler/magicsys, compiler/lookups, compiler/semdata, compiler/lowerings +import ".." / [pluginsupport, ast, astalgo, + magicsys, lookups, semdata, lowerings] -proc semLocals(c: PContext, n: PNode): PNode = +proc semLocals*(c: PContext, n: PNode): PNode = var counter = 0 var tupleType = newTypeS(tyTuple, c) result = newNodeIT(nkPar, n.info, tupleType) @@ -39,5 +39,3 @@ proc semLocals(c: PContext, n: PNode): PNode = var a = newSymNode(it, result.info) if it.typ.skipTypes({tyGenericInst}).kind == tyVar: a = newDeref(a) result.add(a) - -registerPlugin("stdlib", "system", "locals", semLocals) diff --git a/compiler/pluginsupport.nim b/compiler/pluginsupport.nim index f67942c97..a44436f11 100644 --- a/compiler/pluginsupport.nim +++ b/compiler/pluginsupport.nim @@ -8,40 +8,26 @@ # ## Plugin support for the Nim compiler. Right now plugins -## need to be built with the compiler only: plugins using +## need to be built with the compiler only: plugins using ## DLLs or the FFI will not work. import ast, semdata, idents type Transformation* = proc (c: PContext; n: PNode): PNode {.nimcall.} - Plugin = ref object - fn, module, package: PIdent + Plugin* = tuple + package, module, fn: string t: Transformation - next: Plugin -proc pluginMatches(p: Plugin; s: PSym): bool = - if s.name.id != p.fn.id: +proc pluginMatches*(ic: IdentCache; p: Plugin; s: PSym): bool = + if s.name.id != ic.getIdent(p.fn).id: return false let module = s.skipGenericOwner if module == nil or module.kind != skModule or - module.name.id != p.module.id: + module.name.id != ic.getIdent(p.module).id: return false let package = module.owner if package == nil or package.kind != skPackage or - package.name.id != p.package.id: + package.name.id != ic.getIdent(p.package).id: return false return true - -var head: Plugin - -proc getPlugin*(fn: PSym): Transformation = - var it = head - while it != nil: - if pluginMatches(it, fn): return it.t - it = it.next - -proc registerPlugin*(package, module, fn: string; t: Transformation) = - let oldHead = head - head = Plugin(fn: getIdent(fn), module: getIdent(module), - package: getIdent(package), t: t, next: oldHead) diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 1b8078628..9a344c038 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -12,7 +12,7 @@ import os, platform, condsyms, ast, astalgo, idents, semdata, msgs, renderer, wordrecg, ropes, options, strutils, extccomp, math, magicsys, trees, - rodread, types, lookups + types, lookups, lineinfos const FirstCallConv* = wNimcall @@ -24,8 +24,8 @@ const wCompilerProc, wCore, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge, wBorrow, wExtern, wImportCompilerProc, wThread, wImportCpp, wImportObjC, wAsmNoStackFrame, wError, wDiscardable, wNoInit, wCodegenDecl, - wGensym, wInject, wRaises, wTags, wLocks, wDelegator, wGcSafe, - wOverride, wConstructor, wExportNims, wUsed, wLiftLocals} + wGensym, wInject, wRaises, wTags, wLocks, wDelegator, wGcSafe, wOverride, + wConstructor, wExportNims, wUsed, wLiftLocals, wStacktrace, wLinetrace} converterPragmas* = procPragmas methodPragmas* = procPragmas+{wBase}-{wImportCpp} templatePragmas* = {wImmediate, wDeprecated, wError, wGensym, wInject, wDirty, @@ -40,17 +40,20 @@ const wTags, wLocks, wGcSafe, wExportNims, wUsed} exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe} stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks, - wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints, + wBoundchecks, wOverflowchecks, wNilchecks, wMovechecks, wAssertions, + wWarnings, wHints, wLinedir, wStacktrace, wLinetrace, wOptimization, wHint, wWarning, wError, wFatal, wDefine, wUndef, wCompile, wLink, wLinksys, wPure, wPush, wPop, - wBreakpoint, wWatchPoint, wPassl, wPassc, wDeadCodeElim, wDeprecated, + wBreakpoint, wWatchPoint, wPassl, wPassc, + wDeadCodeElimUnused, # deprecated, always on + wDeprecated, wFloatchecks, wInfChecks, wNanChecks, wPragma, wEmit, wUnroll, wLinearScanEnd, wPatterns, wEffects, wNoForward, wReorder, wComputedGoto, wInjectStmt, wDeprecated, wExperimental, wThis} lambdaPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl, wNosideeffect, wSideeffect, wNoreturn, wDynlib, wHeader, wDeprecated, wExtern, wThread, wImportCpp, wImportObjC, wAsmNoStackFrame, - wRaises, wLocks, wTags, wGcSafe} + wRaises, wLocks, wTags, wGcSafe, wCodegenDecl} typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl, wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wExtern, wShallow, wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef, @@ -64,10 +67,11 @@ const wGensym, wInject, wCodegenDecl, wGuard, wGoto, wExportNims, wUsed} constPragmas* = {wImportc, wExportc, wHeader, wDeprecated, wMagic, wNodecl, wExtern, wImportCpp, wImportObjC, wError, wGensym, wInject, wExportNims, - wIntDefine, wStrDefine, wUsed} + wIntDefine, wStrDefine, wUsed, wCompilerProc, wCore} letPragmas* = varPragmas procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNosideeffect, wThread, wRaises, wLocks, wTags, wGcSafe} + forVarPragmas* = {wInject, wGensym} allRoutinePragmas* = methodPragmas + iteratorPragmas + lambdaPragmas proc getPragmaVal*(procAst: PNode; name: TSpecialWord): PNode = @@ -79,10 +83,22 @@ proc getPragmaVal*(procAst: PNode; name: TSpecialWord): PNode = return it[1] proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) -# implementation -proc invalidPragma*(n: PNode) = - localError(n.info, errInvalidPragmaX, renderTree(n, {renderNoComments})) +proc recordPragma(c: PContext; n: PNode; key, val: string; val2 = "") = + var recorded = newNodeI(nkCommentStmt, n.info) + recorded.add newStrNode(key, n.info) + recorded.add newStrNode(val, n.info) + if val2.len > 0: recorded.add newStrNode(val2, n.info) + c.graph.recordStmt(c.graph, c.module, recorded) + +const + errStringLiteralExpected = "string literal expected" + errIntLiteralExpected = "integer literal expected" + +proc invalidPragma*(c: PContext; n: PNode) = + localError(c.config, n.info, "invalid pragma: " & renderTree(n, {renderNoComments})) +proc illegalCustomPragma*(c: PContext, n: PNode, s: PSym) = + localError(c.config, n.info, "cannot attach a custom pragma to '" & s.name.s & "'") proc pragmaAsm*(c: PContext, n: PNode): char = result = '\0' @@ -93,12 +109,12 @@ proc pragmaAsm*(c: PContext, n: PNode): char = case whichKeyword(it.sons[0].ident) of wSubsChar: if it.sons[1].kind == nkCharLit: result = chr(int(it.sons[1].intVal)) - else: invalidPragma(it) - else: invalidPragma(it) + else: invalidPragma(c, it) + else: invalidPragma(c, it) else: - invalidPragma(it) + invalidPragma(c, it) -proc setExternName(s: PSym, extname: string, info: TLineInfo) = +proc setExternName(c: PContext; s: PSym, extname: string, info: TLineInfo) = # special cases to improve performance: if extname == "$1": s.loc.r = rope(s.name.s) @@ -108,73 +124,73 @@ proc setExternName(s: PSym, extname: string, info: TLineInfo) = try: s.loc.r = rope(extname % s.name.s) except ValueError: - localError(info, "invalid extern name: '" & extname & "'. (Forgot to escape '$'?)") - if gCmd == cmdPretty and '$' notin extname: + localError(c.config, info, "invalid extern name: '" & extname & "'. (Forgot to escape '$'?)") + if c.config.cmd == cmdPretty and '$' notin extname: # note that '{.importc.}' is transformed into '{.importc: "$1".}' s.loc.flags.incl(lfFullExternalName) -proc makeExternImport(s: PSym, extname: string, info: TLineInfo) = - setExternName(s, extname, info) +proc makeExternImport(c: PContext; s: PSym, extname: string, info: TLineInfo) = + setExternName(c, s, extname, info) incl(s.flags, sfImportc) excl(s.flags, sfForward) -proc makeExternExport(s: PSym, extname: string, info: TLineInfo) = - setExternName(s, extname, info) +proc makeExternExport(c: PContext; s: PSym, extname: string, info: TLineInfo) = + setExternName(c, s, extname, info) incl(s.flags, sfExportc) -proc processImportCompilerProc(s: PSym, extname: string, info: TLineInfo) = - setExternName(s, extname, info) +proc processImportCompilerProc(c: PContext; s: PSym, extname: string, info: TLineInfo) = + setExternName(c, s, extname, info) incl(s.flags, sfImportc) excl(s.flags, sfForward) incl(s.loc.flags, lfImportCompilerProc) -proc processImportCpp(s: PSym, extname: string, info: TLineInfo) = - setExternName(s, extname, info) +proc processImportCpp(c: PContext; s: PSym, extname: string, info: TLineInfo) = + setExternName(c, s, extname, info) incl(s.flags, sfImportc) incl(s.flags, sfInfixCall) excl(s.flags, sfForward) - if gCmd == cmdCompileToC: + if c.config.cmd == cmdCompileToC: let m = s.getModule() incl(m.flags, sfCompileToCpp) - extccomp.gMixedMode = true + incl c.config.globalOptions, optMixedMode -proc processImportObjC(s: PSym, extname: string, info: TLineInfo) = - setExternName(s, extname, info) +proc processImportObjC(c: PContext; s: PSym, extname: string, info: TLineInfo) = + setExternName(c, s, extname, info) incl(s.flags, sfImportc) incl(s.flags, sfNamedParamCall) excl(s.flags, sfForward) let m = s.getModule() incl(m.flags, sfCompileToObjC) -proc newEmptyStrNode(n: PNode): PNode {.noinline.} = - result = newNodeIT(nkStrLit, n.info, getSysType(tyString)) +proc newEmptyStrNode(c: PContext; n: PNode): PNode {.noinline.} = + result = newNodeIT(nkStrLit, n.info, getSysType(c.graph, n.info, tyString)) result.strVal = "" proc getStrLitNode(c: PContext, n: PNode): PNode = if n.kind notin nkPragmaCallKinds or n.len != 2: - localError(n.info, errStringLiteralExpected) + localError(c.config, n.info, errStringLiteralExpected) # error correction: - result = newEmptyStrNode(n) + result = newEmptyStrNode(c, n) else: n.sons[1] = c.semConstExpr(c, n.sons[1]) case n.sons[1].kind of nkStrLit, nkRStrLit, nkTripleStrLit: result = n.sons[1] else: - localError(n.info, errStringLiteralExpected) + localError(c.config, n.info, errStringLiteralExpected) # error correction: - result = newEmptyStrNode(n) + result = newEmptyStrNode(c, n) proc expectStrLit(c: PContext, n: PNode): string = result = getStrLitNode(c, n).strVal proc expectIntLit(c: PContext, n: PNode): int = if n.kind notin nkPragmaCallKinds or n.len != 2: - localError(n.info, errIntLiteralExpected) + localError(c.config, n.info, errIntLiteralExpected) else: n.sons[1] = c.semConstExpr(c, n.sons[1]) case n.sons[1].kind of nkIntLit..nkInt64Lit: result = int(n.sons[1].intVal) - else: localError(n.info, errIntLiteralExpected) + else: localError(c.config, n.info, errIntLiteralExpected) proc getOptionalStr(c: PContext, n: PNode, defaultStr: string): string = if n.kind in nkPragmaCallKinds: result = expectStrLit(c, n) @@ -187,7 +203,7 @@ proc processMagic(c: PContext, n: PNode, s: PSym) = #if sfSystemModule notin c.module.flags: # liMessage(n.info, errMagicOnlyInSystem) if n.kind notin nkPragmaCallKinds or n.len != 2: - localError(n.info, errStringLiteralExpected) + localError(c.config, n.info, errStringLiteralExpected) return var v: string if n.sons[1].kind == nkIdent: v = n.sons[1].ident.s @@ -196,7 +212,7 @@ proc processMagic(c: PContext, n: PNode, s: PSym) = if substr($m, 1) == v: s.magic = m break - if s.magic == mNone: message(n.info, warnUnknownMagic, v) + if s.magic == mNone: message(c.config, n.info, warnUnknownMagic, v) proc wordToCallConv(sw: TSpecialWord): TCallingConvention = # this assumes that the order of special words and calling conventions is @@ -208,15 +224,11 @@ proc isTurnedOn(c: PContext, n: PNode): bool = let x = c.semConstBoolExpr(c, n.sons[1]) n.sons[1] = x if x.kind == nkIntLit: return x.intVal != 0 - localError(n.info, errOnOrOffExpected) + localError(c.config, n.info, "'on' or 'off' expected") -proc onOff(c: PContext, n: PNode, op: TOptions) = - if isTurnedOn(c, n): gOptions = gOptions + op - else: gOptions = gOptions - op - -proc pragmaDeadCodeElim(c: PContext, n: PNode) = - if isTurnedOn(c, n): incl(c.module.flags, sfDeadCodeElim) - else: excl(c.module.flags, sfDeadCodeElim) +proc onOff(c: PContext, n: PNode, op: TOptions, resOptions: var TOptions) = + if isTurnedOn(c, n): resOptions = resOptions + op + else: resOptions = resOptions - op proc pragmaNoForward(c: PContext, n: PNode; flag=sfNoForward) = if isTurnedOn(c, n): incl(c.module.flags, flag) @@ -224,13 +236,13 @@ proc pragmaNoForward(c: PContext, n: PNode; flag=sfNoForward) = proc processCallConv(c: PContext, n: PNode) = if n.kind in nkPragmaCallKinds and n.len == 2 and n.sons[1].kind == nkIdent: - var sw = whichKeyword(n.sons[1].ident) + let sw = whichKeyword(n.sons[1].ident) case sw of FirstCallConv..LastCallConv: c.optionStack[^1].defaultCC = wordToCallConv(sw) - else: localError(n.info, errCallConvExpected) + else: localError(c.config, n.info, "calling convention expected") else: - localError(n.info, errCallConvExpected) + localError(c.config, n.info, "calling convention expected") proc getLib(c: PContext, kind: TLibKind, path: PNode): PLib = for it in c.libs: @@ -241,13 +253,13 @@ proc getLib(c: PContext, kind: TLibKind, path: PNode): PLib = result.path = path c.libs.add result if path.kind in {nkStrLit..nkTripleStrLit}: - result.isOverriden = options.isDynlibOverride(path.strVal) + result.isOverriden = options.isDynlibOverride(c.config, path.strVal) proc expectDynlibNode(c: PContext, n: PNode): PNode = if n.kind notin nkPragmaCallKinds or n.len != 2: - localError(n.info, errStringLiteralExpected) + localError(c.config, n.info, errStringLiteralExpected) # error correction: - result = newEmptyStrNode(n) + result = newEmptyStrNode(c, n) else: # For the OpenGL wrapper we support: # {.dynlib: myGetProcAddr(...).} @@ -255,8 +267,8 @@ proc expectDynlibNode(c: PContext, n: PNode): PNode = if result.kind == nkSym and result.sym.kind == skConst: result = result.sym.ast # look it up if result.typ == nil or result.typ.kind notin {tyPointer, tyString, tyProc}: - localError(n.info, errStringLiteralExpected) - result = newEmptyStrNode(n) + localError(c.config, n.info, errStringLiteralExpected) + result = newEmptyStrNode(c, n) proc processDynLib(c: PContext, n: PNode, sym: PSym) = if (sym == nil) or (sym.kind == skModule): @@ -279,120 +291,156 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) = sym.typ.callConv = ccCDecl proc processNote(c: PContext, n: PNode) = - if (n.kind in nkPragmaCallKinds) and (sonsLen(n) == 2) and - (n.sons[0].kind == nkBracketExpr) and - (n.sons[0].sons.len == 2) and - (n.sons[0].sons[1].kind == nkIdent) and - (n.sons[0].sons[0].kind == nkIdent): - #and (n.sons[1].kind == nkIdent): + if n.kind in nkPragmaCallKinds and len(n) == 2 and + n[0].kind == nkBracketExpr and + n[0].len == 2 and + n[0][1].kind == nkIdent and n[0][0].kind == nkIdent: var nk: TNoteKind - case whichKeyword(n.sons[0].sons[0].ident) + case whichKeyword(n[0][0].ident) of wHint: - var x = findStr(msgs.HintsToStr, n.sons[0].sons[1].ident.s) + var x = findStr(HintsToStr, n[0][1].ident.s) if x >= 0: nk = TNoteKind(x + ord(hintMin)) - else: invalidPragma(n); return + else: invalidPragma(c, n); return of wWarning: - var x = findStr(msgs.WarningsToStr, n.sons[0].sons[1].ident.s) + var x = findStr(WarningsToStr, n[0][1].ident.s) if x >= 0: nk = TNoteKind(x + ord(warnMin)) - else: invalidPragma(n); return + else: invalidPragma(c, n); return else: - invalidPragma(n) + invalidPragma(c, n) return - let x = c.semConstBoolExpr(c, n.sons[1]) + let x = c.semConstBoolExpr(c, n[1]) n.sons[1] = x - if x.kind == nkIntLit and x.intVal != 0: incl(gNotes, nk) - else: excl(gNotes, nk) + if x.kind == nkIntLit and x.intVal != 0: incl(c.config.notes, nk) + else: excl(c.config.notes, nk) + else: + invalidPragma(c, n) + +proc pragmaToOptions(w: TSpecialWord): TOptions {.inline.} = + case w + of wChecks: ChecksOptions + of wObjChecks: {optObjCheck} + of wFieldChecks: {optFieldCheck} + of wRangechecks: {optRangeCheck} + of wBoundchecks: {optBoundsCheck} + of wOverflowchecks: {optOverflowCheck} + of wNilchecks: {optNilCheck} + of wFloatchecks: {optNaNCheck, optInfCheck} + of wNanChecks: {optNaNCheck} + of wInfChecks: {optInfCheck} + of wMovechecks: {optMoveCheck} + of wAssertions: {optAssert} + of wWarnings: {optWarns} + of wHints: {optHints} + of wLinedir: {optLineDir} + of wStacktrace: {optStackTrace} + of wLinetrace: {optLineTrace} + of wDebugger: {optEndb} + of wProfiler: {optProfiler, optMemTracker} + of wMemTracker: {optMemTracker} + of wByRef: {optByRef} + of wImplicitStatic: {optImplicitStatic} + of wPatterns: {optPatterns} + else: {} + +proc processExperimental(c: PContext; n: PNode) = + if n.kind notin nkPragmaCallKinds or n.len != 2: + c.features.incl oldExperimentalFeatures else: - invalidPragma(n) + n[1] = c.semConstExpr(c, n[1]) + case n[1].kind + of nkStrLit, nkRStrLit, nkTripleStrLit: + try: + c.features.incl parseEnum[Feature](n[1].strVal) + except ValueError: + localError(c.config, n[1].info, "unknown experimental feature") + else: + localError(c.config, n.info, errStringLiteralExpected) -proc processOption(c: PContext, n: PNode): bool = - if n.kind notin nkPragmaCallKinds or n.len != 2: result = true +proc tryProcessOption(c: PContext, n: PNode, resOptions: var TOptions): bool = + result = true + if n.kind notin nkPragmaCallKinds or n.len != 2: result = false elif n.sons[0].kind == nkBracketExpr: processNote(c, n) - elif n.sons[0].kind != nkIdent: result = true + elif n.sons[0].kind != nkIdent: result = false else: - var sw = whichKeyword(n.sons[0].ident) - case sw - of wChecks: onOff(c, n, ChecksOptions) - of wObjChecks: onOff(c, n, {optObjCheck}) - of wFieldChecks: onOff(c, n, {optFieldCheck}) - of wRangechecks: onOff(c, n, {optRangeCheck}) - of wBoundchecks: onOff(c, n, {optBoundsCheck}) - of wOverflowchecks: onOff(c, n, {optOverflowCheck}) - of wNilchecks: onOff(c, n, {optNilCheck}) - of wFloatchecks: onOff(c, n, {optNaNCheck, optInfCheck}) - of wNanChecks: onOff(c, n, {optNaNCheck}) - of wInfChecks: onOff(c, n, {optInfCheck}) - of wAssertions: onOff(c, n, {optAssert}) - of wWarnings: onOff(c, n, {optWarns}) - of wHints: onOff(c, n, {optHints}) - of wCallconv: processCallConv(c, n) - of wLinedir: onOff(c, n, {optLineDir}) - of wStacktrace: onOff(c, n, {optStackTrace}) - of wLinetrace: onOff(c, n, {optLineTrace}) - of wDebugger: onOff(c, n, {optEndb}) - of wProfiler: onOff(c, n, {optProfiler, optMemTracker}) - of wMemTracker: onOff(c, n, {optMemTracker}) - of wByRef: onOff(c, n, {optByRef}) - of wDynlib: processDynLib(c, n, nil) - of wOptimization: - if n.sons[1].kind != nkIdent: - invalidPragma(n) - else: - case n.sons[1].ident.s.normalize - of "speed": - incl(gOptions, optOptimizeSpeed) - excl(gOptions, optOptimizeSize) - of "size": - excl(gOptions, optOptimizeSpeed) - incl(gOptions, optOptimizeSize) - of "none": - excl(gOptions, optOptimizeSpeed) - excl(gOptions, optOptimizeSize) - else: localError(n.info, errNoneSpeedOrSizeExpected) - of wImplicitStatic: onOff(c, n, {optImplicitStatic}) - of wPatterns: onOff(c, n, {optPatterns}) - else: result = true + let sw = whichKeyword(n.sons[0].ident) + if sw == wExperimental: + processExperimental(c, n) + return true + let opts = pragmaToOptions(sw) + if opts != {}: + onOff(c, n, opts, resOptions) + else: + case sw + of wCallconv: processCallConv(c, n) + of wDynlib: processDynLib(c, n, nil) + of wOptimization: + if n.sons[1].kind != nkIdent: + invalidPragma(c, n) + else: + case n.sons[1].ident.s.normalize + of "speed": + incl(resOptions, optOptimizeSpeed) + excl(resOptions, optOptimizeSize) + of "size": + excl(resOptions, optOptimizeSpeed) + incl(resOptions, optOptimizeSize) + of "none": + excl(resOptions, optOptimizeSpeed) + excl(resOptions, optOptimizeSize) + else: localError(c.config, n.info, "'none', 'speed' or 'size' expected") + else: result = false + +proc processOption(c: PContext, n: PNode, resOptions: var TOptions) = + if not tryProcessOption(c, n, resOptions): + # calling conventions (boring...): + localError(c.config, n.info, "option expected") proc processPush(c: PContext, n: PNode, start: int) = if n.sons[start-1].kind in nkPragmaCallKinds: - localError(n.info, errGenerated, "'push' can't have arguments") - var x = newOptionEntry() + localError(c.config, n.info, "'push' cannot have arguments") + var x = newOptionEntry(c.config) var y = c.optionStack[^1] - x.options = gOptions + x.options = c.config.options x.defaultCC = y.defaultCC x.dynlib = y.dynlib - x.notes = gNotes + x.notes = c.config.notes + x.features = c.features c.optionStack.add(x) for i in countup(start, sonsLen(n) - 1): - if processOption(c, n.sons[i]): + if not tryProcessOption(c, n.sons[i], c.config.options): # simply store it somewhere: if x.otherPragmas.isNil: x.otherPragmas = newNodeI(nkPragma, n.info) x.otherPragmas.add n.sons[i] - #localError(n.info, errOptionExpected) + #localError(c.config, n.info, errOptionExpected) + + # If stacktrace is disabled globally we should not enable it + if optStackTrace notin c.optionStack[0].options: + c.config.options.excl(optStackTrace) proc processPop(c: PContext, n: PNode) = if c.optionStack.len <= 1: - localError(n.info, errAtPopWithoutPush) + localError(c.config, n.info, "{.pop.} without a corresponding {.push.}") else: - gOptions = c.optionStack[^1].options - gNotes = c.optionStack[^1].notes + c.config.options = c.optionStack[^1].options + c.config.notes = c.optionStack[^1].notes + c.features = c.optionStack[^1].features c.optionStack.setLen(c.optionStack.len - 1) proc processDefine(c: PContext, n: PNode) = - if (n.kind in nkPragmaCallKinds and n.len == 2) and (n.sons[1].kind == nkIdent): - defineSymbol(n.sons[1].ident.s) - message(n.info, warnDeprecated, "define") + if (n.kind in nkPragmaCallKinds and n.len == 2) and (n[1].kind == nkIdent): + defineSymbol(c.config.symbols, n[1].ident.s) + message(c.config, n.info, warnDeprecated, "define") else: - invalidPragma(n) + invalidPragma(c, n) proc processUndef(c: PContext, n: PNode) = - if (n.kind in nkPragmaCallKinds and n.len == 2) and (n.sons[1].kind == nkIdent): - undefSymbol(n.sons[1].ident.s) - message(n.info, warnDeprecated, "undef") + if (n.kind in nkPragmaCallKinds and n.len == 2) and (n[1].kind == nkIdent): + undefSymbol(c.config.symbols, n[1].ident.s) + message(c.config, n.info, warnDeprecated, "undef") else: - invalidPragma(n) + invalidPragma(c, n) type TLinkFeature = enum @@ -402,52 +450,58 @@ proc relativeFile(c: PContext; n: PNode; ext=""): string = var s = expectStrLit(c, n) if ext.len > 0 and splitFile(s).ext == "": s = addFileExt(s, ext) - result = parentDir(n.info.toFullPath) / s + result = parentDir(toFullPath(c.config, n.info)) / s if not fileExists(result): if isAbsolute(s): result = s else: - result = findFile(s) + result = findFile(c.config, s) if result.len == 0: result = s proc processCompile(c: PContext, n: PNode) = + proc docompile(c: PContext; it: PNode; src, dest: string) = + var cf = Cfile(cname: src, obj: dest, flags: {CfileFlag.External}) + extccomp.addExternalFileToCompile(c.config, cf) + recordPragma(c, it, "compile", src, dest) proc getStrLit(c: PContext, n: PNode; i: int): string = - n.sons[i] = c.semConstExpr(c, n.sons[i]) - case n.sons[i].kind + n.sons[i] = c.semConstExpr(c, n[i]) + case n[i].kind of nkStrLit, nkRStrLit, nkTripleStrLit: - shallowCopy(result, n.sons[i].strVal) + shallowCopy(result, n[i].strVal) else: - localError(n.info, errStringLiteralExpected) + localError(c.config, n.info, errStringLiteralExpected) result = "" let it = if n.kind in nkPragmaCallKinds and n.len == 2: n.sons[1] else: n - if it.kind == nkPar and it.len == 2: + if it.kind in {nkPar, nkTupleConstr} and it.len == 2: let s = getStrLit(c, it, 0) let dest = getStrLit(c, it, 1) - var found = parentDir(n.info.toFullPath) / s + var found = parentDir(toFullPath(c.config, n.info)) / s for f in os.walkFiles(found): - let nameOnly = extractFilename(f) - var cf = Cfile(cname: f, - obj: completeCFilePath(dest % nameOnly), - flags: {CfileFlag.External}) - extccomp.addExternalFileToCompile(cf) + let obj = completeCFilePath(c.config, dest % extractFilename(f)) + docompile(c, it, f, obj) else: let s = expectStrLit(c, n) - var found = parentDir(n.info.toFullPath) / s + var found = parentDir(toFullPath(c.config, n.info)) / s if not fileExists(found): if isAbsolute(s): found = s else: - found = findFile(s) + found = findFile(c.config, s) if found.len == 0: found = s - extccomp.addExternalFileToCompile(found) + let obj = toObjFile(c.config, completeCFilePath(c.config, found, false)) + docompile(c, it, found, obj) proc processCommonLink(c: PContext, n: PNode, feature: TLinkFeature) = - let found = relativeFile(c, n, CC[cCompiler].objExt) + let found = relativeFile(c, n, CC[c.config.cCompiler].objExt) case feature - of linkNormal: extccomp.addExternalFileToLink(found) + of linkNormal: + extccomp.addExternalFileToLink(c.config, found) + recordPragma(c, n, "link", found) of linkSys: - extccomp.addExternalFileToLink(libpath / completeCFilePath(found, false)) - else: internalError(n.info, "processCommonLink") + let dest = c.config.libpath / completeCFilePath(c.config, found, false) + extccomp.addExternalFileToLink(c.config, dest) + recordPragma(c, n, "link", dest) + else: internalError(c.config, n.info, "processCommonLink") proc pragmaBreakpoint(c: PContext, n: PNode) = discard getOptionalStr(c, n, "") @@ -456,7 +510,7 @@ proc pragmaWatchpoint(c: PContext, n: PNode) = if n.kind in nkPragmaCallKinds and n.len == 2: n.sons[1] = c.semExpr(c, n.sons[1]) else: - invalidPragma(n) + invalidPragma(c, n) proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode = case n.sons[1].kind @@ -464,7 +518,7 @@ proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode = result = newNode(if n.kind == nkAsmStmt: nkAsmStmt else: nkArgList, n.info) var str = n.sons[1].strVal if str == "": - localError(n.info, errEmptyAsm) + localError(con.config, n.info, "empty 'asm' statement") return # now parse the string literal and substitute symbols: var a = 0 @@ -477,9 +531,10 @@ proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode = if c < 0: sub = substr(str, b + 1) else: sub = substr(str, b + 1, c - 1) if sub != "": - var e = searchInScopes(con, getIdent(sub)) + var e = searchInScopes(con, getIdent(con.cache, sub)) if e != nil: - if e.kind == skStub: loadStub(e) + when false: + if e.kind == skStub: loadStub(e) incl(e.flags, sfUsed) addSon(result, newSymNode(e)) else: @@ -490,12 +545,12 @@ proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode = if c < 0: break a = c + 1 else: - illFormedAstLocal(n) + illFormedAstLocal(n, con.config) result = newNode(nkAsmStmt, n.info) proc pragmaEmit(c: PContext, n: PNode) = if n.kind notin nkPragmaCallKinds or n.len != 2: - localError(n.info, errStringLiteralExpected) + localError(c.config, n.info, errStringLiteralExpected) else: let n1 = n[1] if n1.kind == nkBracket: @@ -509,52 +564,52 @@ proc pragmaEmit(c: PContext, n: PNode) = of nkStrLit, nkRStrLit, nkTripleStrLit: n.sons[1] = semAsmOrEmit(c, n, '`') else: - localError(n.info, errStringLiteralExpected) + localError(c.config, n.info, errStringLiteralExpected) -proc noVal(n: PNode) = - if n.kind in nkPragmaCallKinds and n.len > 1: invalidPragma(n) +proc noVal(c: PContext; n: PNode) = + if n.kind in nkPragmaCallKinds and n.len > 1: invalidPragma(c, n) proc pragmaUnroll(c: PContext, n: PNode) = if c.p.nestedLoopCounter <= 0: - invalidPragma(n) + invalidPragma(c, n) elif n.kind in nkPragmaCallKinds and n.len == 2: var unrollFactor = expectIntLit(c, n) if unrollFactor <% 32: n.sons[1] = newIntNode(nkIntLit, unrollFactor) else: - invalidPragma(n) + invalidPragma(c, n) proc pragmaLine(c: PContext, n: PNode) = if n.kind in nkPragmaCallKinds and n.len == 2: n.sons[1] = c.semConstExpr(c, n.sons[1]) let a = n.sons[1] - if a.kind == nkPar: + if a.kind in {nkPar, nkTupleConstr}: # unpack the tuple var x = a.sons[0] var y = a.sons[1] if x.kind == nkExprColonExpr: x = x.sons[1] if y.kind == nkExprColonExpr: y = y.sons[1] if x.kind != nkStrLit: - localError(n.info, errStringLiteralExpected) + localError(c.config, n.info, errStringLiteralExpected) elif y.kind != nkIntLit: - localError(n.info, errIntLiteralExpected) + localError(c.config, n.info, errIntLiteralExpected) else: # XXX this produces weird paths which are not properly resolved: - n.info.fileIndex = msgs.fileInfoIdx(x.strVal) - n.info.line = int16(y.intVal) + n.info.fileIndex = msgs.fileInfoIdx(c.config, x.strVal) + n.info.line = uint16(y.intVal) else: - localError(n.info, errXExpected, "tuple") + localError(c.config, n.info, "tuple expected") else: # sensible default: - n.info = getInfoContext(-1) + n.info = getInfoContext(c.config, -1) proc processPragma(c: PContext, n: PNode, i: int) = let it = n[i] - if it.kind notin nkPragmaCallKinds and it.len == 2: invalidPragma(n) - elif it[0].kind != nkIdent: invalidPragma(n) - elif it[1].kind != nkIdent: invalidPragma(n) + if it.kind notin nkPragmaCallKinds and it.len == 2: invalidPragma(c, n) + elif it[0].kind != nkIdent: invalidPragma(c, n) + elif it[1].kind != nkIdent: invalidPragma(c, n) - var userPragma = newSym(skTemplate, it[1].ident, nil, it.info) + var userPragma = newSym(skTemplate, it[1].ident, nil, it.info, c.config.options) userPragma.ast = newNode(nkPragma, n.info, n.sons[i+1..^1]) strTableAdd(c.userPragmas, userPragma) @@ -562,7 +617,7 @@ proc pragmaRaisesOrTags(c: PContext, n: PNode) = proc processExc(c: PContext, x: PNode) = var t = skipTypes(c.semTypeNode(c, x, nil), skipPtrs) if t.kind != tyObject: - localError(x.info, errGenerated, "invalid type for raises/tags list") + localError(c.config, x.info, errGenerated, "invalid type for raises/tags list") x.typ = t if n.kind in nkPragmaCallKinds and n.len == 2: @@ -572,73 +627,77 @@ proc pragmaRaisesOrTags(c: PContext, n: PNode) = else: for e in items(it): processExc(c, e) else: - invalidPragma(n) + invalidPragma(c, n) proc pragmaLockStmt(c: PContext; it: PNode) = if it.kind notin nkPragmaCallKinds or it.len != 2: - invalidPragma(it) + invalidPragma(c, it) else: let n = it[1] if n.kind != nkBracket: - localError(n.info, errGenerated, "locks pragma takes a list of expressions") + localError(c.config, n.info, errGenerated, "locks pragma takes a list of expressions") else: for i in 0 ..< n.len: n.sons[i] = c.semExpr(c, n.sons[i]) proc pragmaLocks(c: PContext, it: PNode): TLockLevel = if it.kind notin nkPragmaCallKinds or it.len != 2: - invalidPragma(it) + invalidPragma(c, it) else: case it[1].kind of nkStrLit, nkRStrLit, nkTripleStrLit: if it[1].strVal == "unknown": result = UnknownLockLevel else: - localError(it[1].info, "invalid string literal for locks pragma (only allowed string is \"unknown\")") + localError(c.config, it[1].info, "invalid string literal for locks pragma (only allowed string is \"unknown\")") else: let x = expectIntLit(c, it) if x < 0 or x > MaxLockLevel: - localError(it[1].info, "integer must be within 0.." & $MaxLockLevel) + localError(c.config, it[1].info, "integer must be within 0.." & $MaxLockLevel) else: result = TLockLevel(x) -proc typeBorrow(sym: PSym, n: PNode) = +proc typeBorrow(c: PContext; sym: PSym, n: PNode) = if n.kind in nkPragmaCallKinds and n.len == 2: let it = n.sons[1] if it.kind != nkAccQuoted: - localError(n.info, "a type can only borrow `.` for now") + localError(c.config, n.info, "a type can only borrow `.` for now") incl(sym.typ.flags, tfBorrowDot) -proc markCompilerProc(s: PSym) = +proc markCompilerProc(c: PContext; s: PSym) = # minor hack ahead: FlowVar is the only generic .compilerProc type which # should not have an external name set: if s.kind != skType or s.name.s != "FlowVar": - makeExternExport(s, "$1", s.info) + makeExternExport(c, s, "$1", s.info) incl(s.flags, sfCompilerProc) incl(s.flags, sfUsed) - registerCompilerProc(s) + registerCompilerProc(c.graph, s) -proc deprecatedStmt(c: PContext; pragma: PNode) = - let pragma = pragma[1] +proc deprecatedStmt(c: PContext; outerPragma: PNode) = + let pragma = outerPragma[1] + if pragma.kind in {nkStrLit..nkTripleStrLit}: + incl(c.module.flags, sfDeprecated) + c.module.constraint = getStrLitNode(c, outerPragma) + return if pragma.kind != nkBracket: - localError(pragma.info, "list of key:value pairs expected"); return + localError(c.config, pragma.info, "list of key:value pairs expected"); return for n in pragma: if n.kind in nkPragmaCallKinds and n.len == 2: let dest = qualifiedLookUp(c, n[1], {checkUndeclared}) if dest == nil or dest.kind in routineKinds: - localError(n.info, warnUser, "the .deprecated pragma is unreliable for routines") - let src = considerQuotedIdent(n[0]) - let alias = newSym(skAlias, src, dest, n[0].info) + localError(c.config, n.info, warnUser, "the .deprecated pragma is unreliable for routines") + let src = considerQuotedIdent(c, n[0]) + let alias = newSym(skAlias, src, dest, n[0].info, c.config.options) incl(alias.flags, sfExported) - if sfCompilerProc in dest.flags: markCompilerProc(alias) + if sfCompilerProc in dest.flags: markCompilerProc(c, alias) addInterfaceDecl(c, alias) n.sons[1] = newSymNode(dest) else: - localError(n.info, "key:value pair expected") + localError(c.config, n.info, "key:value pair expected") proc pragmaGuard(c: PContext; it: PNode; kind: TSymKind): PSym = if it.kind notin nkPragmaCallKinds or it.len != 2: - invalidPragma(it); return + invalidPragma(c, it); return let n = it[1] if n.kind == nkSym: result = n.sym @@ -650,7 +709,8 @@ proc pragmaGuard(c: PContext; it: PNode; kind: TSymKind): PSym = # We return a dummy symbol; later passes over the type will repair it. # Generic instantiation needs to know about this too. But we're lazy # and perform the lookup on demand instead. - result = newSym(skUnknown, considerQuotedIdent(n), nil, n.info) + result = newSym(skUnknown, considerQuotedIdent(c, n), nil, n.info, + c.config.options) else: result = qualifiedLookUp(c, n, {checkUndeclared}) @@ -663,12 +723,12 @@ proc semCustomPragma(c: PContext, n: PNode): PNode = elif n.kind in nkPragmaCallKinds + {nkIdent}: result = n else: - invalidPragma(n) + invalidPragma(c, n) return n let r = c.semOverloadedCall(c, result, n, {skTemplate}, {}) if r.isNil or sfCustomPragma notin r[0].sym.flags: - invalidPragma(n) + invalidPragma(c, n) else: result = r if n.kind == nkIdent: @@ -686,95 +746,97 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, elif key.kind notin nkIdentKinds: n.sons[i] = semCustomPragma(c, it) return - let ident = considerQuotedIdent(key) + let ident = considerQuotedIdent(c, key) var userPragma = strTableGet(c.userPragmas, ident) if userPragma != nil: # number of pragmas increase/decrease with user pragma expansion inc c.instCounter if c.instCounter > 100: - globalError(it.info, errRecursiveDependencyX, userPragma.name.s) - + globalError(c.config, it.info, "recursive dependency: " & userPragma.name.s) + pragma(c, sym, userPragma.ast, validPragmas) n.sons[i..i] = userPragma.ast.sons # expand user pragma with its content i.inc(userPragma.ast.len - 1) # inc by -1 is ok, user pragmas was empty dec c.instCounter else: - var k = whichKeyword(ident) + let k = whichKeyword(ident) if k in validPragmas: case k of wExportc: - makeExternExport(sym, getOptionalStr(c, it, "$1"), it.info) + makeExternExport(c, sym, getOptionalStr(c, it, "$1"), it.info) incl(sym.flags, sfUsed) # avoid wrong hints of wImportc: let name = getOptionalStr(c, it, "$1") - cppDefine(c.graph.config, name) - makeExternImport(sym, name, it.info) + cppDefine(c.config, name) + recordPragma(c, it, "cppdefine", name) + makeExternImport(c, sym, name, it.info) of wImportCompilerProc: let name = getOptionalStr(c, it, "$1") - cppDefine(c.graph.config, name) - processImportCompilerProc(sym, name, it.info) - of wExtern: setExternName(sym, expectStrLit(c, it), it.info) + cppDefine(c.config, name) + recordPragma(c, it, "cppdefine", name) + processImportCompilerProc(c, sym, name, it.info) + of wExtern: setExternName(c, sym, expectStrLit(c, it), it.info) of wImmediate: if sym.kind in {skTemplate, skMacro}: incl(sym.flags, sfImmediate) incl(sym.flags, sfAllUntyped) - message(n.info, warnDeprecated, "use 'untyped' parameters instead; immediate") - else: invalidPragma(it) + message(c.config, n.info, warnDeprecated, "use 'untyped' parameters instead; immediate") + else: invalidPragma(c, it) of wDirty: if sym.kind == skTemplate: incl(sym.flags, sfDirty) - else: invalidPragma(it) + else: invalidPragma(c, it) of wImportCpp: - processImportCpp(sym, getOptionalStr(c, it, "$1"), it.info) + processImportCpp(c, sym, getOptionalStr(c, it, "$1"), it.info) of wImportObjC: - processImportObjC(sym, getOptionalStr(c, it, "$1"), it.info) + processImportObjC(c, sym, getOptionalStr(c, it, "$1"), it.info) of wAlign: - if sym.typ == nil: invalidPragma(it) + if sym.typ == nil: invalidPragma(c, it) var align = expectIntLit(c, it) if (not isPowerOfTwo(align) and align != 0) or align >% high(int16): - localError(it.info, errPowerOfTwoExpected) + localError(c.config, it.info, "power of two expected") else: sym.typ.align = align.int16 of wSize: - if sym.typ == nil: invalidPragma(it) + if sym.typ == nil: invalidPragma(c, it) var size = expectIntLit(c, it) if not isPowerOfTwo(size) or size <= 0 or size > 8: - localError(it.info, errPowerOfTwoExpected) + localError(c.config, it.info, "power of two expected") else: sym.typ.size = size of wNodecl: - noVal(it) + noVal(c, it) incl(sym.loc.flags, lfNoDecl) of wPure, wAsmNoStackFrame: - noVal(it) + noVal(c, it) if sym != nil: - if k == wPure and sym.kind in routineKinds: invalidPragma(it) + if k == wPure and sym.kind in routineKinds: invalidPragma(c, it) else: incl(sym.flags, sfPure) of wVolatile: - noVal(it) + noVal(c, it) incl(sym.flags, sfVolatile) of wRegister: - noVal(it) + noVal(c, it) incl(sym.flags, sfRegister) of wThreadVar: - noVal(it) - incl(sym.flags, sfThread) - of wDeadCodeElim: pragmaDeadCodeElim(c, it) + noVal(c, it) + incl(sym.flags, {sfThread, sfGlobal}) + of wDeadCodeElimUnused: discard # deprecated, dead code elim always on of wNoForward: pragmaNoForward(c, it) of wReorder: pragmaNoForward(c, it, sfReorder) of wMagic: processMagic(c, it, sym) of wCompileTime: - noVal(it) + noVal(c, it) incl(sym.flags, sfCompileTime) incl(sym.loc.flags, lfNoDecl) of wGlobal: - noVal(it) + noVal(c, it) incl(sym.flags, sfGlobal) incl(sym.flags, sfPure) of wMerge: # only supported for backwards compat, doesn't do anything anymore - noVal(it) + noVal(c, it) of wConstructor: - noVal(it) + noVal(c, it) incl(sym.flags, sfConstructor) of wHeader: var lib = getLib(c, libHeader, getStrLitNode(c, it)) @@ -787,104 +849,126 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, of wOverride: sym.flags.incl sfOverriden of wNosideeffect: - noVal(it) + noVal(c, it) incl(sym.flags, sfNoSideEffect) if sym.typ != nil: incl(sym.typ.flags, tfNoSideEffect) of wSideeffect: - noVal(it) + noVal(c, it) incl(sym.flags, sfSideEffect) of wNoreturn: - noVal(it) + noVal(c, it) incl(sym.flags, sfNoReturn) if sym.typ[0] != nil: - localError(sym.ast[paramsPos][0].info, errNoReturnWithReturnTypeNotAllowed) + localError(c.config, sym.ast[paramsPos][0].info, + ".noreturn with return type not allowed") of wDynlib: processDynLib(c, it, sym) of wCompilerProc, wCore: - noVal(it) # compilerproc may not get a string! + noVal(c, it) # compilerproc may not get a string! cppDefine(c.graph.config, sym.name.s) - if sfFromGeneric notin sym.flags: markCompilerProc(sym) + recordPragma(c, it, "cppdefine", sym.name.s) + if sfFromGeneric notin sym.flags: markCompilerProc(c, sym) of wProcVar: - noVal(it) + noVal(c, it) incl(sym.flags, sfProcvar) of wExplain: sym.flags.incl sfExplain of wDeprecated: - if sym != nil and sym.kind in routineKinds: + if sym != nil and sym.kind in routineKinds + {skType}: if it.kind in nkPragmaCallKinds: discard getStrLitNode(c, it) incl(sym.flags, sfDeprecated) + elif sym != nil and sym.kind != skModule: + # We don't support the extra annotation field + if it.kind in nkPragmaCallKinds: + localError(c.config, it.info, "annotation to deprecated not supported here") + incl(sym.flags, sfDeprecated) + # At this point we're quite sure this is a statement and applies to the + # whole module elif it.kind in nkPragmaCallKinds: deprecatedStmt(c, it) - elif sym != nil: incl(sym.flags, sfDeprecated) else: incl(c.module.flags, sfDeprecated) of wVarargs: - noVal(it) - if sym.typ == nil: invalidPragma(it) + noVal(c, it) + if sym.typ == nil: invalidPragma(c, it) else: incl(sym.typ.flags, tfVarargs) of wBorrow: if sym.kind == skType: - typeBorrow(sym, it) + typeBorrow(c, sym, it) else: - noVal(it) + noVal(c, it) incl(sym.flags, sfBorrow) of wFinal: - noVal(it) - if sym.typ == nil: invalidPragma(it) + noVal(c, it) + if sym.typ == nil: invalidPragma(c, it) else: incl(sym.typ.flags, tfFinal) of wInheritable: - noVal(it) - if sym.typ == nil or tfFinal in sym.typ.flags: invalidPragma(it) + noVal(c, it) + if sym.typ == nil or tfFinal in sym.typ.flags: invalidPragma(c, it) else: incl(sym.typ.flags, tfInheritable) of wPackage: - noVal(it) - if sym.typ == nil: invalidPragma(it) + noVal(c, it) + if sym.typ == nil: invalidPragma(c, it) else: incl(sym.flags, sfForward) of wAcyclic: - noVal(it) - if sym.typ == nil: invalidPragma(it) + noVal(c, it) + if sym.typ == nil: invalidPragma(c, it) else: incl(sym.typ.flags, tfAcyclic) of wShallow: - noVal(it) - if sym.typ == nil: invalidPragma(it) + noVal(c, it) + if sym.typ == nil: invalidPragma(c, it) else: incl(sym.typ.flags, tfShallow) of wThread: - noVal(it) + noVal(c, it) incl(sym.flags, sfThread) incl(sym.flags, sfProcvar) if sym.typ != nil: incl(sym.typ.flags, tfThread) if sym.typ.callConv == ccClosure: sym.typ.callConv = ccDefault of wGcSafe: - noVal(it) + noVal(c, it) if sym != nil: if sym.kind != skType: incl(sym.flags, sfThread) if sym.typ != nil: incl(sym.typ.flags, tfGcSafe) - else: invalidPragma(it) + else: invalidPragma(c, it) else: discard "no checking if used as a code block" of wPacked: - noVal(it) - if sym.typ == nil: invalidPragma(it) + noVal(c, it) + if sym.typ == nil: invalidPragma(c, it) else: incl(sym.typ.flags, tfPacked) - of wHint: message(it.info, hintUser, expectStrLit(c, it)) - of wWarning: message(it.info, warnUser, expectStrLit(c, it)) + of wHint: + let s = expectStrLit(c, it) + recordPragma(c, it, "hint", s) + message(c.config, it.info, hintUser, s) + of wWarning: + let s = expectStrLit(c, it) + recordPragma(c, it, "warning", s) + message(c.config, it.info, warnUser, s) of wError: if sym != nil and sym.isRoutine: # This is subtle but correct: the error *statement* is only # allowed for top level statements. Seems to be easier than # distinguishing properly between # ``proc p() {.error}`` and ``proc p() = {.error: "msg".}`` - noVal(it) + noVal(c, it) incl(sym.flags, sfError) else: - localError(it.info, errUser, expectStrLit(c, it)) - of wFatal: fatal(it.info, errUser, expectStrLit(c, it)) + let s = expectStrLit(c, it) + recordPragma(c, it, "error", s) + localError(c.config, it.info, errUser, s) + of wFatal: fatal(c.config, it.info, errUser, expectStrLit(c, it)) of wDefine: processDefine(c, it) of wUndef: processUndef(c, it) of wCompile: processCompile(c, it) of wLink: processCommonLink(c, it, linkNormal) of wLinksys: processCommonLink(c, it, linkSys) - of wPassl: extccomp.addLinkOption(expectStrLit(c, it)) - of wPassc: extccomp.addCompileOption(expectStrLit(c, it)) + of wPassl: + let s = expectStrLit(c, it) + extccomp.addLinkOption(c.config, s) + recordPragma(c, it, "passl", s) + of wPassc: + let s = expectStrLit(c, it) + extccomp.addCompileOption(c.config, s) + recordPragma(c, it, "passc", s) of wBreakpoint: pragmaBreakpoint(c, it) of wWatchPoint: pragmaWatchpoint(c, it) of wPush: @@ -898,131 +982,129 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, processPragma(c, n, i) result = true of wDiscardable: - noVal(it) + noVal(c, it) if sym != nil: incl(sym.flags, sfDiscardable) of wNoInit: - noVal(it) + noVal(c, it) if sym != nil: incl(sym.flags, sfNoInit) of wCodegenDecl: processCodegenDecl(c, it, sym) of wChecks, wObjChecks, wFieldChecks, wRangechecks, wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints, - wLinedir, wStacktrace, wLinetrace, wOptimization, - wCallconv, - wDebugger, wProfiler, wFloatchecks, wNanChecks, wInfChecks, - wPatterns: - if processOption(c, it): - # calling conventions (boring...): - localError(it.info, errOptionExpected) + wLinedir, wOptimization, wMovechecks, wCallconv, wDebugger, wProfiler, + wFloatchecks, wNanChecks, wInfChecks, wPatterns: + processOption(c, it, c.config.options) + of wStacktrace, wLinetrace: + if sym.kind in {skProc, skMethod, skConverter}: + processOption(c, it, sym.options) + else: + processOption(c, it, c.config.options) of FirstCallConv..LastCallConv: assert(sym != nil) - if sym.typ == nil: invalidPragma(it) + if sym.typ == nil: invalidPragma(c, it) else: sym.typ.callConv = wordToCallConv(k) of wEmit: pragmaEmit(c, it) of wUnroll: pragmaUnroll(c, it) - of wLinearScanEnd, wComputedGoto: noVal(it) + of wLinearScanEnd, wComputedGoto: noVal(c, it) of wEffects: # is later processed in effect analysis: - noVal(it) + noVal(c, it) of wIncompleteStruct: - noVal(it) - if sym.typ == nil: invalidPragma(it) + noVal(c, it) + if sym.typ == nil: invalidPragma(c, it) else: incl(sym.typ.flags, tfIncompleteStruct) of wUnchecked: - noVal(it) - if sym.typ == nil: invalidPragma(it) + noVal(c, it) + if sym.typ == nil: invalidPragma(c, it) else: incl(sym.typ.flags, tfUncheckedArray) of wUnion: - noVal(it) - if sym.typ == nil: invalidPragma(it) + noVal(c, it) + if sym.typ == nil: invalidPragma(c, it) else: incl(sym.typ.flags, tfUnion) of wRequiresInit: - noVal(it) - if sym.typ == nil: invalidPragma(it) + noVal(c, it) + if sym.typ == nil: invalidPragma(c, it) else: incl(sym.typ.flags, tfNeedsInit) of wByRef: - noVal(it) + noVal(c, it) if sym == nil or sym.typ == nil: - if processOption(c, it): localError(it.info, errOptionExpected) + processOption(c, it, c.config.options) else: incl(sym.typ.flags, tfByRef) of wByCopy: - noVal(it) - if sym.kind != skType or sym.typ == nil: invalidPragma(it) + noVal(c, it) + if sym.kind != skType or sym.typ == nil: invalidPragma(c, it) else: incl(sym.typ.flags, tfByCopy) of wPartial: - noVal(it) - if sym.kind != skType or sym.typ == nil: invalidPragma(it) + noVal(c, it) + if sym.kind != skType or sym.typ == nil: invalidPragma(c, it) else: incl(sym.typ.flags, tfPartial) - # .partial types can only work with dead code elimination - # to prevent the codegen from doing anything before we compiled - # the whole program: - incl gGlobalOptions, optDeadCodeElim of wInject, wGensym: # We check for errors, but do nothing with these pragmas otherwise # as they are handled directly in 'evalTemplate'. - noVal(it) - if sym == nil: invalidPragma(it) + noVal(c, it) + if sym == nil: invalidPragma(c, it) of wLine: pragmaLine(c, it) of wRaises, wTags: pragmaRaisesOrTags(c, it) of wLocks: if sym == nil: pragmaLockStmt(c, it) - elif sym.typ == nil: invalidPragma(it) + elif sym.typ == nil: invalidPragma(c, it) else: sym.typ.lockLevel = pragmaLocks(c, it) of wBitsize: if sym == nil or sym.kind != skField: - invalidPragma(it) + invalidPragma(c, it) else: sym.bitsize = expectIntLit(c, it) of wGuard: if sym == nil or sym.kind notin {skVar, skLet, skField}: - invalidPragma(it) + invalidPragma(c, it) else: sym.guard = pragmaGuard(c, it, sym.kind) of wGoto: if sym == nil or sym.kind notin {skVar, skLet}: - invalidPragma(it) + invalidPragma(c, it) else: sym.flags.incl sfGoto of wExportNims: - if sym == nil: invalidPragma(it) - else: magicsys.registerNimScriptSymbol(sym) + if sym == nil: invalidPragma(c, it) + else: magicsys.registerNimScriptSymbol(c.graph, sym) of wInjectStmt: if it.kind notin nkPragmaCallKinds or it.len != 2: - localError(it.info, errExprExpected) + localError(c.config, it.info, "expression expected") else: it.sons[1] = c.semExpr(c, it.sons[1]) of wExperimental: - noVal(it) - if isTopLevel(c): - c.module.flags.incl sfExperimental - else: - localError(it.info, "'experimental' pragma only valid as toplevel statement") + if not isTopLevel(c): + localError(c.config, n.info, "'experimental' pragma only valid as toplevel statement or in a 'push' environment") + processExperimental(c, it) of wThis: if it.kind in nkPragmaCallKinds and it.len == 2: - c.selfName = considerQuotedIdent(it[1]) + c.selfName = considerQuotedIdent(c, it[1]) + message(c.config, n.info, warnDeprecated, "the '.this' pragma") elif it.kind == nkIdent or it.len == 1: - c.selfName = getIdent("self") + c.selfName = getIdent(c.cache, "self") + message(c.config, n.info, warnDeprecated, "the '.this' pragma") else: - localError(it.info, "'this' pragma is allowed to have zero or one arguments") + localError(c.config, it.info, "'this' pragma is allowed to have zero or one arguments") of wNoRewrite: - noVal(it) + noVal(c, it) of wBase: - noVal(it) + noVal(c, it) sym.flags.incl sfBase of wIntDefine: sym.magic = mIntDefine of wStrDefine: sym.magic = mStrDefine of wUsed: - noVal(it) - if sym == nil: invalidPragma(it) + noVal(c, it) + if sym == nil: invalidPragma(c, it) else: sym.flags.incl sfUsed of wLiftLocals: discard - else: invalidPragma(it) - else: + else: invalidPragma(c, it) + elif sym.kind in {skField,skProc,skFunc,skConverter,skMethod,skType}: n.sons[i] = semCustomPragma(c, it) - + else: + illegalCustomPragma(c, it, sym) proc implicitPragmas*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) = @@ -1030,16 +1112,16 @@ proc implicitPragmas*(c: PContext, sym: PSym, n: PNode, for it in c.optionStack: let o = it.otherPragmas if not o.isNil: - pushInfoContext(n.info) + pushInfoContext(c.config, n.info) var i = 0 while i < o.len(): if singlePragma(c, sym, o, i, validPragmas): - internalError(n.info, "implicitPragmas") + internalError(c.config, n.info, "implicitPragmas") inc i - popInfoContext() + popInfoContext(c.config) if lfExportLib in sym.loc.flags and sfExportc notin sym.flags: - localError(n.info, errDynlibRequiresExportc) + localError(c.config, n.info, ".dynlib requires .exportc") var lib = c.optionStack[^1].dynlib if {lfDynamicLib, lfHeader} * sym.loc.flags == {} and sfImportc in sym.flags and lib != nil: @@ -1048,10 +1130,9 @@ proc implicitPragmas*(c: PContext, sym: PSym, n: PNode, if sym.loc.r == nil: sym.loc.r = rope(sym.name.s) proc hasPragma*(n: PNode, pragma: TSpecialWord): bool = - if n == nil or n.sons == nil: - return false + if n == nil: return false - for p in n.sons: + for p in n: var key = if p.kind in nkPragmaCallKinds and p.len > 1: p[0] else: p if key.kind == nkIdent and whichKeyword(key.ident) == pragma: return true @@ -1061,7 +1142,7 @@ proc hasPragma*(n: PNode, pragma: TSpecialWord): bool = proc pragmaRec(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) = if n == nil: return var i = 0 - while i < n.len(): + while i < n.len: if singlePragma(c, sym, n, i, validPragmas): break inc i diff --git a/compiler/prefixmatches.nim b/compiler/prefixmatches.nim index 00e2c537d..246d1ae5e 100644 --- a/compiler/prefixmatches.nim +++ b/compiler/prefixmatches.nim @@ -24,7 +24,7 @@ proc prefixMatch*(p, s: string): PrefixMatch = # check for prefix/contains: while i < L: if s[i] == '_': inc i - if eq(s[i], p[0]): + if i < L and eq(s[i], p[0]): var ii = i+1 var jj = 1 while ii < L and jj < p.len: @@ -43,10 +43,10 @@ proc prefixMatch*(p, s: string): PrefixMatch = i = 1 var j = 1 while i < s.len: - if s[i] == '_' and i < s.len-1: + if i < s.len-1 and s[i] == '_': if j < p.len and eq(p[j], s[i+1]): inc j else: return PrefixMatch.None - if s[i] in {'A'..'Z'} and s[i-1] notin {'A'..'Z'}: + if i < s.len and s[i] in {'A'..'Z'} and s[i-1] notin {'A'..'Z'}: if j < p.len and eq(p[j], s[i]): inc j else: return PrefixMatch.None inc i diff --git a/compiler/procfind.nim b/compiler/procfind.nim index 137765ddb..3f47e7e8a 100644 --- a/compiler/procfind.nim +++ b/compiler/procfind.nim @@ -14,14 +14,12 @@ import ast, astalgo, msgs, semdata, types, trees, strutils proc equalGenericParams(procA, procB: PNode): bool = - if sonsLen(procA) != sonsLen(procB): return + if sonsLen(procA) != sonsLen(procB): return false for i in countup(0, sonsLen(procA) - 1): if procA.sons[i].kind != nkSym: - internalError(procA.info, "equalGenericParams") - return + return false if procB.sons[i].kind != nkSym: - internalError(procB.info, "equalGenericParams") - return + return false let a = procA.sons[i].sym let b = procB.sons[i].sym if a.name.id != b.name.id or @@ -57,7 +55,7 @@ proc searchForProcOld*(c: PContext, scope: PScope, fn: PSym): PSym = of paramsEqual: return of paramsIncompatible: - localError(fn.info, errNotOverloadable, fn.name.s) + localError(c.config, fn.info, "overloaded '$1' leads to ambiguous calls" % fn.name.s) return of paramsNotEqual: discard @@ -66,30 +64,25 @@ proc searchForProcOld*(c: PContext, scope: PScope, fn: PSym): PSym = proc searchForProcNew(c: PContext, scope: PScope, fn: PSym): PSym = const flags = {ExactGenericParams, ExactTypeDescValues, ExactConstraints, IgnoreCC} - var it: TIdentIter - result = initIdentIter(it, scope.symbols, fn.name) while result != nil: - if result.kind == fn.kind and sameType(result.typ, fn.typ, flags): + if result.kind == fn.kind: #and sameType(result.typ, fn.typ, flags): case equalParams(result.typ.n, fn.typ.n) of paramsEqual: if (sfExported notin result.flags) and (sfExported in fn.flags): let message = ("public implementation '$1' has non-public " & "forward declaration in $2") % - [getProcHeader(result), $result.info] - localError(fn.info, errGenerated, message) + [getProcHeader(c.config, result), c.config$result.info] + localError(c.config, fn.info, message) return of paramsIncompatible: - localError(fn.info, errNotOverloadable, fn.name.s) + localError(c.config, fn.info, "overloaded '$1' leads to ambiguous calls" % fn.name.s) return of paramsNotEqual: discard - result = nextIdentIter(it, scope.symbols) - return nil - proc searchForProc*(c: PContext, scope: PScope, fn: PSym): PSym = result = searchForProcNew(c, scope, fn) when false: diff --git a/compiler/renderer.nim b/compiler/renderer.nim index fa93c968a..aa666290c 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -10,7 +10,7 @@ # This module implements the renderer of the standard Nim representation. import - lexer, options, idents, strutils, ast, msgs + lexer, options, idents, strutils, ast, msgs, lineinfos type TRenderFlag* = enum @@ -39,8 +39,8 @@ type inPragma: int when defined(nimpretty): pendingNewlineCount: int - origContent: string - + fid*: FileIndex + config*: ConfigRef # We render the source code in a two phases: The first # determines how long the subtree will likely be, the second @@ -91,7 +91,7 @@ const MaxLineLen = 80 LineCommentColumn = 30 -proc initSrcGen(g: var TSrcGen, renderFlags: TRenderFlags) = +proc initSrcGen(g: var TSrcGen, renderFlags: TRenderFlags; config: ConfigRef) = g.comStack = @[] g.tokens = @[] g.indent = 0 @@ -103,6 +103,7 @@ proc initSrcGen(g: var TSrcGen, renderFlags: TRenderFlags) = g.pendingNL = -1 g.pendingWhitespace = -1 g.inGenericParams = false + g.config = config proc addTok(g: var TSrcGen, kind: TTokType, s: string) = var length = len(g.tokens) @@ -173,34 +174,14 @@ proc put(g: var TSrcGen, kind: TTokType, s: string) = else: g.pendingWhitespace = s.len -proc toNimChar(c: char): string = - case c - of '\0': result = "\\x00" # not "\\0" to avoid ambiguous cases like "\\012". - of '\a': result = "\\a" # \x07 - of '\b': result = "\\b" # \x08 - of '\t': result = "\\t" # \x09 - of '\L': result = "\\L" # \x0A - of '\v': result = "\\v" # \x0B - of '\f': result = "\\f" # \x0C - of '\c': result = "\\c" # \x0D - of '\e': result = "\\e" # \x1B - of '\x01'..'\x06', '\x0E'..'\x1A', '\x1C'..'\x1F', '\x80'..'\xFF': - result = "\\x" & strutils.toHex(ord(c), 2) - of '\'', '\"', '\\': result = '\\' & c - else: result = c & "" - -proc makeNimString(s: string): string = - result = "\"" - for i in countup(0, len(s)-1): add(result, toNimChar(s[i])) - add(result, '\"') - proc putComment(g: var TSrcGen, s: string) = - if s.isNil: return + if s.len == 0: return var i = 0 + let hi = len(s) - 1 var isCode = (len(s) >= 2) and (s[1] != ' ') var ind = g.lineLen var com = "## " - while true: + while i <= hi: case s[i] of '\0': break @@ -208,7 +189,7 @@ proc putComment(g: var TSrcGen, s: string) = put(g, tkComment, com) com = "## " inc(i) - if s[i] == '\x0A': inc(i) + if i <= hi and s[i] == '\x0A': inc(i) optNL(g, ind) of '\x0A': put(g, tkComment, com) @@ -223,28 +204,29 @@ proc putComment(g: var TSrcGen, s: string) = # gets too long: # compute length of the following word: var j = i - while s[j] > ' ': inc(j) + while j <= hi and s[j] > ' ': inc(j) if not isCode and (g.lineLen + (j - i) > MaxLineLen): put(g, tkComment, com) optNL(g, ind) com = "## " - while s[i] > ' ': + while i <= hi and s[i] > ' ': add(com, s[i]) inc(i) put(g, tkComment, com) optNL(g) proc maxLineLength(s: string): int = - if s.isNil: return 0 + if s.len == 0: return 0 var i = 0 + let hi = len(s) - 1 var lineLen = 0 - while true: + while i <= hi: case s[i] of '\0': break of '\x0D': inc(i) - if s[i] == '\x0A': inc(i) + if i <= hi and s[i] == '\x0A': inc(i) result = max(result, lineLen) lineLen = 0 of '\x0A': @@ -257,7 +239,7 @@ proc maxLineLength(s: string): int = proc putRawStr(g: var TSrcGen, kind: TTokType, s: string) = var i = 0 - var hi = len(s) - 1 + let hi = len(s) - 1 var str = "" while i <= hi: case s[i] @@ -265,7 +247,7 @@ proc putRawStr(g: var TSrcGen, kind: TTokType, s: string) = put(g, kind, str) str = "" inc(i) - if (i <= hi) and (s[i] == '\x0A'): inc(i) + if i <= hi and s[i] == '\x0A': inc(i) optNL(g, 0) of '\x0A': put(g, kind, str) @@ -299,7 +281,7 @@ const proc shouldRenderComment(g: var TSrcGen, n: PNode): bool = result = false - if n.comment != nil: + if n.comment.len > 0: result = (renderNoComments notin g.flags) or (renderDocComments in g.flags) @@ -325,14 +307,19 @@ proc lsub(g: TSrcGen; n: PNode): int proc litAux(g: TSrcGen; n: PNode, x: BiggestInt, size: int): string = proc skip(t: PType): PType = result = t - while result.kind in {tyGenericInst, tyRange, tyVar, tyLent, tyDistinct, + while result != nil and result.kind in {tyGenericInst, tyRange, tyVar, tyLent, tyDistinct, tyOrdinal, tyAlias, tySink}: result = lastSon(result) - if n.typ != nil and n.typ.skip.kind in {tyBool, tyEnum}: - let enumfields = n.typ.skip.n + let typ = n.typ.skip + if typ != nil and typ.kind in {tyBool, tyEnum}: + if sfPure in typ.sym.flags: + result = typ.sym.name.s & '.' + let enumfields = typ.n # we need a slow linear search because of enums with holes: for e in items(enumfields): - if e.sym.position == x: return e.sym.name.s + if e.sym.position == x: + result &= e.sym.name.s + return if nfBase2 in n.flags: result = "0b" & toBin(x, size * 8) elif nfBase8 in n.flags: result = "0o" & toOct(x, size * 3) @@ -348,23 +335,27 @@ proc ulitAux(g: TSrcGen; n: PNode, x: BiggestInt, size: int): string = proc atom(g: TSrcGen; n: PNode): string = when defined(nimpretty): + doAssert g.config != nil, "g.config not initialized!" let comment = if n.info.commentOffsetA < n.info.commentOffsetB: - " " & substr(g.origContent, n.info.commentOffsetA, n.info.commentOffsetB) + " " & fileSection(g.config, g.fid, n.info.commentOffsetA, n.info.commentOffsetB) else: "" if n.info.offsetA <= n.info.offsetB: # for some constructed tokens this can not be the case and we're better # off to not mess with the offset then. - return substr(g.origContent, n.info.offsetA, n.info.offsetB) & comment + return fileSection(g.config, g.fid, n.info.offsetA, n.info.offsetB) & comment var f: float32 case n.kind of nkEmpty: result = "" of nkIdent: result = n.ident.s of nkSym: result = n.sym.name.s - of nkStrLit: result = makeNimString(n.strVal) - of nkRStrLit: result = "r\"" & replace(n.strVal, "\"", "\"\"") & '\"' + of nkStrLit: result = ""; result.addQuoted(n.strVal) + of nkRStrLit: result = "r\"" & replace(n.strVal, "\"", "\"\"") & '\"' of nkTripleStrLit: result = "\"\"\"" & n.strVal & "\"\"\"" - of nkCharLit: result = '\'' & toNimChar(chr(int(n.intVal))) & '\'' + of nkCharLit: + result = "\'" + result.addEscapedChar(chr(int(n.intVal))); + result.add '\'' of nkIntLit: result = litAux(g, n, n.intVal, 4) of nkInt8Lit: result = litAux(g, n, n.intVal, 1) & "\'i8" of nkInt16Lit: result = litAux(g, n, n.intVal, 2) & "\'i16" @@ -394,15 +385,17 @@ proc atom(g: TSrcGen; n: PNode): string = if (n.typ != nil) and (n.typ.sym != nil): result = n.typ.sym.name.s else: result = "[type node]" else: - internalError("rnimsyn.atom " & $n.kind) + internalError(g.config, "rnimsyn.atom " & $n.kind) result = "" proc lcomma(g: TSrcGen; n: PNode, start: int = 0, theEnd: int = - 1): int = assert(theEnd < 0) result = 0 for i in countup(start, sonsLen(n) + theEnd): - inc(result, lsub(g, n.sons[i])) - inc(result, 2) # for ``, `` + let param = n.sons[i] + if nfDefaultParam notin param.flags: + inc(result, lsub(g, param)) + inc(result, 2) # for ``, `` if result > 0: dec(result, 2) # last does not get a comma! @@ -414,7 +407,7 @@ proc lsons(g: TSrcGen; n: PNode, start: int = 0, theEnd: int = - 1): int = proc lsub(g: TSrcGen; n: PNode): int = # computes the length of a tree if isNil(n): return 0 - if n.comment != nil: return MaxLineLen + 1 + if n.comment.len > 0: return MaxLineLen + 1 case n.kind of nkEmpty: result = 0 of nkTripleStrLit: @@ -428,10 +421,13 @@ proc lsub(g: TSrcGen; n: PNode): int = of nkCast: result = lsub(g, n.sons[0]) + lsub(g, n.sons[1]) + len("cast[]()") of nkAddr: result = (if n.len>0: lsub(g, n.sons[0]) + len("addr()") else: 4) of nkStaticExpr: result = lsub(g, n.sons[0]) + len("static_") - of nkHiddenAddr, nkHiddenDeref: result = lsub(g, n.sons[0]) + of nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString: result = lsub(g, n.sons[0]) of nkCommand: result = lsub(g, n.sons[0]) + lcomma(g, n, 1) + 1 of nkExprEqExpr, nkAsgn, nkFastAsgn: result = lsons(g, n) + 3 of nkPar, nkCurly, nkBracket, nkClosure: result = lcomma(g, n) + 2 + of nkTupleConstr: + # assume the trailing comma: + result = lcomma(g, n) + 3 of nkArgList: result = lcomma(g, n) of nkTableConstr: result = if n.len > 0: lcomma(g, n) + 2 else: len("{:}") @@ -455,7 +451,7 @@ proc lsub(g: TSrcGen; n: PNode): int = of nkChckRangeF: result = len("chckRangeF") + 2 + lcomma(g, n) of nkChckRange64: result = len("chckRange64") + 2 + lcomma(g, n) of nkChckRange: result = len("chckRange") + 2 + lcomma(g, n) - of nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString: + of nkObjDownConv, nkObjUpConv: result = 2 if sonsLen(n) >= 1: result = result + lsub(g, n.sons[0]) result = result + lcomma(g, n, 1) @@ -509,7 +505,7 @@ proc lsub(g: TSrcGen; n: PNode): int = of nkBreakStmt: result = lsub(g, n.sons[0]) + len("break_") of nkContinueStmt: result = lsub(g, n.sons[0]) + len("continue_") of nkPragma: result = lcomma(g, n) + 4 - of nkCommentStmt: result = if n.comment.isNil: 0 else: len(n.comment) + of nkCommentStmt: result = len(n.comment) of nkOfBranch: result = lcomma(g, n, 0, - 2) + lsub(g, lastSon(n)) + len("of_:_") of nkImportAs: result = lsub(g, n.sons[0]) + len("_as_") + lsub(g, n.sons[1]) of nkElifBranch: result = lsons(g, n) + len("elif_:_") @@ -548,7 +544,7 @@ proc gsub(g: var TSrcGen, n: PNode) = proc hasCom(n: PNode): bool = result = false if n.isNil: return false - if n.comment != nil: return true + if n.comment.len > 0: return true case n.kind of nkEmpty..nkNilLit: discard else: @@ -611,7 +607,7 @@ proc gsection(g: var TSrcGen, n: PNode, c: TContext, kind: TTokType, dedent(g) proc longMode(g: TSrcGen; n: PNode, start: int = 0, theEnd: int = - 1): bool = - result = n.comment != nil + result = n.comment.len > 0 if not result: # check further for i in countup(start, sonsLen(n) + theEnd): @@ -636,15 +632,23 @@ proc gstmts(g: var TSrcGen, n: PNode, c: TContext, doIndent=true) = gcoms(g) if doIndent: dedent(g) else: - if rfLongMode in c.flags: indentNL(g) + indentNL(g) gsub(g, n) gcoms(g) + dedent(g) optNL(g) - if rfLongMode in c.flags: dedent(g) + + +proc gcond(g: var TSrcGen, n: PNode) = + if n.kind == nkStmtListExpr: + put(g, tkParLe, "(") + gsub(g, n) + if n.kind == nkStmtListExpr: + put(g, tkParRi, ")") proc gif(g: var TSrcGen, n: PNode) = var c: TContext - gsub(g, n.sons[0].sons[0]) + gcond(g, n.sons[0].sons[0]) initContext(c) putWithSpace(g, tkColon, ":") if longMode(g, n) or (lsub(g, n.sons[0].sons[1]) + g.lineLen > MaxLineLen): @@ -659,7 +663,7 @@ proc gif(g: var TSrcGen, n: PNode) = proc gwhile(g: var TSrcGen, n: PNode) = var c: TContext putWithSpace(g, tkWhile, "while") - gsub(g, n.sons[0]) + gcond(g, n.sons[0]) putWithSpace(g, tkColon, ":") initContext(c) if longMode(g, n) or (lsub(g, n.sons[1]) + g.lineLen > MaxLineLen): @@ -723,7 +727,7 @@ proc gcase(g: var TSrcGen, n: PNode) = var last = if n.sons[length-1].kind == nkElse: -2 else: -1 if longMode(g, n, 0, last): incl(c.flags, rfLongMode) putWithSpace(g, tkCase, "case") - gsub(g, n.sons[0]) + gcond(g, n.sons[0]) gcoms(g) optNL(g) gsons(g, n, c, 1, last) @@ -753,7 +757,8 @@ proc gproc(g: var TSrcGen, n: PNode) = gsub(g, n.sons[genericParamsPos]) g.inGenericParams = oldInGenericParams gsub(g, n.sons[paramsPos]) - gsub(g, n.sons[pragmasPos]) + if renderNoPragmas notin g.flags: + gsub(g, n.sons[pragmasPos]) if renderNoBody notin g.flags: if n.sons[bodyPos].kind != nkEmpty: put(g, tkSpaces, Space) @@ -794,10 +799,7 @@ proc gblock(g: var TSrcGen, n: PNode) = if longMode(g, n) or (lsub(g, n.sons[1]) + g.lineLen > MaxLineLen): incl(c.flags, rfLongMode) gcoms(g) - # XXX I don't get why this is needed here! gstmts should already handle this! - indentNL(g) gstmts(g, n.sons[1], c) - dedent(g) proc gstaticStmt(g: var TSrcGen, n: PNode) = var c: TContext @@ -823,8 +825,8 @@ proc gident(g: var TSrcGen, n: PNode) = var t: TTokType var s = atom(g, n) - if (s[0] in lexer.SymChars): - if (n.kind == nkIdent): + if s.len > 0 and s[0] in lexer.SymChars: + if n.kind == nkIdent: if (n.ident.id < ord(tokKeywordLow) - ord(tkSymbol)) or (n.ident.id > ord(tokKeywordHigh) - ord(tkSymbol)): t = tkSymbol @@ -864,11 +866,52 @@ proc isBracket*(n: PNode): bool = of nkSym: result = n.sym.name.s == "[]" else: result = false +proc skipHiddenNodes(n: PNode): PNode = + result = n + while result != nil: + if result.kind in {nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv} and result.len > 1: + result = result[1] + elif result.kind in {nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString} and + result.len > 0: + result = result[0] + else: break + +proc accentedName(g: var TSrcGen, n: PNode) = + if n == nil: return + let isOperator = + if n.kind == nkIdent and n.ident.s.len > 0 and n.ident.s[0] in OpChars: true + elif n.kind == nkSym and n.sym.name.s.len > 0 and n.sym.name.s[0] in OpChars: true + else: false + + if isOperator: + put(g, tkAccent, "`") + gident(g, n) + put(g, tkAccent, "`") + else: + gsub(g, n) + +proc infixArgument(g: var TSrcGen, n: PNode, i: int) = + if i >= n.len: return + + var needsParenthesis = false + let n_next = n[i].skipHiddenNodes + if n_next.kind == nkInfix: + if n_next[0].kind in {nkSym, nkIdent} and n[0].kind in {nkSym, nkIdent}: + let nextId = if n_next[0].kind == nkSym: n_next[0].sym.name else: n_next[0].ident + let nnId = if n[0].kind == nkSym: n[0].sym.name else: n[0].ident + if getPrecedence(nextId) < getPrecedence(nnId): + needsParenthesis = true + if needsParenthesis: + put(g, tkParLe, "(") + gsub(g, n, i) + if needsParenthesis: + put(g, tkParRi, ")") + proc gsub(g: var TSrcGen, n: PNode, c: TContext) = if isNil(n): return var a: TContext - if n.comment != nil: pushCom(g, n) + if n.comment.len > 0: pushCom(g, n) case n.kind # atoms: of nkTripleStrLit: put(g, tkTripleStrLit, atom(g, n)) of nkEmpty: discard @@ -899,7 +942,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = gcomma(g, n, 2) put(g, tkBracketRi, "]") elif n.len > 1 and n.lastSon.kind == nkStmtList: - gsub(g, n[0]) + accentedName(g, n[0]) if n.len > 2: put(g, tkParLe, "(") gcomma(g, n, 1, -2) @@ -907,16 +950,16 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = put(g, tkColon, ":") gsub(g, n, n.len-1) else: - if sonsLen(n) >= 1: gsub(g, n.sons[0]) + if sonsLen(n) >= 1: accentedName(g, n[0]) put(g, tkParLe, "(") gcomma(g, n, 1) put(g, tkParRi, ")") of nkCallStrLit: - gsub(g, n, 0) + if n.len > 0: accentedName(g, n[0]) if n.len > 1 and n.sons[1].kind == nkRStrLit: put(g, tkRStrLit, '\"' & replace(n[1].strVal, "\"", "\"\"") & '\"') else: - gsub(g, n.sons[1]) + gsub(g, n, 1) of nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv: if n.len >= 2: gsub(g, n.sons[1]) @@ -954,7 +997,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = gsub(g, n, 0) gcomma(g, n, 1) of nkCommand: - gsub(g, n, 0) + accentedName(g, n[0]) put(g, tkSpaces, Space) gcomma(g, n, 1) of nkExprEqExpr, nkAsgn, nkFastAsgn: @@ -977,7 +1020,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = put(g, tkParLe, "(") gcomma(g, n) put(g, tkParRi, ")") - of nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString: + of nkObjDownConv, nkObjUpConv: if sonsLen(n) >= 1: gsub(g, n.sons[0]) put(g, tkParLe, "(") gcomma(g, n, 1) @@ -1002,6 +1045,11 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = put(g, tkParLe, "(") gcomma(g, n, c) put(g, tkParRi, ")") + of nkTupleConstr: + put(g, tkParLe, "(") + gcomma(g, n, c) + if n.len == 1: put(g, tkComma, ",") + put(g, tkParRi, ")") of nkCurly: put(g, tkCurlyLe, "{") gcomma(g, n, c) @@ -1024,7 +1072,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = of nkBind: putWithSpace(g, tkBind, "bind") gsub(g, n, 0) - of nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref: + of nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString: gsub(g, n, 0) of nkLambda: putWithSpace(g, tkProc, "proc") @@ -1062,23 +1110,25 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = putWithSpace(g, tkColon, ":") gsub(g, n, 1) of nkInfix: - gsub(g, n, 1) + infixArgument(g, n, 1) put(g, tkSpaces, Space) gsub(g, n, 0) # binary operator if not fits(g, lsub(g, n.sons[2]) + lsub(g, n.sons[0]) + 1): optNL(g, g.indent + longIndentWid) else: put(g, tkSpaces, Space) - gsub(g, n, 2) + infixArgument(g, n, 2) of nkPrefix: gsub(g, n, 0) if n.len > 1: let opr = if n[0].kind == nkIdent: n[0].ident elif n[0].kind == nkSym: n[0].sym.name + elif n[0].kind in {nkOpenSymChoice, nkClosedSymChoice}: n[0][0].sym.name else: nil - if n[1].kind == nkPrefix or (opr != nil and renderer.isKeyword(opr)): + let n_next = skipHiddenNodes(n[1]) + if n_next.kind == nkPrefix or (opr != nil and renderer.isKeyword(opr)): put(g, tkSpaces, Space) - if n.sons[1].kind == nkInfix: + if n_next.kind == nkInfix: put(g, tkParLe, "(") gsub(g, n.sons[1]) put(g, tkParRi, ")") @@ -1103,13 +1153,13 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = put(g, tkAccent, "`") of nkIfExpr: putWithSpace(g, tkIf, "if") - if n.len > 0: gsub(g, n.sons[0], 0) + if n.len > 0: gcond(g, n.sons[0].sons[0]) putWithSpace(g, tkColon, ":") if n.len > 0: gsub(g, n.sons[0], 1) gsons(g, n, emptyContext, 1) of nkElifExpr: putWithSpace(g, tkElif, " elif") - gsub(g, n, 0) + gcond(g, n[0]) putWithSpace(g, tkColon, ":") gsub(g, n, 1) of nkElseExpr: @@ -1291,17 +1341,16 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = putWithSpace(g, tkContinue, "continue") gsub(g, n, 0) of nkPragma: - if renderNoPragmas notin g.flags: - if g.inPragma <= 0: - inc g.inPragma - #if not previousNL(g): - put(g, tkSpaces, Space) - put(g, tkCurlyDotLe, "{.") - gcomma(g, n, emptyContext) - put(g, tkCurlyDotRi, ".}") - dec g.inPragma - else: - gcomma(g, n, emptyContext) + if g.inPragma <= 0: + inc g.inPragma + #if not previousNL(g): + put(g, tkSpaces, Space) + put(g, tkCurlyDotLe, "{.") + gcomma(g, n, emptyContext) + put(g, tkCurlyDotRi, ".}") + dec g.inPragma + else: + gcomma(g, n, emptyContext) of nkImportStmt, nkExportStmt: if n.kind == nkImportStmt: putWithSpace(g, tkImport, "import") @@ -1419,22 +1468,32 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = put(g, tkParLe, "(ComesFrom|") gsub(g, n, 0) put(g, tkParRi, ")") - of nkGotoState, nkState: + of nkGotoState: var c: TContext initContext c - putWithSpace g, tkSymbol, if n.kind == nkState: "state" else: "goto" + putWithSpace g, tkSymbol, "goto" gsons(g, n, c) + of nkState: + var c: TContext + initContext c + putWithSpace g, tkSymbol, "state" + gsub(g, n[0], c) + putWithSpace(g, tkColon, ":") + indentNL(g) + gsons(g, n, c, 1) + dedent(g) + of nkBreakState: put(g, tkTuple, "breakstate") of nkTypeClassTy: gTypeClassTy(g, n) else: #nkNone, nkExplicitTypeListCall: - internalError(n.info, "rnimsyn.gsub(" & $n.kind & ')') + internalError(g.config, n.info, "rnimsyn.gsub(" & $n.kind & ')') proc renderTree*(n: PNode, renderFlags: TRenderFlags = {}): string = var g: TSrcGen - initSrcGen(g, renderFlags) + initSrcGen(g, renderFlags, newPartialConfigRef()) # do not indent the initial statement list so that # writeFile("file.nim", repr n) # produces working Nim code: @@ -1447,17 +1506,14 @@ proc renderTree*(n: PNode, renderFlags: TRenderFlags = {}): string = proc `$`*(n: PNode): string = n.renderTree proc renderModule*(n: PNode, infile, outfile: string, - renderFlags: TRenderFlags = {}) = + renderFlags: TRenderFlags = {}; + fid = FileIndex(-1); + conf: ConfigRef = nil) = var f: File g: TSrcGen - initSrcGen(g, renderFlags) - when defined(nimpretty): - try: - g.origContent = readFile(infile) - except IOError: - rawMessage(errCannotOpenFile, infile) - + initSrcGen(g, renderFlags, conf) + g.fid = fid for i in countup(0, sonsLen(n) - 1): gsub(g, n.sons[i]) optNL(g) @@ -1466,16 +1522,14 @@ proc renderModule*(n: PNode, infile, outfile: string, nkCommentStmt: putNL(g) else: discard gcoms(g) - if optStdout in gGlobalOptions: - write(stdout, g.buf) - elif open(f, outfile, fmWrite): + if open(f, outfile, fmWrite): write(f, g.buf) close(f) else: - rawMessage(errCannotOpenFile, outfile) + rawMessage(g.config, errGenerated, "cannot open file: " & outfile) proc initTokRender*(r: var TSrcGen, n: PNode, renderFlags: TRenderFlags = {}) = - initSrcGen(r, renderFlags) + initSrcGen(r, renderFlags, newPartialConfigRef()) gsub(r, n) proc getNextTok*(r: var TSrcGen, kind: var TTokType, literal: var string) = diff --git a/compiler/reorder.nim b/compiler/reorder.nim index cde5b3a9f..27b19a373 100644 --- a/compiler/reorder.nim +++ b/compiler/reorder.nim @@ -1,7 +1,8 @@ -import - intsets, ast, idents, algorithm, renderer, parser, ospaths, strutils, - sequtils, msgs, modulegraphs, syntaxes, options, modulepaths, tables +import + intsets, ast, idents, algorithm, renderer, parser, ospaths, strutils, + sequtils, msgs, modulegraphs, syntaxes, options, modulepaths, tables, + lineinfos type DepN = ref object @@ -10,11 +11,11 @@ type onStack: bool kids: seq[DepN] hAQ, hIS, hB, hCmd: int - when not defined(release): + when defined(debugReorder): expls: seq[string] DepG = seq[DepN] -when not defined(release): +when defined(debugReorder): var idNames = newTable[int, string]() proc newDepN(id: int, pnode: PNode): DepN = @@ -29,10 +30,10 @@ proc newDepN(id: int, pnode: PNode): DepN = result.hIS = -1 result.hB = -1 result.hCmd = -1 - when not defined(release): + when defined(debugReorder): result.expls = @[] -proc accQuoted(n: PNode): PIdent = +proc accQuoted(cache: IdentCache; n: PNode): PIdent = var id = "" for i in 0 ..< n.len: let x = n[i] @@ -40,33 +41,33 @@ proc accQuoted(n: PNode): PIdent = of nkIdent: id.add(x.ident.s) of nkSym: id.add(x.sym.name.s) else: discard - result = getIdent(id) + result = getIdent(cache, id) -proc addDecl(n: PNode; declares: var IntSet) = +proc addDecl(cache: IdentCache; n: PNode; declares: var IntSet) = case n.kind - of nkPostfix: addDecl(n[1], declares) - of nkPragmaExpr: addDecl(n[0], declares) + of nkPostfix: addDecl(cache, n[1], declares) + of nkPragmaExpr: addDecl(cache, n[0], declares) of nkIdent: declares.incl n.ident.id - when not defined(release): + when defined(debugReorder): idNames[n.ident.id] = n.ident.s of nkSym: declares.incl n.sym.name.id - when not defined(release): + when defined(debugReorder): idNames[n.sym.name.id] = n.sym.name.s of nkAccQuoted: - let a = accQuoted(n) + let a = accQuoted(cache, n) declares.incl a.id - when not defined(release): + when defined(debugReorder): idNames[a.id] = a.s of nkEnumFieldDef: - addDecl(n[0], declares) + addDecl(cache, n[0], declares) else: discard -proc computeDeps(n: PNode, declares, uses: var IntSet; topLevel: bool) = - template deps(n) = computeDeps(n, declares, uses, false) +proc computeDeps(cache: IdentCache; n: PNode, declares, uses: var IntSet; topLevel: bool) = + template deps(n) = computeDeps(cache, n, declares, uses, false) template decl(n) = - if topLevel: addDecl(n, declares) + if topLevel: addDecl(cache, n, declares) case n.kind of procDefs, nkMacroDef, nkTemplateDef: decl(n[0]) @@ -92,11 +93,11 @@ proc computeDeps(n: PNode, declares, uses: var IntSet; topLevel: bool) = deps(n[i]) of nkIdent: uses.incl n.ident.id of nkSym: uses.incl n.sym.name.id - of nkAccQuoted: uses.incl accQuoted(n).id + of nkAccQuoted: uses.incl accQuoted(cache, n).id of nkOpenSymChoice, nkClosedSymChoice: uses.incl n.sons[0].sym.name.id of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt: - for i in 0..<len(n): computeDeps(n[i], declares, uses, topLevel) + for i in 0..<len(n): computeDeps(cache, n[i], declares, uses, topLevel) of nkPragma: let a = n.sons[0] if a.kind == nkExprColonExpr and a.sons[0].kind == nkIdent and @@ -135,15 +136,13 @@ proc hasIncludes(n:PNode): bool = if a.kind == nkIncludeStmt: return true -proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: int32; - cache: IdentCache): PNode {.procvar.} = - result = syntaxes.parseFile(fileIdx, cache) +proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PNode {.procvar.} = + result = syntaxes.parseFile(fileIdx, graph.cache, graph.config) graph.addDep(s, fileIdx) - graph.addIncludeDep(s.position.int32, fileIdx) + graph.addIncludeDep(FileIndex s.position, fileIdx) -proc expandIncludes(graph: ModuleGraph, module: PSym, n: PNode, - modulePath: string, includedFiles: var IntSet, - cache: IdentCache): PNode = +proc expandIncludes(graph: ModuleGraph, module: PSym, n: PNode, + modulePath: string, includedFiles: var IntSet): PNode = # Parses includes and injects them in the current tree if not n.hasIncludes: return n @@ -151,15 +150,16 @@ proc expandIncludes(graph: ModuleGraph, module: PSym, n: PNode, for a in n: if a.kind == nkIncludeStmt: for i in 0..<a.len: - var f = checkModuleName(a.sons[i]) + var f = checkModuleName(graph.config, a.sons[i]) if f != InvalidFileIDX: - if containsOrIncl(includedFiles, f): - localError(a.info, errRecursiveDependencyX, f.toFilename) + if containsOrIncl(includedFiles, f.int): + localError(graph.config, a.info, "recursive dependency: '$1'" % + toFilename(graph.config, f)) else: - let nn = includeModule(graph, module, f, cache) - let nnn = expandIncludes(graph, module, nn, modulePath, - includedFiles, cache) - excl(includedFiles, f) + let nn = includeModule(graph, module, f) + let nnn = expandIncludes(graph, module, nn, modulePath, + includedFiles) + excl(includedFiles, f.int) for b in nnn: result.add b else: @@ -189,7 +189,7 @@ proc haveSameKind(dns: seq[DepN]): bool = if dn.pnode.kind != kind: return false -proc mergeSections(comps: seq[seq[DepN]], res: PNode) = +proc mergeSections(conf: ConfigRef; comps: seq[seq[DepN]], res: PNode) = # Merges typeSections and ConstSections when they form # a strong component (ex: circular type definition) for c in comps: @@ -214,7 +214,7 @@ proc mergeSections(comps: seq[seq[DepN]], res: PNode) = # consecutive type and const sections var wmsg = "Circular dependency detected. reorder pragma may not be able to" & " reorder some nodes properely" - when not defined(release): + when defined(debugReorder): wmsg &= ":\n" for i in 0..<cs.len-1: for j in i..<cs.len: @@ -229,7 +229,7 @@ proc mergeSections(comps: seq[seq[DepN]], res: PNode) = wmsg &= "line " & $cs[^1].pnode.info.line & " depends on line " & $cs[j].pnode.info.line & ": " & cs[^1].expls[ci] & "\n" - message(cs[0].pnode.info, warnUser, wmsg) + message(conf, cs[0].pnode.info, warnUser, wmsg) var i = 0 while i < cs.len: @@ -273,9 +273,9 @@ proc hasCommand(n: PNode): bool = of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt, nkLetSection, nkConstSection, nkVarSection, nkIdentDefs: - for a in n: - if a.hasCommand: - return true + for a in n: + if a.hasCommand: + return true else: return false @@ -353,13 +353,13 @@ proc buildGraph(n: PNode, deps: seq[(IntSet, IntSet)]): DepG = if j < i and nj.hasCommand and niHasCmd: # Preserve order for commands and calls ni.kids.add nj - when not defined(release): + when defined(debugReorder): ni.expls.add "both have commands and one comes after the other" elif j < i and nj.hasImportStmt: # Every node that comes after an import statement must # depend on that import ni.kids.add nj - when not defined(release): + when defined(debugReorder): ni.expls.add "parent is, or contains, an import statement and child comes after it" elif j < i and niHasBody and nj.hasAccQuotedDef: # Every function, macro, template... with a body depends @@ -367,13 +367,13 @@ proc buildGraph(n: PNode, deps: seq[(IntSet, IntSet)]): DepG = # That's because it is hard to detect the use of functions # like "[]=", "[]", "or" ... in their bodies. ni.kids.add nj - when not defined(release): + when defined(debugReorder): ni.expls.add "one declares a quoted identifier and the other has a body and comes after it" elif j < i and niHasBody and not nj.hasBody and intersects(deps[i][0], declares): # Keep function declaration before function definition ni.kids.add nj - when not defined(release): + when defined(debugReorder): for dep in deps[i][0]: if dep in declares: ni.expls.add "one declares \"" & idNames[dep] & "\" and the other defines it" @@ -381,7 +381,7 @@ proc buildGraph(n: PNode, deps: seq[(IntSet, IntSet)]): DepG = for d in declares: if uses.contains(d): ni.kids.add nj - when not defined(release): + when defined(debugReorder): ni.expls.add "one declares \"" & idNames[d] & "\" and the other uses it" proc strongConnect(v: var DepN, idx: var int, s: var seq[DepN], @@ -425,20 +425,20 @@ proc hasForbiddenPragma(n: PNode): bool = a[0].ident.s == "push": return true -proc reorder*(graph: ModuleGraph, n: PNode, module: PSym, cache: IdentCache): PNode = +proc reorder*(graph: ModuleGraph, n: PNode, module: PSym): PNode = if n.hasForbiddenPragma: return n var includedFiles = initIntSet() - let mpath = module.fileIdx.toFullPath - let n = expandIncludes(graph, module, n, mpath, - includedFiles, cache).splitSections + let mpath = toFullPath(graph.config, module.fileIdx) + let n = expandIncludes(graph, module, n, mpath, + includedFiles).splitSections result = newNodeI(nkStmtList, n.info) var deps = newSeq[(IntSet, IntSet)](n.len) for i in 0..<n.len: deps[i][0] = initIntSet() deps[i][1] = initIntSet() - computeDeps(n[i], deps[i][0], deps[i][1], true) + computeDeps(graph.cache, n[i], deps[i][0], deps[i][1], true) var g = buildGraph(n, deps) let comps = getStrongComponents(g) - mergeSections(comps, result) + mergeSections(graph.config, comps, result) diff --git a/compiler/rod.nim b/compiler/rod.nim index bc2f3931e..f9208f5dc 100644 --- a/compiler/rod.nim +++ b/compiler/rod.nim @@ -9,124 +9,21 @@ ## This module implements the canonalization for the various caching mechanisms. -import ast, idgen +import ast, idgen, lineinfos, msgs, incremental, modulegraphs -when not defined(nimSymbolfiles): - template setupModuleCache* = discard - template storeNode*(module: PSym; n: PNode) = discard - template loadNode*(module: PSym; index: var int): PNode = PNode(nil) +when not nimIncremental: + template setupModuleCache*(g: ModuleGraph) = discard + template storeNode*(g: ModuleGraph; module: PSym; n: PNode) = discard + template loadNode*(g: ModuleGraph; module: PSym): PNode = newNode(nkStmtList) - template getModuleId*(fileIdx: int32; fullpath: string): int = getID() + template getModuleId*(g: ModuleGraph; fileIdx: FileIndex; fullpath: string): int = getID() - template addModuleDep*(module, fileIdx: int32; isIncludeFile: bool) = discard + template addModuleDep*(g: ModuleGraph; module, fileIdx: FileIndex; isIncludeFile: bool) = discard - template storeRemaining*(module: PSym) = discard + template storeRemaining*(g: ModuleGraph; module: PSym) = discard else: include rodimpl -when false: - type - BlobWriter* = object - buf: string - pos: int - - SerializationAction = enum acRead, acWrite - - # Varint implementation inspired by SQLite. - proc rdVaruint64(z: ptr UncheckedArray[byte]; n: int; pResult: var uint64): int = - if z[0] <= 240: - pResult = z[0] - return 1 - if z[0] <= 248: - if n < 2: return 0 - pResult = (z[0] - 241) * 256 + z[1] + 240 - return 2 - if n < z[0]-246: return 0 - if z[0] == 249: - pResult = 2288 + 256*z[1] + z[2] - return 3 - if z[0] == 250: - pResult = (z[1] shl 16u64) + (z[2] shl 8u64) + z[3] - return 4 - let x = (z[1] shl 24) + (z[2] shl 16) + (z[3] shl 8) + z[4] - if z[0] == 251: - pResult = x - return 5 - if z[0] == 252: - pResult = (((uint64)x) shl 8) + z[5] - return 6 - if z[0] == 253: - pResult = (((uint64)x) shl 16) + (z[5] shl 8) + z[6] - return 7 - if z[0] == 254: - pResult = (((uint64)x) shl 24) + (z[5] shl 16) + (z[6] shl 8) + z[7] - return 8 - pResult = (((uint64)x) shl 32) + - (0xffffffff & ((z[5] shl 24) + (z[6] shl 16) + (z[7] shl 8) + z[8])) - return 9 - - proc varintWrite32(z: ptr UncheckedArray[byte]; y: uint32) = - z[0] = uint8(y shr 24) - z[1] = uint8(y shr 16) - z[2] = uint8(y shr 8) - z[3] = uint8(y) - - proc sqlite4PutVarint64(z: ptr UncheckedArray[byte], x: uint64): int = - ## Write a varint into z. The buffer z must be at least 9 characters - ## long to accommodate the largest possible varint. Returns the number of - ## bytes used. - if x <= 240: - z[0] = uint8 x - return 1 - if x <= 2287: - y = uint32(x - 240) - z[0] = uint8(y shr 8 + 241) - z[1] = uint8(y and 255) - return 2 - if x <= 67823: - y = uint32(x - 2288) - z[0] = 249 - z[1] = uint8(y shr 8) - z[2] = uint8(y and 255) - return 3 - let y = uint32 x - let w = uint32(x shr 32) - if w == 0: - if y <= 16777215: - z[0] = 250 - z[1] = uint8(y shr 16) - z[2] = uint8(y shr 8) - z[3] = uint8(y) - return 4 - z[0] = 251 - varintWrite32(z+1, y) - return 5 - if w <= 255: - z[0] = 252 - z[1] = uint8 w - varintWrite32(z+2, y) - return 6 - if w <= 65535: - z[0] = 253 - z[1] = uint8(w shr 8) - z[2] = uint8 w - varintWrite32(z+3, y) - return 7 - if w <= 16777215: - z[0] = 254 - z[1] = uint8(w shr 16) - z[2] = uint8(w shr 8) - z[3] = uint8 w - varintWrite32(z+4, y) - return 8 - z[0] = 255 - varintWrite32(z+1, w) - varintWrite32(z+5, y) - return 9 - - template field(x: BiggestInt; action: SerializationAction) = - when action == acRead: - readBiggestInt(x) - else: - writeBiggestInt() + # idea for testing all this logic: *Always* load the AST from the DB, whether + # we already have it in RAM or not! diff --git a/compiler/rodimpl.nim b/compiler/rodimpl.nim index aff4f6909..7d24e4e67 100644 --- a/compiler/rodimpl.nim +++ b/compiler/rodimpl.nim @@ -10,59 +10,67 @@ ## This module implements the new compilation cache. import strutils, os, intsets, tables, ropes, db_sqlite, msgs, options, types, - renderer, rodutils, std / sha1, idents, astalgo, magicsys + renderer, rodutils, idents, astalgo, btrees, magicsys, cgmeth, extccomp, + btrees, trees, condsyms, nversion ## Todo: -## - Implement the 'import' replay logic so that the codegen runs over -## dependent modules. -## - Make conditional symbols and the configuration part of a module's -## dependencies. -## - Test multi methods. -## - Implement the limited VM support based on sets. -## - Depencency computation should use signature hashes in order to +## - Dependency computation should use *signature* hashes in order to ## avoid recompiling dependent modules. +## - Patch the rest of the compiler to do lazy loading of proc bodies. +## - Patch the C codegen to cache proc bodies and maybe types. -var db: DbConn +template db(): DbConn = g.incr.db -proc hashFileCached(fileIdx: int32; fullpath: string): string = - result = msgs.getHash(fileIdx) - if result.len == 0: - result = $secureHashFile(fullpath) - msgs.setHash(fileIdx, result) +proc encodeConfig(g: ModuleGraph): string = + result = newStringOfCap(100) + result.add RodFileVersion + for d in definedSymbolNames(g.config.symbols): + result.add ' ' + result.add d -proc needsRecompile(fileIdx: int32; fullpath: string; cycleCheck: var IntSet): bool = + template serialize(field) = + result.add ' ' + result.add($g.config.field) + + depConfigFields(serialize) + +proc needsRecompile(g: ModuleGraph; fileIdx: FileIndex; fullpath: string; + cycleCheck: var IntSet): bool = let root = db.getRow(sql"select id, fullhash from filenames where fullpath = ?", fullpath) if root[0].len == 0: return true - if root[1] != hashFileCached(fileIdx, fullpath): + if root[1] != hashFileCached(g.config, fileIdx, fullpath): return true # cycle detection: assume "not changed" is correct. - if cycleCheck.containsOrIncl(fileIdx): + if cycleCheck.containsOrIncl(int fileIdx): return false # check dependencies (recursively): for row in db.fastRows(sql"select fullpath from filenames where id in (select dependency from deps where module = ?)", root[0]): let dep = row[0] - if needsRecompile(dep.fileInfoIdx, dep, cycleCheck): + if needsRecompile(g, g.config.fileInfoIdx(dep), dep, cycleCheck): return true return false -proc getModuleId*(fileIdx: int32; fullpath: string): int = - if gSymbolFiles != v2Sf: return getID() - let module = db.getRow( - sql"select id, fullHash from modules where fullpath = ?", fullpath) - let currentFullhash = hashFileCached(fileIdx, fullpath) +proc getModuleId*(g: ModuleGraph; fileIdx: FileIndex; fullpath: string): int = + if g.config.symbolFiles in {disabledSf, writeOnlySf} or + g.incr.configChanged: + return getID() + let module = g.incr.db.getRow( + sql"select id, fullHash, nimid from modules where fullpath = ?", fullpath) + let currentFullhash = hashFileCached(g.config, fileIdx, fullpath) if module[0].len == 0: - result = int db.insertID(sql"insert into modules(fullpath, interfHash, fullHash) values (?, ?, ?)", - fullpath, "", currentFullhash) + result = getID() + db.exec(sql"insert into modules(fullpath, interfHash, fullHash, nimid) values (?, ?, ?, ?)", + fullpath, "", currentFullhash, result) else: - result = parseInt(module[0]) + result = parseInt(module[2]) if currentFullhash == module[1]: - # not changed, so use the cached AST (even if it might be wrong - # due to its dependencies): + # not changed, so use the cached AST: doAssert(result != 0) var cycleCheck = initIntSet() - if not needsRecompile(fileIdx, fullpath, cycleCheck): + if not needsRecompile(g, fileIdx, fullpath, cycleCheck): + echo "cached successfully! ", fullpath return -result db.exec(sql"update modules set fullHash = ? where id = ?", currentFullhash, module[0]) db.exec(sql"delete from deps where module = ?", module[0]) @@ -71,60 +79,17 @@ proc getModuleId*(fileIdx: int32; fullpath: string): int = db.exec(sql"delete from toplevelstmts where module = ?", module[0]) db.exec(sql"delete from statics where module = ?", module[0]) -type - TRodWriter = object - module: PSym - sstack: seq[PSym] # a stack of symbols to process - tstack: seq[PType] # a stack of types to process - tmarks, smarks: IntSet - forwardedSyms: seq[PSym] - - PRodWriter = var TRodWriter - -proc initRodWriter(module: PSym): TRodWriter = - result = TRodWriter(module: module, sstack: @[], tstack: @[], - tmarks: initIntSet(), smarks: initIntSet(), forwardedSyms: @[]) - -when false: - proc getDefines(): string = - result = "" - for d in definedSymbolNames(): - if result.len != 0: add(result, " ") - add(result, d) - -const - rodNL = "\L" - -proc pushType(w: PRodWriter, t: PType) = +proc pushType(w: var Writer, t: PType) = if not containsOrIncl(w.tmarks, t.id): w.tstack.add(t) -proc pushSym(w: PRodWriter, s: PSym) = +proc pushSym(w: var Writer, s: PSym) = if not containsOrIncl(w.smarks, s.id): w.sstack.add(s) -proc toDbFileId(fileIdx: int32): int = - if fileIdx == -1: return -1 - let fullpath = fileIdx.toFullPath - let row = db.getRow(sql"select id, fullhash from filenames where fullpath = ?", - fullpath) - let id = row[0] - let fullhash = hashFileCached(fileIdx, fullpath) - if id.len == 0: - result = int db.insertID(sql"insert into filenames(fullpath, fullhash) values (?, ?)", - fullpath, fullhash) - else: - if row[1] != fullhash: - db.exec(sql"update filenames set fullhash = ? where fullpath = ?", fullhash, fullpath) - result = parseInt(id) - -proc fromDbFileId(dbId: int): int32 = - if dbId == -1: return -1 - let fullpath = db.getValue(sql"select fullpath from filenames where id = ?", dbId) - doAssert fullpath.len > 0, "cannot find file name for DB ID " & $dbId - result = fileInfoIdx(fullpath) +template w: untyped = g.incr.w -proc encodeNode(w: PRodWriter, fInfo: TLineInfo, n: PNode, +proc encodeNode(g: ModuleGraph; fInfo: TLineInfo, n: PNode, result: var string) = if n == nil: # nil nodes have to be stored too: @@ -139,14 +104,14 @@ proc encodeNode(w: PRodWriter, fInfo: TLineInfo, n: PNode, result.add('?') encodeVInt(n.info.col, result) result.add(',') - encodeVInt(n.info.line, result) + encodeVInt(int n.info.line, result) result.add(',') - encodeVInt(toDbFileId(n.info.fileIndex), result) + encodeVInt(toDbFileId(g.incr, g.config, n.info.fileIndex), result) elif fInfo.line != n.info.line: result.add('?') encodeVInt(n.info.col, result) result.add(',') - encodeVInt(n.info.line, result) + encodeVInt(int n.info.line, result) elif fInfo.col != n.info.col: result.add('?') encodeVInt(n.info.col, result) @@ -182,10 +147,10 @@ proc encodeNode(w: PRodWriter, fInfo: TLineInfo, n: PNode, pushSym(w, n.sym) else: for i in countup(0, sonsLen(n) - 1): - encodeNode(w, n.info, n.sons[i], result) + encodeNode(g, n.info, n.sons[i], result) add(result, ')') -proc encodeLoc(w: PRodWriter, loc: TLoc, result: var string) = +proc encodeLoc(g: ModuleGraph; loc: TLoc, result: var string) = var oldLen = result.len result.add('<') if loc.k != low(loc.k): encodeVInt(ord(loc.k), result) @@ -197,9 +162,7 @@ proc encodeLoc(w: PRodWriter, loc: TLoc, result: var string) = encodeVInt(cast[int32](loc.flags), result) if loc.lode != nil: add(result, '^') - encodeNode(w, unknownLineInfo(), loc.lode, result) - #encodeVInt(cast[int32](loc.t.id), result) - #pushType(w, loc.t) + encodeNode(g, unknownLineInfo(), loc.lode, result) if loc.r != nil: add(result, '!') encodeStr($loc.r, result) @@ -209,13 +172,13 @@ proc encodeLoc(w: PRodWriter, loc: TLoc, result: var string) = else: add(result, '>') -proc encodeType(w: PRodWriter, t: PType, result: var string) = +proc encodeType(g: ModuleGraph, t: PType, result: var string) = if t == nil: # nil nodes have to be stored too: result.add("[]") return # we need no surrounding [] here because the type is in a line of its own - if t.kind == tyForward: internalError("encodeType: tyForward") + if t.kind == tyForward: internalError(g.config, "encodeType: tyForward") # for the new rodfile viewer we use a preceding [ so that the data section # can easily be disambiguated: add(result, '[') @@ -223,7 +186,7 @@ proc encodeType(w: PRodWriter, t: PType, result: var string) = add(result, '+') encodeVInt(t.id, result) if t.n != nil: - encodeNode(w, w.module.info, t.n, result) + encodeNode(g, unknownLineInfo(), t.n, result) if t.flags != {}: add(result, '$') encodeVInt(cast[int32](t.flags), result) @@ -269,7 +232,7 @@ proc encodeType(w: PRodWriter, t: PType, result: var string) = add(result, '\20') encodeVInt(s.id, result) pushSym(w, s) - encodeLoc(w, t.loc, result) + encodeLoc(g, t.loc, result) for i in countup(0, sonsLen(t) - 1): if t.sons[i] == nil: add(result, "^()") @@ -278,15 +241,15 @@ proc encodeType(w: PRodWriter, t: PType, result: var string) = encodeVInt(t.sons[i].id, result) pushType(w, t.sons[i]) -proc encodeLib(w: PRodWriter, lib: PLib, info: TLineInfo, result: var string) = +proc encodeLib(g: ModuleGraph, lib: PLib, info: TLineInfo, result: var string) = add(result, '|') encodeVInt(ord(lib.kind), result) add(result, '|') encodeStr($lib.name, result) add(result, '|') - encodeNode(w, info, lib.path, result) + encodeNode(g, info, lib.path, result) -proc encodeInstantiations(w: PRodWriter; s: seq[PInstantiation]; +proc encodeInstantiations(g: ModuleGraph; s: seq[PInstantiation]; result: var string) = for t in s: result.add('\15') @@ -299,7 +262,7 @@ proc encodeInstantiations(w: PRodWriter; s: seq[PInstantiation]; result.add('\20') encodeVInt(t.compilesId, result) -proc encodeSym(w: PRodWriter, s: PSym, result: var string) = +proc encodeSym(g: ModuleGraph, s: PSym, result: var string) = if s == nil: # nil nodes have to be stored too: result.add("{}") @@ -317,9 +280,9 @@ proc encodeSym(w: PRodWriter, s: PSym, result: var string) = result.add('?') if s.info.col != -1'i16: encodeVInt(s.info.col, result) result.add(',') - if s.info.line != -1'i16: encodeVInt(s.info.line, result) + encodeVInt(int s.info.line, result) result.add(',') - encodeVInt(toDbFileId(s.info.fileIndex), result) + encodeVInt(toDbFileId(g.incr, g.config, s.info.fileIndex), result) if s.owner != nil: result.add('*') encodeVInt(s.owner.id, result) @@ -338,11 +301,11 @@ proc encodeSym(w: PRodWriter, s: PSym, result: var string) = if s.offset != - 1: result.add('`') encodeVInt(s.offset, result) - encodeLoc(w, s.loc, result) - if s.annex != nil: encodeLib(w, s.annex, s.info, result) + encodeLoc(g, s.loc, result) + if s.annex != nil: encodeLib(g, s.annex, s.info, result) if s.constraint != nil: add(result, '#') - encodeNode(w, unknownLineInfo(), s.constraint, result) + encodeNode(g, unknownLineInfo(), s.constraint, result) case s.kind of skType, skGenericParam: for t in s.typeInstCache: @@ -350,13 +313,13 @@ proc encodeSym(w: PRodWriter, s: PSym, result: var string) = encodeVInt(t.id, result) pushType(w, t) of routineKinds: - encodeInstantiations(w, s.procInstCache, result) + encodeInstantiations(g, s.procInstCache, result) if s.gcUnsafetyReason != nil: result.add('\16') encodeVInt(s.gcUnsafetyReason.id, result) pushSym(w, s.gcUnsafetyReason) of skModule, skPackage: - encodeInstantiations(w, s.usedGenerics, result) + encodeInstantiations(g, s.usedGenerics, result) # we don't serialize: #tab*: TStrTable # interface table for modules of skLet, skVar, skField, skForVar: @@ -374,105 +337,93 @@ proc encodeSym(w: PRodWriter, s: PSym, result: var string) = # we used to attempt to save space here by only storing a dummy AST if # it is not necessary, but Nim's heavy compile-time evaluation features # make that unfeasible nowadays: - encodeNode(w, s.info, s.ast, result) + encodeNode(g, s.info, s.ast, result) -proc storeSym(w: PRodWriter; s: PSym) = +proc storeSym(g: ModuleGraph; s: PSym) = if sfForward in s.flags and s.kind != skModule: w.forwardedSyms.add s return var buf = newStringOfCap(160) - encodeSym(w, s, buf) + encodeSym(g, s, buf) # XXX only store the name for exported symbols in order to speed up lookup # times once we enable the skStub logic. + let m = getModule(s) + let mid = if m == nil: 0 else: abs(m.id) db.exec(sql"insert into syms(nimid, module, name, data, exported) values (?, ?, ?, ?, ?)", - s.id, abs(w.module.id), s.name.s, buf, ord(sfExported in s.flags)) + s.id, mid, s.name.s, buf, ord(sfExported in s.flags)) -proc storeType(w: PRodWriter; t: PType) = +proc storeType(g: ModuleGraph; t: PType) = var buf = newStringOfCap(160) - encodeType(w, t, buf) + encodeType(g, t, buf) + let m = if t.owner != nil: getModule(t.owner) else: nil + let mid = if m == nil: 0 else: abs(m.id) db.exec(sql"insert into types(nimid, module, data) values (?, ?, ?)", - t.id, abs(w.module.id), buf) - -var w = initRodWriter(nil) + t.id, mid, buf) -proc storeNode*(module: PSym; n: PNode) = - if gSymbolFiles != v2Sf: return - w.module = module +proc storeNode*(g: ModuleGraph; module: PSym; n: PNode) = + if g.config.symbolFiles == disabledSf: return var buf = newStringOfCap(160) - encodeNode(w, module.info, n, buf) + encodeNode(g, module.info, n, buf) db.exec(sql"insert into toplevelstmts(module, position, data) values (?, ?, ?)", abs(module.id), module.offset, buf) inc module.offset var i = 0 while true: if i > 10_000: - quit "loop never ends!" + doAssert false, "loop never ends!" if w.sstack.len > 0: let s = w.sstack.pop() when false: echo "popped ", s.name.s, " ", s.id - storeSym(w, s) + storeSym(g, s) elif w.tstack.len > 0: let t = w.tstack.pop() - storeType(w, t) + storeType(g, t) when false: echo "popped type ", typeToString(t), " ", t.id else: break inc i -proc storeRemaining*(module: PSym) = - if gSymbolFiles != v2Sf: return - w.module = module +proc recordStmt*(g: ModuleGraph; module: PSym; n: PNode) = + storeNode(g, module, n) + +proc storeRemaining*(g: ModuleGraph; module: PSym) = + if g.config.symbolFiles == disabledSf: return + var stillForwarded: seq[PSym] = @[] for s in w.forwardedSyms: - assert sfForward notin s.flags - storeSym(w, s) - w.forwardedSyms.setLen 0 + if sfForward notin s.flags: + storeSym(g, s) + else: + stillForwarded.add s + swap w.forwardedSyms, stillForwarded # ---------------- decoder ----------------------------------- -type - TRodReader = object - module: PSym - #sstack: seq[(PSym, ptr PSym)] # a stack of symbols to process - #tstack: seq[(PType, ptr PType)] # a stack of types to process - - #tmarks, smarks: IntSet - syms: Table[int, PSym] ## XXX make this more efficients - types: Table[int, PType] - cache: IdentCache +type BlobReader = object s: string pos: int - PRodReader = var TRodReader - -proc initRodReader(cache: IdentCache): TRodReader = - TRodReader(module: nil, - syms: initTable[int, PSym](), types: initTable[int, PType](), - cache: cache) - -var gr = initRodReader(newIdentCache()) - using - r: PRodReader b: var BlobReader + g: ModuleGraph -proc loadSym(r; id: int, info: TLineInfo): PSym -proc loadType(r; id: int, info: TLineInfo): PType +proc loadSym(g; id: int, info: TLineInfo): PSym +proc loadType(g; id: int, info: TLineInfo): PType -proc decodeLineInfo(r; b; info: var TLineInfo) = +proc decodeLineInfo(g; b; info: var TLineInfo) = if b.s[b.pos] == '?': inc(b.pos) if b.s[b.pos] == ',': info.col = -1'i16 else: info.col = int16(decodeVInt(b.s, b.pos)) if b.s[b.pos] == ',': inc(b.pos) - if b.s[b.pos] == ',': info.line = -1'i16 - else: info.line = int16(decodeVInt(b.s, b.pos)) + if b.s[b.pos] == ',': info.line = 0'u16 + else: info.line = uint16(decodeVInt(b.s, b.pos)) if b.s[b.pos] == ',': inc(b.pos) - info.fileIndex = fromDbFileId(decodeVInt(b.s, b.pos)) + info.fileIndex = fromDbFileId(g.incr, g.config, decodeVInt(b.s, b.pos)) proc skipNode(b) = assert b.s[b.pos] == '(' @@ -488,7 +439,7 @@ proc skipNode(b) = inc pos b.pos = pos+1 # skip ')' -proc decodeNodeLazyBody(r; b; fInfo: TLineInfo, +proc decodeNodeLazyBody(g; b; fInfo: TLineInfo, belongsTo: PSym): PNode = result = nil if b.s[b.pos] == '(': @@ -497,14 +448,14 @@ proc decodeNodeLazyBody(r; b; fInfo: TLineInfo, inc(b.pos) return # nil node result = newNodeI(TNodeKind(decodeVInt(b.s, b.pos)), fInfo) - decodeLineInfo(r, b, result.info) + decodeLineInfo(g, b, result.info) if b.s[b.pos] == '$': inc(b.pos) result.flags = cast[TNodeFlags](int32(decodeVInt(b.s, b.pos))) if b.s[b.pos] == '^': inc(b.pos) var id = decodeVInt(b.s, b.pos) - result.typ = loadType(r, id, result.info) + result.typ = loadType(g, id, result.info) case result.kind of nkCharLit..nkUInt64Lit: if b.s[b.pos] == '!': @@ -525,16 +476,16 @@ proc decodeNodeLazyBody(r; b; fInfo: TLineInfo, if b.s[b.pos] == '!': inc(b.pos) var fl = decodeStr(b.s, b.pos) - result.ident = r.cache.getIdent(fl) + result.ident = g.cache.getIdent(fl) else: - internalError(result.info, "decodeNode: nkIdent") + internalError(g.config, result.info, "decodeNode: nkIdent") of nkSym: if b.s[b.pos] == '!': inc(b.pos) var id = decodeVInt(b.s, b.pos) - result.sym = loadSym(r, id, result.info) + result.sym = loadSym(g, id, result.info) else: - internalError(result.info, "decodeNode: nkSym") + internalError(g.config, result.info, "decodeNode: nkSym") else: var i = 0 while b.s[b.pos] != ')': @@ -545,17 +496,17 @@ proc decodeNodeLazyBody(r; b; fInfo: TLineInfo, skipNode(b) else: discard - addSonNilAllowed(result, decodeNodeLazyBody(r, b, result.info, nil)) + addSonNilAllowed(result, decodeNodeLazyBody(g, b, result.info, nil)) inc i if b.s[b.pos] == ')': inc(b.pos) - else: internalError(result.info, "decodeNode: ')' missing") + else: internalError(g.config, result.info, "decodeNode: ')' missing") else: - internalError(fInfo, "decodeNode: '(' missing " & $b.pos) + internalError(g.config, fInfo, "decodeNode: '(' missing " & $b.pos) -proc decodeNode(r; b; fInfo: TLineInfo): PNode = - result = decodeNodeLazyBody(r, b, fInfo, nil) +proc decodeNode(g; b; fInfo: TLineInfo): PNode = + result = decodeNodeLazyBody(g, b, fInfo, nil) -proc decodeLoc(r; b; loc: var TLoc, info: TLineInfo) = +proc decodeLoc(g; b; loc: var TLoc, info: TLineInfo) = if b.s[b.pos] == '<': inc(b.pos) if b.s[b.pos] in {'0'..'9', 'a'..'z', 'A'..'Z'}: @@ -574,7 +525,7 @@ proc decodeLoc(r; b; loc: var TLoc, info: TLineInfo) = loc.flags = {} if b.s[b.pos] == '^': inc(b.pos) - loc.lode = decodeNode(r, b, info) + loc.lode = decodeNode(g, b, info) # rrGetType(b, decodeVInt(b.s, b.pos), info) else: loc.lode = nil @@ -584,19 +535,21 @@ proc decodeLoc(r; b; loc: var TLoc, info: TLineInfo) = else: loc.r = nil if b.s[b.pos] == '>': inc(b.pos) - else: internalError(info, "decodeLoc " & b.s[b.pos]) + else: internalError(g.config, info, "decodeLoc " & b.s[b.pos]) -proc loadBlob(query: SqlQuery; id: int): BlobReader = +proc loadBlob(g; query: SqlQuery; id: int): BlobReader = let blob = db.getValue(query, id) if blob.len == 0: - internalError("symbolfiles: cannot find ID " & $ id) + internalError(g.config, "symbolfiles: cannot find ID " & $ id) result = BlobReader(pos: 0) shallowCopy(result.s, blob) + # ensure we can read without index checks: + result.s.add '\0' -proc loadType(r; id: int; info: TLineInfo): PType = - result = r.types.getOrDefault(id) +proc loadType(g; id: int; info: TLineInfo): PType = + result = g.incr.r.types.getOrDefault(id) if result != nil: return result - var b = loadBlob(sql"select data from types where nimid = ?", id) + var b = loadBlob(g, sql"select data from types where nimid = ?", id) if b.s[b.pos] == '[': inc(b.pos) @@ -611,10 +564,10 @@ proc loadType(r; id: int; info: TLineInfo): PType = setId(result.id) #if debugIds: registerID(result) else: - internalError(info, "decodeType: no id") + internalError(g.config, info, "decodeType: no id") # here this also avoids endless recursion for recursive type - r.types[result.id] = result - if b.s[b.pos] == '(': result.n = decodeNode(r, b, unknownLineInfo()) + g.incr.r.types.add(result.id, result) + if b.s[b.pos] == '(': result.n = decodeNode(g, b, unknownLineInfo()) if b.s[b.pos] == '$': inc(b.pos) result.flags = cast[TTypeFlags](int32(decodeVInt(b.s, b.pos))) @@ -623,10 +576,10 @@ proc loadType(r; id: int; info: TLineInfo): PType = result.callConv = TCallingConvention(decodeVInt(b.s, b.pos)) if b.s[b.pos] == '*': inc(b.pos) - result.owner = loadSym(r, decodeVInt(b.s, b.pos), info) + result.owner = loadSym(g, decodeVInt(b.s, b.pos), info) if b.s[b.pos] == '&': inc(b.pos) - result.sym = loadSym(r, decodeVInt(b.s, b.pos), info) + result.sym = loadSym(g, decodeVInt(b.s, b.pos), info) if b.s[b.pos] == '/': inc(b.pos) result.size = decodeVInt(b.s, b.pos) @@ -646,65 +599,65 @@ proc loadType(r; id: int; info: TLineInfo): PType = if b.s[b.pos] == '\15': inc(b.pos) - result.destructor = loadSym(r, decodeVInt(b.s, b.pos), info) + result.destructor = loadSym(g, decodeVInt(b.s, b.pos), info) if b.s[b.pos] == '\16': inc(b.pos) - result.deepCopy = loadSym(r, decodeVInt(b.s, b.pos), info) + result.deepCopy = loadSym(g, decodeVInt(b.s, b.pos), info) if b.s[b.pos] == '\17': inc(b.pos) - result.assignment = loadSym(r, decodeVInt(b.s, b.pos), info) + result.assignment = loadSym(g, decodeVInt(b.s, b.pos), info) if b.s[b.pos] == '\18': inc(b.pos) - result.sink = loadSym(r, decodeVInt(b.s, b.pos), info) + result.sink = loadSym(g, decodeVInt(b.s, b.pos), info) while b.s[b.pos] == '\19': inc(b.pos) let x = decodeVInt(b.s, b.pos) doAssert b.s[b.pos] == '\20' inc(b.pos) - let y = loadSym(r, decodeVInt(b.s, b.pos), info) + let y = loadSym(g, decodeVInt(b.s, b.pos), info) result.methods.safeAdd((x, y)) - decodeLoc(r, b, result.loc, info) + decodeLoc(g, b, result.loc, info) while b.s[b.pos] == '^': inc(b.pos) if b.s[b.pos] == '(': inc(b.pos) if b.s[b.pos] == ')': inc(b.pos) - else: internalError(info, "decodeType ^(" & b.s[b.pos]) + else: internalError(g.config, info, "decodeType ^(" & b.s[b.pos]) rawAddSon(result, nil) else: - var d = decodeVInt(b.s, b.pos) - rawAddSon(result, loadType(r, d, info)) + let d = decodeVInt(b.s, b.pos) + rawAddSon(result, loadType(g, d, info)) -proc decodeLib(r; b; info: TLineInfo): PLib = +proc decodeLib(g; b; info: TLineInfo): PLib = result = nil if b.s[b.pos] == '|': new(result) inc(b.pos) result.kind = TLibKind(decodeVInt(b.s, b.pos)) - if b.s[b.pos] != '|': internalError("decodeLib: 1") + if b.s[b.pos] != '|': internalError(g.config, "decodeLib: 1") inc(b.pos) result.name = rope(decodeStr(b.s, b.pos)) - if b.s[b.pos] != '|': internalError("decodeLib: 2") + if b.s[b.pos] != '|': internalError(g.config, "decodeLib: 2") inc(b.pos) - result.path = decodeNode(r, b, info) + result.path = decodeNode(g, b, info) -proc decodeInstantiations(r; b; info: TLineInfo; +proc decodeInstantiations(g; b; info: TLineInfo; s: var seq[PInstantiation]) = while b.s[b.pos] == '\15': inc(b.pos) var ii: PInstantiation new ii - ii.sym = loadSym(r, decodeVInt(b.s, b.pos), info) + ii.sym = loadSym(g, decodeVInt(b.s, b.pos), info) ii.concreteTypes = @[] while b.s[b.pos] == '\17': inc(b.pos) - ii.concreteTypes.add loadType(r, decodeVInt(b.s, b.pos), info) + ii.concreteTypes.add loadType(g, decodeVInt(b.s, b.pos), info) if b.s[b.pos] == '\20': inc(b.pos) ii.compilesId = decodeVInt(b.s, b.pos) s.safeAdd ii -proc loadSymFromBlob(r; b; info: TLineInfo): PSym = +proc loadSymFromBlob(g; b; info: TLineInfo): PSym = if b.s[b.pos] == '{': inc(b.pos) if b.s[b.pos] == '}': @@ -717,26 +670,26 @@ proc loadSymFromBlob(r; b; info: TLineInfo): PSym = id = decodeVInt(b.s, b.pos) setId(id) else: - internalError(info, "decodeSym: no id") + internalError(g.config, info, "decodeSym: no id") var ident: PIdent if b.s[b.pos] == '&': inc(b.pos) - ident = r.cache.getIdent(decodeStr(b.s, b.pos)) + ident = g.cache.getIdent(decodeStr(b.s, b.pos)) else: - internalError(info, "decodeSym: no ident") + internalError(g.config, info, "decodeSym: no ident") #echo "decoding: {", ident.s new(result) result.id = id result.kind = k result.name = ident # read the rest of the symbol description: - r.syms[result.id] = result + g.incr.r.syms.add(result.id, result) if b.s[b.pos] == '^': inc(b.pos) - result.typ = loadType(r, decodeVInt(b.s, b.pos), info) - decodeLineInfo(r, b, result.info) + result.typ = loadType(g, decodeVInt(b.s, b.pos), info) + decodeLineInfo(g, b, result.info) if b.s[b.pos] == '*': inc(b.pos) - result.owner = loadSym(r, decodeVInt(b.s, b.pos), result.info) + result.owner = loadSym(g, decodeVInt(b.s, b.pos), result.info) if b.s[b.pos] == '$': inc(b.pos) result.flags = cast[TSymFlags](int32(decodeVInt(b.s, b.pos))) @@ -746,8 +699,6 @@ proc loadSymFromBlob(r; b; info: TLineInfo): PSym = if b.s[b.pos] == '!': inc(b.pos) result.options = cast[TOptions](int32(decodeVInt(b.s, b.pos))) - else: - result.options = r.module.options if b.s[b.pos] == '%': inc(b.pos) result.position = decodeVInt(b.s, b.pos) @@ -755,28 +706,28 @@ proc loadSymFromBlob(r; b; info: TLineInfo): PSym = inc(b.pos) result.offset = decodeVInt(b.s, b.pos) else: - result.offset = - 1 - decodeLoc(r, b, result.loc, result.info) - result.annex = decodeLib(r, b, info) + result.offset = -1 + decodeLoc(g, b, result.loc, result.info) + result.annex = decodeLib(g, b, info) if b.s[b.pos] == '#': inc(b.pos) - result.constraint = decodeNode(r, b, unknownLineInfo()) + result.constraint = decodeNode(g, b, unknownLineInfo()) case result.kind of skType, skGenericParam: while b.s[b.pos] == '\14': inc(b.pos) - result.typeInstCache.safeAdd loadType(r, decodeVInt(b.s, b.pos), result.info) + result.typeInstCache.safeAdd loadType(g, decodeVInt(b.s, b.pos), result.info) of routineKinds: - decodeInstantiations(r, b, result.info, result.procInstCache) + decodeInstantiations(g, b, result.info, result.procInstCache) if b.s[b.pos] == '\16': inc(b.pos) - result.gcUnsafetyReason = loadSym(r, decodeVInt(b.s, b.pos), result.info) + result.gcUnsafetyReason = loadSym(g, decodeVInt(b.s, b.pos), result.info) of skModule, skPackage: - decodeInstantiations(r, b, result.info, result.usedGenerics) + decodeInstantiations(g, b, result.info, result.usedGenerics) of skLet, skVar, skField, skForVar: if b.s[b.pos] == '\18': inc(b.pos) - result.guard = loadSym(r, decodeVInt(b.s, b.pos), result.info) + result.guard = loadSym(g, decodeVInt(b.s, b.pos), result.info) if b.s[b.pos] == '\19': inc(b.pos) result.bitsize = decodeVInt(b.s, b.pos).int16 @@ -786,159 +737,146 @@ proc loadSymFromBlob(r; b; info: TLineInfo): PSym = #if result.kind in routineKinds: # result.ast = decodeNodeLazyBody(b, result.info, result) #else: - result.ast = decodeNode(r, b, result.info) + result.ast = decodeNode(g, b, result.info) if sfCompilerProc in result.flags: - registerCompilerProc(result) + registerCompilerProc(g, result) #echo "loading ", result.name.s -proc loadSym(r; id: int; info: TLineInfo): PSym = - result = r.syms.getOrDefault(id) +proc loadSym(g; id: int; info: TLineInfo): PSym = + result = g.incr.r.syms.getOrDefault(id) if result != nil: return result - var b = loadBlob(sql"select data from syms where nimid = ?", id) - result = loadSymFromBlob(r, b, info) + var b = loadBlob(g, sql"select data from syms where nimid = ?", id) + result = loadSymFromBlob(g, b, info) doAssert id == result.id, "symbol ID is not consistent!" -proc loadModuleSymTab(r; module: PSym) = +proc loadModuleSymTab(g; module: PSym) = ## goal: fill module.tab - gr.syms[module.id] = module + g.incr.r.syms.add(module.id, module) for row in db.fastRows(sql"select nimid, data from syms where module = ? and exported = 1", abs(module.id)): let id = parseInt(row[0]) - var s = r.syms.getOrDefault(id) + var s = g.incr.r.syms.getOrDefault(id) if s == nil: var b = BlobReader(pos: 0) shallowCopy(b.s, row[1]) - s = loadSymFromBlob(r, b, module.info) + # ensure we can read without index checks: + b.s.add '\0' + s = loadSymFromBlob(g, b, module.info) assert s != nil strTableAdd(module.tab, s) if sfSystemModule in module.flags: - magicsys.systemModule = module - -proc loadNode*(module: PSym; index: int): PNode = - assert gSymbolFiles == v2Sf - if index == 0: - loadModuleSymTab(gr, module) - #index = parseInt db.getValue( - # sql"select min(id) from toplevelstmts where module = ?", abs module.id) - var b = BlobReader(pos: 0) - b.s = db.getValue(sql"select data from toplevelstmts where position = ? and module = ?", - index, abs module.id) - if b.s.len == 0: - db.exec(sql"insert into controlblock(idgen) values (?)", gFrontEndId) - return nil # end marker - gr.module = module - result = decodeNode(gr, b, module.info) - -proc addModuleDep*(module, fileIdx: int32; isIncludeFile: bool) = - if gSymbolFiles != v2Sf: return - - let a = toDbFileId(module) - let b = toDbFileId(fileIdx) - - db.exec(sql"insert into deps(module, dependency, isIncludeFile) values (?, ?, ?)", - a, b, ord(isIncludeFile)) - -# --------------- Database model --------------------------------------------- - -proc createDb() = - db.exec(sql""" - create table if not exists controlblock( - idgen integer not null - ); - """) - - db.exec(sql""" - create table if not exists filenames( - id integer primary key, - fullpath varchar(8000) not null, - fullHash varchar(256) not null - ); - """) - db.exec sql"create index if not exists FilenameIx on filenames(fullpath);" - - db.exec(sql""" - create table if not exists modules( - id integer primary key, - fullpath varchar(8000) not null, - interfHash varchar(256) not null, - fullHash varchar(256) not null, - - created timestamp not null default (DATETIME('now')) - );""") - db.exec(sql"""create unique index if not exists SymNameIx on modules(fullpath);""") - - db.exec(sql""" - create table if not exists deps( - id integer primary key, - module integer not null, - dependency integer not null, - isIncludeFile integer not null, - foreign key (module) references filenames(id), - foreign key (dependency) references filenames(id) - );""") - db.exec(sql"""create index if not exists DepsIx on deps(module);""") - - db.exec(sql""" - create table if not exists types( - id integer primary key, - nimid integer not null, - module integer not null, - data blob not null, - foreign key (module) references module(id) - ); - """) - db.exec sql"create index TypeByModuleIdx on types(module);" - db.exec sql"create index TypeByNimIdIdx on types(nimid);" - - db.exec(sql""" - create table if not exists syms( - id integer primary key, - nimid integer not null, - module integer not null, - name varchar(256) not null, - data blob not null, - exported int not null, - foreign key (module) references module(id) - ); - """) - db.exec sql"create index if not exists SymNameIx on syms(name);" - db.exec sql"create index SymByNameAndModuleIdx on syms(name, module);" - db.exec sql"create index SymByModuleIdx on syms(module);" - db.exec sql"create index SymByNimIdIdx on syms(nimid);" - - - db.exec(sql""" - create table if not exists toplevelstmts( - id integer primary key, - position integer not null, - module integer not null, - data blob not null, - foreign key (module) references module(id) - ); - """) - db.exec sql"create index TopLevelStmtByModuleIdx on toplevelstmts(module);" - db.exec sql"create index TopLevelStmtByPositionIdx on toplevelstmts(position);" - - db.exec(sql""" - create table if not exists statics( - id integer primary key, - module integer not null, - data blob not null, - foreign key (module) references module(id) - ); - """) - db.exec sql"create index StaticsByModuleIdx on toplevelstmts(module);" - db.exec sql"insert into controlblock(idgen) values (0)" - -proc setupModuleCache* = - if gSymbolFiles != v2Sf: return - let dbfile = getNimcacheDir() / "rodfiles.db" + g.systemModule = module + +proc replay(g: ModuleGraph; module: PSym; n: PNode) = + # XXX check if we need to replay nkStaticStmt here. + case n.kind + #of nkStaticStmt: + #evalStaticStmt(module, g, n[0], module) + #of nkVarSection, nkLetSection: + # nkVarSections are already covered by the vmgen which produces nkStaticStmt + of nkMethodDef: + methodDef(g, n[namePos].sym, fromCache=true) + of nkCommentStmt: + # pragmas are complex and can be user-overriden via templates. So + # instead of using the original ``nkPragma`` nodes, we rely on the + # fact that pragmas.nim was patched to produce specialized recorded + # statements for us in the form of ``nkCommentStmt`` with (key, value) + # pairs. Ordinary nkCommentStmt nodes never have children so this is + # not ambiguous. + # Fortunately only a tiny subset of the available pragmas need to + # be replayed here. This is always a subset of ``pragmas.stmtPragmas``. + if n.len >= 2: + internalAssert g.config, n[0].kind == nkStrLit and n[1].kind == nkStrLit + case n[0].strVal + of "hint": message(g.config, n.info, hintUser, n[1].strVal) + of "warning": message(g.config, n.info, warnUser, n[1].strVal) + of "error": localError(g.config, n.info, errUser, n[1].strVal) + of "compile": + internalAssert g.config, n.len == 3 and n[2].kind == nkStrLit + var cf = Cfile(cname: n[1].strVal, obj: n[2].strVal, + flags: {CfileFlag.External}) + extccomp.addExternalFileToCompile(g.config, cf) + of "link": + extccomp.addExternalFileToLink(g.config, n[1].strVal) + of "passl": + extccomp.addLinkOption(g.config, n[1].strVal) + of "passc": + extccomp.addCompileOption(g.config, n[1].strVal) + of "cppdefine": + options.cppDefine(g.config, n[1].strVal) + of "inc": + let destKey = n[1].strVal + let by = n[2].intVal + let v = getOrDefault(g.cacheCounters, destKey) + g.cacheCounters[destKey] = v+by + of "put": + let destKey = n[1].strVal + let key = n[2].strVal + let val = n[3] + if not contains(g.cacheTables, destKey): + g.cacheTables[destKey] = initBTree[string, PNode]() + if not contains(g.cacheTables[destKey], key): + g.cacheTables[destKey].add(key, val) + else: + internalError(g.config, n.info, "key already exists: " & key) + of "incl": + let destKey = n[1].strVal + let val = n[2] + if not contains(g.cacheSeqs, destKey): + g.cacheSeqs[destKey] = newTree(nkStmtList, val) + else: + block search: + for existing in g.cacheSeqs[destKey]: + if exprStructuralEquivalent(existing, val, strictSymEquality=true): + break search + g.cacheSeqs[destKey].add val + of "add": + let destKey = n[1].strVal + let val = n[2] + if not contains(g.cacheSeqs, destKey): + g.cacheSeqs[destKey] = newTree(nkStmtList, val) + else: + g.cacheSeqs[destKey].add val + else: + internalAssert g.config, false + of nkImportStmt: + for x in n: + internalAssert g.config, x.kind == nkStrLit + let imported = g.importModuleCallback(g, module, fileInfoIdx(g.config, n[0].strVal)) + internalAssert g.config, imported.id < 0 + of nkStmtList, nkStmtListExpr: + for x in n: replay(g, module, x) + else: discard "nothing to do for this node" + +proc loadNode*(g: ModuleGraph; module: PSym): PNode = + loadModuleSymTab(g, module) + result = newNodeI(nkStmtList, module.info) + for row in db.rows(sql"select data from toplevelstmts where module = ? order by position asc", + abs module.id): + + var b = BlobReader(pos: 0) + # ensure we can read without index checks: + b.s = row[0] & '\0' + result.add decodeNode(g, b, module.info) + + db.exec(sql"insert into controlblock(idgen) values (?)", gFrontEndId) + replay(g, module, result) + +proc setupModuleCache*(g: ModuleGraph) = + if g.config.symbolFiles == disabledSf: return + g.recordStmt = recordStmt + let dbfile = getNimcacheDir(g.config) / "rodfiles.db" + if g.config.symbolFiles == writeOnlySf: + removeFile(dbfile) if not fileExists(dbfile): db = open(connection=dbfile, user="nim", password="", database="nim") - createDb() + createDb(db) + db.exec(sql"insert into config(config) values (?)", encodeConfig(g)) else: db = open(connection=dbfile, user="nim", password="", database="nim") + let oldConfig = db.getValue(sql"select config from config") + g.incr.configChanged = oldConfig != encodeConfig(g) db.exec(sql"pragma journal_mode=off") db.exec(sql"pragma SYNCHRONOUS=off") db.exec(sql"pragma LOCKING_MODE=exclusive") diff --git a/compiler/rodread.nim b/compiler/rodread.nim deleted file mode 100644 index 84f175cb5..000000000 --- a/compiler/rodread.nim +++ /dev/null @@ -1,1240 +0,0 @@ -# -# -# The Nim Compiler -# (c) Copyright 2013 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -# This module is responsible for loading of rod files. -# -# Reading and writing binary files are really hard to debug. Therefore we use -# a "creative" text/binary hybrid format. ROD-files are more efficient -# to process because symbols can be loaded on demand. -# -# A ROD file consists of: -# -# - a header: -# NIM:$fileversion\n -# - the module's id (even if the module changed, its ID will not!): -# ID:Ax3\n -# - HASH value of this module: -# HASH:HASH-val\n -# - a section containing the compiler options and defines this -# module has been compiled with: -# OPTIONS:options\n -# GOPTIONS:options\n # global options -# CMD:command\n -# DEFINES:defines\n -# - FILES( -# myfile.inc -# lib/mymodA -# ) -# - an include file dependency section: -# INCLUDES( -# <fileidx> <Hash of myfile.inc>\n # fileidx is the LINE in the file section! -# ) -# - a module dependency section: -# DEPS: <fileidx> <fileidx>\n -# - an interface section: -# INTERF( -# identifier1 id\n # id is the symbol's id -# identifier2 id\n -# ) -# - a compiler proc section: -# COMPILERPROCS( -# identifier1 id\n # id is the symbol's id -# ) -# - an index consisting of (ID, linenumber)-pairs: -# INDEX( -# id-diff idx-diff\n -# id-diff idx-diff\n -# ) -# -# Since the whole index has to be read in advance, we compress it by -# storing the integer differences to the last entry instead of using the -# real numbers. -# -# - an import index consisting of (ID, moduleID)-pairs: -# IMPORTS( -# id-diff moduleID-diff\n -# id-diff moduleID-diff\n -# ) -# - a list of all exported type converters because they are needed for correct -# semantic checking: -# CONVERTERS:id id\n # symbol ID -# -# This is a misnomer now; it's really a "load unconditionally" section as -# it is also used for pattern templates. -# -# - a list of all (private or exported) methods because they are needed for -# correct dispatcher generation: -# METHODS: id id\n # symbol ID -# - an AST section that contains the module's AST: -# INIT( -# idx\n # position of the node in the DATA section -# idx\n -# ) -# - a data section, where each type, symbol or AST is stored. -# DATA( -# type -# (node) -# sym -# ) -# -# The data section MUST be the last section of the file, because processing -# stops immediately after ``DATA(`` and the rest is only loaded on demand -# by using a mem'mapped file. -# - -import - os, options, strutils, nversion, ast, astalgo, msgs, platform, condsyms, - ropes, idents, std / sha1, idgen, types, rodutils, memfiles, tables - -type - TReasonForRecompile* = enum ## all the reasons that can trigger recompilation - rrEmpty, # dependencies not yet computed - rrNone, # no need to recompile - rrRodDoesNotExist, # rod file does not exist - rrRodInvalid, # rod file is invalid - rrHashChange, # file has been edited since last recompilation - rrDefines, # defines have changed - rrOptions, # options have changed - rrInclDeps, # an include has changed - rrModDeps # a module this module depends on has been changed - -const - reasonToFrmt*: array[TReasonForRecompile, string] = ["", - "no need to recompile: $1", "symbol file for $1 does not exist", - "symbol file for $1 has the wrong version", - "file edited since last compilation: $1", - "list of conditional symbols changed for: $1", - "list of options changed for: $1", - "an include file edited: $1", - "a module $1 depends on has changed"] - -type - TIndex*{.final.} = object # an index with compression - lastIdxKey*, lastIdxVal*: int - tab*: TIITable - r*: string # writers use this - offset*: int # readers use this - - TRodReader* = object of RootObj - pos: int # position; used for parsing - s: cstring # mmap'ed file contents - options: TOptions - reason: TReasonForRecompile - modDeps: seq[int32] - files: seq[int32] - dataIdx: int # offset of start of data section - convertersIdx: int # offset of start of converters section - initIdx, interfIdx, compilerProcsIdx, methodsIdx: int - filename: string - index, imports: TIndex - readerIndex: int - line: int # only used for debugging, but is always in the code - moduleID: int - syms: Table[int, PSym] # already processed symbols - memfile: MemFile # unfortunately there is no point in time where we - # can close this! XXX - methods*: TSymSeq - origFile: string - inViewMode: bool - cache*: IdentCache - - PRodReader* = ref TRodReader - -var rodCompilerprocs*: TStrTable # global because this is needed by magicsys - -proc rawLoadStub(s: PSym) - -var gTypeTable: TIdTable - -proc rrGetSym(r: PRodReader, id: int, info: TLineInfo): PSym - # `info` is only used for debugging purposes -proc rrGetType(r: PRodReader, id: int, info: TLineInfo): PType - -proc decodeLineInfo(r: PRodReader, info: var TLineInfo) = - if r.s[r.pos] == '?': - inc(r.pos) - if r.s[r.pos] == ',': info.col = -1'i16 - else: info.col = int16(decodeVInt(r.s, r.pos)) - if r.s[r.pos] == ',': - inc(r.pos) - if r.s[r.pos] == ',': info.line = -1'i16 - else: info.line = int16(decodeVInt(r.s, r.pos)) - if r.s[r.pos] == ',': - inc(r.pos) - info = newLineInfo(r.files[decodeVInt(r.s, r.pos)], info.line, info.col) - -proc skipNode(r: PRodReader) = - assert r.s[r.pos] == '(' - var par = 0 - var pos = r.pos+1 - while true: - case r.s[pos] - of ')': - if par == 0: break - dec par - of '(': inc par - else: discard - inc pos - r.pos = pos+1 # skip ')' - -proc decodeNodeLazyBody(r: PRodReader, fInfo: TLineInfo, - belongsTo: PSym): PNode = - result = nil - if r.s[r.pos] == '(': - inc(r.pos) - if r.s[r.pos] == ')': - inc(r.pos) - return # nil node - result = newNodeI(TNodeKind(decodeVInt(r.s, r.pos)), fInfo) - decodeLineInfo(r, result.info) - if r.s[r.pos] == '$': - inc(r.pos) - result.flags = cast[TNodeFlags](int32(decodeVInt(r.s, r.pos))) - if r.s[r.pos] == '^': - inc(r.pos) - var id = decodeVInt(r.s, r.pos) - result.typ = rrGetType(r, id, result.info) - case result.kind - of nkCharLit..nkUInt64Lit: - if r.s[r.pos] == '!': - inc(r.pos) - result.intVal = decodeVBiggestInt(r.s, r.pos) - of nkFloatLit..nkFloat64Lit: - if r.s[r.pos] == '!': - inc(r.pos) - var fl = decodeStr(r.s, r.pos) - result.floatVal = parseFloat(fl) - of nkStrLit..nkTripleStrLit: - if r.s[r.pos] == '!': - inc(r.pos) - result.strVal = decodeStr(r.s, r.pos) - else: - result.strVal = "" # BUGFIX - of nkIdent: - if r.s[r.pos] == '!': - inc(r.pos) - var fl = decodeStr(r.s, r.pos) - result.ident = r.cache.getIdent(fl) - else: - internalError(result.info, "decodeNode: nkIdent") - of nkSym: - if r.s[r.pos] == '!': - inc(r.pos) - var id = decodeVInt(r.s, r.pos) - result.sym = rrGetSym(r, id, result.info) - else: - internalError(result.info, "decodeNode: nkSym") - else: - var i = 0 - while r.s[r.pos] != ')': - if belongsTo != nil and i == bodyPos: - addSonNilAllowed(result, nil) - belongsTo.offset = r.pos - skipNode(r) - else: - addSonNilAllowed(result, decodeNodeLazyBody(r, result.info, nil)) - inc i - if r.s[r.pos] == ')': inc(r.pos) - else: internalError(result.info, "decodeNode: ')' missing") - else: - internalError(fInfo, "decodeNode: '(' missing " & $r.pos) - -proc decodeNode(r: PRodReader, fInfo: TLineInfo): PNode = - result = decodeNodeLazyBody(r, fInfo, nil) - -proc decodeLoc(r: PRodReader, loc: var TLoc, info: TLineInfo) = - if r.s[r.pos] == '<': - inc(r.pos) - if r.s[r.pos] in {'0'..'9', 'a'..'z', 'A'..'Z'}: - loc.k = TLocKind(decodeVInt(r.s, r.pos)) - else: - loc.k = low(loc.k) - if r.s[r.pos] == '*': - inc(r.pos) - loc.storage = TStorageLoc(decodeVInt(r.s, r.pos)) - else: - loc.storage = low(loc.storage) - if r.s[r.pos] == '$': - inc(r.pos) - loc.flags = cast[TLocFlags](int32(decodeVInt(r.s, r.pos))) - else: - loc.flags = {} - if r.s[r.pos] == '^': - inc(r.pos) - loc.lode = decodeNode(r, info) - # rrGetType(r, decodeVInt(r.s, r.pos), info) - else: - loc.lode = nil - if r.s[r.pos] == '!': - inc(r.pos) - loc.r = rope(decodeStr(r.s, r.pos)) - else: - loc.r = nil - if r.s[r.pos] == '>': inc(r.pos) - else: internalError(info, "decodeLoc " & r.s[r.pos]) - -proc decodeType(r: PRodReader, info: TLineInfo): PType = - result = nil - if r.s[r.pos] == '[': - inc(r.pos) - if r.s[r.pos] == ']': - inc(r.pos) - return # nil type - new(result) - result.kind = TTypeKind(decodeVInt(r.s, r.pos)) - if r.s[r.pos] == '+': - inc(r.pos) - result.id = decodeVInt(r.s, r.pos) - setId(result.id) - if debugIds: registerID(result) - else: - internalError(info, "decodeType: no id") - # here this also avoids endless recursion for recursive type - idTablePut(gTypeTable, result, result) - if r.s[r.pos] == '(': result.n = decodeNode(r, unknownLineInfo()) - if r.s[r.pos] == '$': - inc(r.pos) - result.flags = cast[TTypeFlags](int32(decodeVInt(r.s, r.pos))) - if r.s[r.pos] == '?': - inc(r.pos) - result.callConv = TCallingConvention(decodeVInt(r.s, r.pos)) - if r.s[r.pos] == '*': - inc(r.pos) - result.owner = rrGetSym(r, decodeVInt(r.s, r.pos), info) - if r.s[r.pos] == '&': - inc(r.pos) - result.sym = rrGetSym(r, decodeVInt(r.s, r.pos), info) - if r.s[r.pos] == '/': - inc(r.pos) - result.size = decodeVInt(r.s, r.pos) - else: - result.size = - 1 - if r.s[r.pos] == '=': - inc(r.pos) - result.align = decodeVInt(r.s, r.pos).int16 - else: - result.align = 2 - - if r.s[r.pos] == '\14': - inc(r.pos) - result.lockLevel = decodeVInt(r.s, r.pos).TLockLevel - else: - result.lockLevel = UnspecifiedLockLevel - - if r.s[r.pos] == '\15': - inc(r.pos) - result.destructor = rrGetSym(r, decodeVInt(r.s, r.pos), info) - if r.s[r.pos] == '\16': - inc(r.pos) - result.deepCopy = rrGetSym(r, decodeVInt(r.s, r.pos), info) - if r.s[r.pos] == '\17': - inc(r.pos) - result.assignment = rrGetSym(r, decodeVInt(r.s, r.pos), info) - if r.s[r.pos] == '\18': - inc(r.pos) - result.sink = rrGetSym(r, decodeVInt(r.s, r.pos), info) - while r.s[r.pos] == '\19': - inc(r.pos) - let x = decodeVInt(r.s, r.pos) - doAssert r.s[r.pos] == '\20' - inc(r.pos) - let y = rrGetSym(r, decodeVInt(r.s, r.pos), info) - result.methods.safeAdd((x, y)) - decodeLoc(r, result.loc, info) - while r.s[r.pos] == '^': - inc(r.pos) - if r.s[r.pos] == '(': - inc(r.pos) - if r.s[r.pos] == ')': inc(r.pos) - else: internalError(info, "decodeType ^(" & r.s[r.pos]) - rawAddSon(result, nil) - else: - var d = decodeVInt(r.s, r.pos) - rawAddSon(result, rrGetType(r, d, info)) - -proc decodeLib(r: PRodReader, info: TLineInfo): PLib = - result = nil - if r.s[r.pos] == '|': - new(result) - inc(r.pos) - result.kind = TLibKind(decodeVInt(r.s, r.pos)) - if r.s[r.pos] != '|': internalError("decodeLib: 1") - inc(r.pos) - result.name = rope(decodeStr(r.s, r.pos)) - if r.s[r.pos] != '|': internalError("decodeLib: 2") - inc(r.pos) - result.path = decodeNode(r, info) - -proc decodeInstantiations(r: PRodReader; info: TLineInfo; - s: var seq[PInstantiation]) = - while r.s[r.pos] == '\15': - inc(r.pos) - var ii: PInstantiation - new ii - ii.sym = rrGetSym(r, decodeVInt(r.s, r.pos), info) - ii.concreteTypes = @[] - while r.s[r.pos] == '\17': - inc(r.pos) - ii.concreteTypes.add rrGetType(r, decodeVInt(r.s, r.pos), info) - if r.s[r.pos] == '\20': - inc(r.pos) - ii.compilesId = decodeVInt(r.s, r.pos) - s.safeAdd ii - -proc decodeSym(r: PRodReader, info: TLineInfo): PSym = - var - id: int - ident: PIdent - result = nil - if r.s[r.pos] == '{': - inc(r.pos) - if r.s[r.pos] == '}': - inc(r.pos) - return # nil sym - var k = TSymKind(decodeVInt(r.s, r.pos)) - if r.s[r.pos] == '+': - inc(r.pos) - id = decodeVInt(r.s, r.pos) - setId(id) - else: - internalError(info, "decodeSym: no id") - if r.s[r.pos] == '&': - inc(r.pos) - ident = r.cache.getIdent(decodeStr(r.s, r.pos)) - else: - internalError(info, "decodeSym: no ident") - #echo "decoding: {", ident.s - result = r.syms.getOrDefault(id) - if result == nil: - new(result) - result.id = id - r.syms[result.id] = result - if debugIds: registerID(result) - elif result.id != id: - internalError(info, "decodeSym: wrong id") - elif result.kind != skStub and not r.inViewMode: - # we already loaded the symbol - return - else: - reset(result[]) - result.id = id - result.kind = k - result.name = ident # read the rest of the symbol description: - if r.s[r.pos] == '^': - inc(r.pos) - result.typ = rrGetType(r, decodeVInt(r.s, r.pos), info) - decodeLineInfo(r, result.info) - if r.s[r.pos] == '*': - inc(r.pos) - result.owner = rrGetSym(r, decodeVInt(r.s, r.pos), result.info) - if r.s[r.pos] == '$': - inc(r.pos) - result.flags = cast[TSymFlags](int32(decodeVInt(r.s, r.pos))) - if r.s[r.pos] == '@': - inc(r.pos) - result.magic = TMagic(decodeVInt(r.s, r.pos)) - if r.s[r.pos] == '!': - inc(r.pos) - result.options = cast[TOptions](int32(decodeVInt(r.s, r.pos))) - else: - result.options = r.options - if r.s[r.pos] == '%': - inc(r.pos) - result.position = decodeVInt(r.s, r.pos) - elif result.kind notin routineKinds + {skModule}: - result.position = 0 - # this may have been misused as reader index! But we still - # need it for routines as the body is loaded lazily. - if r.s[r.pos] == '`': - inc(r.pos) - result.offset = decodeVInt(r.s, r.pos) - else: - result.offset = - 1 - decodeLoc(r, result.loc, result.info) - result.annex = decodeLib(r, info) - if r.s[r.pos] == '#': - inc(r.pos) - result.constraint = decodeNode(r, unknownLineInfo()) - case result.kind - of skType, skGenericParam: - while r.s[r.pos] == '\14': - inc(r.pos) - result.typeInstCache.safeAdd rrGetType(r, decodeVInt(r.s, r.pos), result.info) - of routineKinds: - decodeInstantiations(r, result.info, result.procInstCache) - if r.s[r.pos] == '\16': - inc(r.pos) - result.gcUnsafetyReason = rrGetSym(r, decodeVInt(r.s, r.pos), result.info) - of skModule, skPackage: - decodeInstantiations(r, result.info, result.usedGenerics) - of skLet, skVar, skField, skForVar: - if r.s[r.pos] == '\18': - inc(r.pos) - result.guard = rrGetSym(r, decodeVInt(r.s, r.pos), result.info) - if r.s[r.pos] == '\19': - inc(r.pos) - result.bitsize = decodeVInt(r.s, r.pos).int16 - else: discard - - if r.s[r.pos] == '(': - if result.kind in routineKinds: - result.ast = decodeNodeLazyBody(r, result.info, result) - # since we load the body lazily, we need to set the reader to - # be able to reload: - result.position = r.readerIndex - else: - result.ast = decodeNode(r, result.info) - #echo "decoded: ", ident.s, "}" - -proc skipSection(r: PRodReader) = - if r.s[r.pos] == ':': - while r.s[r.pos] > '\x0A': inc(r.pos) - elif r.s[r.pos] == '(': - var c = 0 # count () pairs - inc(r.pos) - while true: - case r.s[r.pos] - of '\x0A': inc(r.line) - of '(': inc(c) - of ')': - if c == 0: - inc(r.pos) - break - elif c > 0: - dec(c) - of '\0': break # end of file - else: discard - inc(r.pos) - else: - internalError("skipSection " & $r.line) - -proc rdWord(r: PRodReader): string = - result = "" - while r.s[r.pos] in {'A'..'Z', '_', 'a'..'z', '0'..'9'}: - add(result, r.s[r.pos]) - inc(r.pos) - -proc newStub(r: PRodReader, name: string, id: int): PSym = - new(result) - result.kind = skStub - result.id = id - result.name = r.cache.getIdent(name) - result.position = r.readerIndex - setId(id) #MessageOut(result.name.s); - if debugIds: registerID(result) - -proc processInterf(r: PRodReader, module: PSym) = - if r.interfIdx == 0: internalError("processInterf") - r.pos = r.interfIdx - while (r.s[r.pos] > '\x0A') and (r.s[r.pos] != ')'): - var w = decodeStr(r.s, r.pos) - inc(r.pos) - var key = decodeVInt(r.s, r.pos) - inc(r.pos) # #10 - var s = newStub(r, w, key) - s.owner = module - strTableAdd(module.tab, s) - r.syms[s.id] = s - -proc processCompilerProcs(r: PRodReader, module: PSym) = - if r.compilerProcsIdx == 0: internalError("processCompilerProcs") - r.pos = r.compilerProcsIdx - while (r.s[r.pos] > '\x0A') and (r.s[r.pos] != ')'): - var w = decodeStr(r.s, r.pos) - inc(r.pos) - var key = decodeVInt(r.s, r.pos) - inc(r.pos) # #10 - var s = r.syms.getOrDefault(key) - if s == nil: - s = newStub(r, w, key) - s.owner = module - r.syms[s.id] = s - strTableAdd(rodCompilerprocs, s) - -proc processIndex(r: PRodReader; idx: var TIndex; outf: File = nil) = - var key, val, tmp: int - inc(r.pos, 2) # skip "(\10" - inc(r.line) - while (r.s[r.pos] > '\x0A') and (r.s[r.pos] != ')'): - tmp = decodeVInt(r.s, r.pos) - if r.s[r.pos] == ' ': - inc(r.pos) - key = idx.lastIdxKey + tmp - val = decodeVInt(r.s, r.pos) + idx.lastIdxVal - else: - key = idx.lastIdxKey + 1 - val = tmp + idx.lastIdxVal - iiTablePut(idx.tab, key, val) - if not outf.isNil: outf.write(key, " ", val, "\n") - idx.lastIdxKey = key - idx.lastIdxVal = val - setId(key) # ensure that this id will not be used - if r.s[r.pos] == '\x0A': - inc(r.pos) - inc(r.line) - if r.s[r.pos] == ')': inc(r.pos) - -proc cmdChangeTriggersRecompilation(old, new: TCommands): bool = - if old == new: return false - # we use a 'case' statement without 'else' so that addition of a - # new command forces us to consider it here :-) - case old - of cmdCompileToC, cmdCompileToCpp, cmdCompileToOC, - cmdCompileToJS, cmdCompileToPHP, cmdCompileToLLVM: - if new in {cmdDoc, cmdCheck, cmdIdeTools, cmdPretty, cmdDef, - cmdInteractive}: - return false - of cmdNone, cmdDoc, cmdInterpret, cmdPretty, cmdGenDepend, cmdDump, - cmdCheck, cmdParse, cmdScan, cmdIdeTools, cmdDef, - cmdRst2html, cmdRst2tex, cmdInteractive, cmdRun, cmdJsonScript: - discard - # else: trigger recompilation: - result = true - -proc processRodFile(r: PRodReader, hash: SecureHash) = - var - w: string - d: int - var inclHash: SecureHash - while r.s[r.pos] != '\0': - var section = rdWord(r) - if r.reason != rrNone: - break # no need to process this file further - case section - of "HASH": - inc(r.pos) # skip ':' - if hash != parseSecureHash(decodeStr(r.s, r.pos)): - r.reason = rrHashChange - of "ID": - inc(r.pos) # skip ':' - r.moduleID = decodeVInt(r.s, r.pos) - setId(r.moduleID) - of "ORIGFILE": - inc(r.pos) - r.origFile = decodeStr(r.s, r.pos) - of "OPTIONS": - inc(r.pos) # skip ':' - r.options = cast[TOptions](int32(decodeVInt(r.s, r.pos))) - if options.gOptions != r.options: r.reason = rrOptions - of "GOPTIONS": - inc(r.pos) # skip ':' - var dep = cast[TGlobalOptions](int32(decodeVInt(r.s, r.pos))) - if gGlobalOptions-harmlessOptions != dep-harmlessOptions: - r.reason = rrOptions - of "CMD": - inc(r.pos) # skip ':' - var dep = cast[TCommands](int32(decodeVInt(r.s, r.pos))) - if cmdChangeTriggersRecompilation(dep, gCmd): r.reason = rrOptions - of "DEFINES": - inc(r.pos) # skip ':' - d = 0 - while r.s[r.pos] > '\x0A': - w = decodeStr(r.s, r.pos) - inc(d) - if not condsyms.isDefined(r.cache.getIdent(w)): - r.reason = rrDefines #MessageOut('not defined, but should: ' + w); - if r.s[r.pos] == ' ': inc(r.pos) - if d != countDefinedSymbols(): r.reason = rrDefines - of "FILES": - inc(r.pos, 2) # skip "(\10" - inc(r.line) - while r.s[r.pos] != ')': - let finalPath = decodeStr(r.s, r.pos) - #let resolvedPath = relativePath.findModule(r.origFile) - #let finalPath = if resolvedPath.len > 0: resolvedPath else: relativePath - r.files.add(finalPath.fileInfoIdx) - inc(r.pos) # skip #10 - inc(r.line) - if r.s[r.pos] == ')': inc(r.pos) - of "INCLUDES": - inc(r.pos, 2) # skip "(\10" - inc(r.line) - while r.s[r.pos] != ')': - w = r.files[decodeVInt(r.s, r.pos)].toFullPath - inc(r.pos) # skip ' ' - inclHash = parseSecureHash(decodeStr(r.s, r.pos)) - if r.reason == rrNone: - if not existsFile(w) or (inclHash != secureHashFile(w)): - r.reason = rrInclDeps - if r.s[r.pos] == '\x0A': - inc(r.pos) - inc(r.line) - if r.s[r.pos] == ')': inc(r.pos) - of "DEPS": - inc(r.pos) # skip ':' - while r.s[r.pos] > '\x0A': - r.modDeps.add(r.files[int32(decodeVInt(r.s, r.pos))]) - if r.s[r.pos] == ' ': inc(r.pos) - of "INTERF": - r.interfIdx = r.pos + 2 - skipSection(r) - of "COMPILERPROCS": - r.compilerProcsIdx = r.pos + 2 - skipSection(r) - of "INDEX": - processIndex(r, r.index) - of "IMPORTS": - processIndex(r, r.imports) - of "CONVERTERS": - r.convertersIdx = r.pos + 1 - skipSection(r) - of "METHODS": - r.methodsIdx = r.pos + 1 - skipSection(r) - of "DATA": - r.dataIdx = r.pos + 2 # "(\10" - # We do not read the DATA section here! We read the needed objects on - # demand. And the DATA section comes last in the file, so we stop here: - break - of "INIT": - r.initIdx = r.pos + 2 # "(\10" - skipSection(r) - else: - internalError("invalid section: '" & section & - "' at " & $r.line & " in " & r.filename) - #MsgWriteln("skipping section: " & section & - # " at " & $r.line & " in " & r.filename) - skipSection(r) - if r.s[r.pos] == '\x0A': - inc(r.pos) - inc(r.line) - - -proc startsWith(buf: cstring, token: string, pos = 0): bool = - var s = 0 - while s < token.len and buf[pos+s] == token[s]: inc s - result = s == token.len - -proc newRodReader(modfilename: string, hash: SecureHash, - readerIndex: int; cache: IdentCache): PRodReader = - new(result) - result.cache = cache - try: - result.memfile = memfiles.open(modfilename) - except OSError: - return nil - result.files = @[] - result.modDeps = @[] - result.methods = @[] - var r = result - r.reason = rrNone - r.pos = 0 - r.line = 1 - r.readerIndex = readerIndex - r.filename = modfilename - r.syms = initTable[int, PSym]() - # we terminate the file explicitly with ``\0``, so the cast to `cstring` - # is safe: - r.s = cast[cstring](r.memfile.mem) - if startsWith(r.s, "NIM:"): - initIiTable(r.index.tab) - initIiTable(r.imports.tab) # looks like a ROD file - inc(r.pos, 4) - var version = "" - while r.s[r.pos] notin {'\0', '\x0A'}: - add(version, r.s[r.pos]) - inc(r.pos) - if r.s[r.pos] == '\x0A': inc(r.pos) - if version != RodFileVersion: - # since ROD files are only for caching, no backwards compatibility is - # needed - #echo "expected version ", version, " ", RodFileVersion - result.memfile.close - result = nil - else: - result.memfile.close - result = nil - -proc rrGetType(r: PRodReader, id: int, info: TLineInfo): PType = - result = PType(idTableGet(gTypeTable, id)) - if result == nil: - # load the type: - var oldPos = r.pos - var d = iiTableGet(r.index.tab, id) - if d == InvalidKey: internalError(info, "rrGetType") - r.pos = d + r.dataIdx - result = decodeType(r, info) - r.pos = oldPos - -type - TFileModuleRec{.final.} = object - filename*: string - reason*: TReasonForRecompile - rd*: PRodReader - hash*: SecureHash - hashDone*: bool - - TFileModuleMap = seq[TFileModuleRec] - -var gMods*: TFileModuleMap = @[] - -proc decodeSymSafePos(rd: PRodReader, offset: int, info: TLineInfo): PSym = - # all compiled modules - if rd.dataIdx == 0: internalError(info, "dataIdx == 0") - var oldPos = rd.pos - rd.pos = offset + rd.dataIdx - result = decodeSym(rd, info) - rd.pos = oldPos - -proc findSomeWhere(id: int) = - for i in countup(0, high(gMods)): - var rd = gMods[i].rd - if rd != nil: - var d = iiTableGet(rd.index.tab, id) - if d != InvalidKey: - echo "found id ", id, " in ", gMods[i].filename - -proc getReader(moduleId: int): PRodReader = - # we can't index 'gMods' here as it's indexed by a *file index* which is not - # the module ID! We could introduce a mapping ID->PRodReader but I'll leave - # this for later versions if benchmarking shows the linear search causes - # problems: - for i in 0 ..< gMods.len: - result = gMods[i].rd - if result != nil and result.moduleID == moduleId: return result - return nil - -proc rrGetSym(r: PRodReader, id: int, info: TLineInfo): PSym = - result = r.syms.getOrDefault(id) - if result == nil: - # load the symbol: - var d = iiTableGet(r.index.tab, id) - if d == InvalidKey: - # import from other module: - var moduleID = iiTableGet(r.imports.tab, id) - if moduleID < 0: - var x = "" - encodeVInt(id, x) - internalError(info, "missing from both indexes: +" & x) - var rd = getReader(moduleID) - doAssert rd != nil - d = iiTableGet(rd.index.tab, id) - if d != InvalidKey: - result = decodeSymSafePos(rd, d, info) - else: - var x = "" - encodeVInt(id, x) - when false: findSomeWhere(id) - internalError(info, "rrGetSym: no reader found: +" & x) - else: - # own symbol: - result = decodeSymSafePos(r, d, info) - if result != nil and result.kind == skStub: rawLoadStub(result) - -proc loadInitSection*(r: PRodReader): PNode = - if r.initIdx == 0 or r.dataIdx == 0: internalError("loadInitSection") - var oldPos = r.pos - r.pos = r.initIdx - result = newNode(nkStmtList) - while r.s[r.pos] > '\x0A' and r.s[r.pos] != ')': - var d = decodeVInt(r.s, r.pos) - inc(r.pos) # #10 - var p = r.pos - r.pos = d + r.dataIdx - addSon(result, decodeNode(r, unknownLineInfo())) - r.pos = p - r.pos = oldPos - -proc loadConverters(r: PRodReader) = - # We have to ensure that no exported converter is a stub anymore, and the - # import mechanism takes care of the rest. - if r.convertersIdx == 0 or r.dataIdx == 0: - internalError("importConverters") - r.pos = r.convertersIdx - while r.s[r.pos] > '\x0A': - var d = decodeVInt(r.s, r.pos) - discard rrGetSym(r, d, unknownLineInfo()) - if r.s[r.pos] == ' ': inc(r.pos) - -proc loadMethods(r: PRodReader) = - if r.methodsIdx == 0 or r.dataIdx == 0: - internalError("loadMethods") - r.pos = r.methodsIdx - while r.s[r.pos] > '\x0A': - var d = decodeVInt(r.s, r.pos) - r.methods.add(rrGetSym(r, d, unknownLineInfo())) - if r.s[r.pos] == ' ': inc(r.pos) - -proc getHash*(fileIdx: int32): SecureHash = - if fileIdx <% gMods.len and gMods[fileIdx].hashDone: - return gMods[fileIdx].hash - - result = secureHashFile(fileIdx.toFullPath) - if fileIdx >= gMods.len: setLen(gMods, fileIdx+1) - gMods[fileIdx].hash = result - -template growCache*(cache, pos) = - if cache.len <= pos: cache.setLen(pos+1) - -proc checkDep(fileIdx: int32; cache: IdentCache): TReasonForRecompile = - assert fileIdx != InvalidFileIDX - growCache gMods, fileIdx - if gMods[fileIdx].reason != rrEmpty: - # reason has already been computed for this module: - return gMods[fileIdx].reason - let filename = fileIdx.toFilename - var hash = getHash(fileIdx) - gMods[fileIdx].reason = rrNone # we need to set it here to avoid cycles - result = rrNone - var rodfile = toGeneratedFile(filename.withPackageName, RodExt) - var r = newRodReader(rodfile, hash, fileIdx, cache) - if r == nil: - result = (if existsFile(rodfile): rrRodInvalid else: rrRodDoesNotExist) - else: - processRodFile(r, hash) - result = r.reason - if result == rrNone: - # check modules it depends on - # NOTE: we need to process the entire module graph so that no ID will - # be used twice! However, compilation speed does not suffer much from - # this, since results are cached. - var res = checkDep(systemFileIdx, cache) - if res != rrNone: result = rrModDeps - for i in countup(0, high(r.modDeps)): - res = checkDep(r.modDeps[i], cache) - if res != rrNone: - result = rrModDeps - # we cannot break here, because of side-effects of `checkDep` - if result != rrNone: - rawMessage(hintProcessing, reasonToFrmt[result] % filename) - if result != rrNone or optForceFullMake in gGlobalOptions: - # recompilation is necessary: - if r != nil: memfiles.close(r.memfile) - r = nil - gMods[fileIdx].rd = r - gMods[fileIdx].reason = result # now we know better - -proc handleSymbolFile*(module: PSym; cache: IdentCache): PRodReader = - let fileIdx = module.fileIdx - if gSymbolFiles in {disabledSf, writeOnlySf, v2Sf}: - module.id = getID() - return nil - idgen.loadMaxIds(options.gProjectPath / options.gProjectName) - - discard checkDep(fileIdx, cache) - if gMods[fileIdx].reason == rrEmpty: internalError("handleSymbolFile") - result = gMods[fileIdx].rd - if result != nil: - module.id = result.moduleID - result.syms[module.id] = module - processInterf(result, module) - processCompilerProcs(result, module) - loadConverters(result) - loadMethods(result) - else: - module.id = getID() - -proc rawLoadStub(s: PSym) = - if s.kind != skStub: internalError("loadStub") - var rd = gMods[s.position].rd - var theId = s.id # used for later check - var d = iiTableGet(rd.index.tab, s.id) - if d == InvalidKey: internalError("loadStub: invalid key") - var rs = decodeSymSafePos(rd, d, unknownLineInfo()) - if rs != s: - #echo "rs: ", toHex(cast[int](rs.position), int.sizeof * 2), - # "\ns: ", toHex(cast[int](s.position), int.sizeof * 2) - internalError(rs.info, "loadStub: wrong symbol") - elif rs.id != theId: - internalError(rs.info, "loadStub: wrong ID") - #MessageOut('loaded stub: ' + s.name.s); - -proc loadStub*(s: PSym) = - ## loads the stub symbol `s`. - - # deactivate the GC here because we do a deep recursion and generate no - # garbage when restoring parts of the object graph anyway. - # Since we die with internal errors if this fails, no try-finally is - # necessary. - GC_disable() - rawLoadStub(s) - GC_enable() - -proc getBody*(s: PSym): PNode = - ## retrieves the AST's body of `s`. If `s` has been loaded from a rod-file - ## it may perform an expensive reload operation. Otherwise it's a simple - ## accessor. - assert s.kind in routineKinds - # prevent crashes due to incorrect macro transformations (bug #2377) - if s.ast.isNil or bodyPos >= s.ast.len: return ast.emptyNode - result = s.ast.sons[bodyPos] - if result == nil: - assert s.offset != 0 - var r = gMods[s.position].rd - var oldPos = r.pos - r.pos = s.offset - result = decodeNode(r, s.info) - r.pos = oldPos - s.ast.sons[bodyPos] = result - s.offset = 0 - -initIdTable(gTypeTable) -initStrTable(rodCompilerprocs) - -# viewer: -proc writeNode(f: File; n: PNode) = - f.write("(") - if n != nil: - f.write($n.kind) - if n.typ != nil: - f.write('^') - f.write(n.typ.id) - case n.kind - of nkCharLit..nkUInt64Lit: - if n.intVal != 0: - f.write('!') - f.write(n.intVal) - of nkFloatLit..nkFloat64Lit: - if n.floatVal != 0.0: - f.write('!') - f.write($n.floatVal) - of nkStrLit..nkTripleStrLit: - if n.strVal != "": - f.write('!') - f.write(n.strVal.escape) - of nkIdent: - f.write('!') - f.write(n.ident.s) - of nkSym: - f.write('!') - f.write(n.sym.id) - else: - for i in countup(0, sonsLen(n) - 1): - writeNode(f, n.sons[i]) - f.write(")") - -proc writeSym(f: File; s: PSym) = - if s == nil: - f.write("{}\n") - return - f.write("{") - f.write($s.kind) - f.write('+') - f.write(s.id) - f.write('&') - f.write(s.name.s) - if s.typ != nil: - f.write('^') - f.write(s.typ.id) - if s.owner != nil: - f.write('*') - f.write(s.owner.id) - if s.flags != {}: - f.write('$') - f.write($s.flags) - if s.magic != mNone: - f.write('@') - f.write($s.magic) - if s.options != gOptions: - f.write('!') - f.write($s.options) - if s.position != 0: - f.write('%') - f.write($s.position) - if s.offset != -1: - f.write('`') - f.write($s.offset) - if s.constraint != nil: - f.write('#') - f.writeNode(s.constraint) - if s.ast != nil: - f.writeNode(s.ast) - f.write("}\n") - -proc writeType(f: File; t: PType) = - if t == nil: - f.write("[]\n") - return - f.write('[') - f.write($t.kind) - f.write('+') - f.write($t.id) - if t.n != nil: - f.writeNode(t.n) - if t.flags != {}: - f.write('$') - f.write($t.flags) - if t.callConv != low(t.callConv): - f.write('?') - f.write($t.callConv) - if t.owner != nil: - f.write('*') - f.write($t.owner.id) - if t.sym != nil: - f.write('&') - f.write(t.sym.id) - if t.size != -1: - f.write('/') - f.write($t.size) - if t.align != 2: - f.write('=') - f.write($t.align) - for i in countup(0, sonsLen(t) - 1): - if t.sons[i] == nil: - f.write("^()") - else: - f.write('^') - f.write($t.sons[i].id) - f.write("]\n") - -proc viewFile(rodfile: string) = - var r = newRodReader(rodfile, secureHash(""), 0, newIdentCache()) - if r == nil: - rawMessage(errGenerated, "cannot open file (or maybe wrong version):" & - rodfile) - return - r.inViewMode = true - var outf = system.open(rodfile.changeFileExt(".rod.txt"), fmWrite) - while r.s[r.pos] != '\0': - let section = rdWord(r) - case section - of "HASH": - inc(r.pos) # skip ':' - outf.writeLine("HASH:", $decodeVInt(r.s, r.pos)) - of "ID": - inc(r.pos) # skip ':' - r.moduleID = decodeVInt(r.s, r.pos) - setId(r.moduleID) - outf.writeLine("ID:", $r.moduleID) - of "ORIGFILE": - inc(r.pos) - r.origFile = decodeStr(r.s, r.pos) - outf.writeLine("ORIGFILE:", r.origFile) - of "OPTIONS": - inc(r.pos) # skip ':' - r.options = cast[TOptions](int32(decodeVInt(r.s, r.pos))) - outf.writeLine("OPTIONS:", $r.options) - of "GOPTIONS": - inc(r.pos) # skip ':' - let dep = cast[TGlobalOptions](int32(decodeVInt(r.s, r.pos))) - outf.writeLine("GOPTIONS:", $dep) - of "CMD": - inc(r.pos) # skip ':' - let dep = cast[TCommands](int32(decodeVInt(r.s, r.pos))) - outf.writeLine("CMD:", $dep) - of "DEFINES": - inc(r.pos) # skip ':' - var d = 0 - outf.write("DEFINES:") - while r.s[r.pos] > '\x0A': - let w = decodeStr(r.s, r.pos) - inc(d) - outf.write(" ", w) - if r.s[r.pos] == ' ': inc(r.pos) - outf.write("\n") - of "FILES": - inc(r.pos, 2) # skip "(\10" - inc(r.line) - outf.write("FILES(\n") - while r.s[r.pos] != ')': - let relativePath = decodeStr(r.s, r.pos) - let resolvedPath = relativePath.findModule(r.origFile) - let finalPath = if resolvedPath.len > 0: resolvedPath else: relativePath - r.files.add(finalPath.fileInfoIdx) - inc(r.pos) # skip #10 - inc(r.line) - outf.writeLine finalPath - if r.s[r.pos] == ')': inc(r.pos) - outf.write(")\n") - of "INCLUDES": - inc(r.pos, 2) # skip "(\10" - inc(r.line) - outf.write("INCLUDES(\n") - while r.s[r.pos] != ')': - let w = r.files[decodeVInt(r.s, r.pos)] - inc(r.pos) # skip ' ' - let inclHash = decodeVInt(r.s, r.pos) - if r.s[r.pos] == '\x0A': - inc(r.pos) - inc(r.line) - outf.write(w, " ", inclHash, "\n") - if r.s[r.pos] == ')': inc(r.pos) - outf.write(")\n") - of "DEPS": - inc(r.pos) # skip ':' - outf.write("DEPS:") - while r.s[r.pos] > '\x0A': - let v = int32(decodeVInt(r.s, r.pos)) - r.modDeps.add(r.files[v]) - if r.s[r.pos] == ' ': inc(r.pos) - outf.write(" ", r.files[v]) - outf.write("\n") - of "INTERF", "COMPILERPROCS": - inc r.pos, 2 - if section == "INTERF": r.interfIdx = r.pos - else: r.compilerProcsIdx = r.pos - outf.write(section, "(\n") - while (r.s[r.pos] > '\x0A') and (r.s[r.pos] != ')'): - let w = decodeStr(r.s, r.pos) - inc(r.pos) - let key = decodeVInt(r.s, r.pos) - inc(r.pos) # #10 - outf.write(w, " ", key, "\n") - if r.s[r.pos] == ')': inc r.pos - outf.write(")\n") - of "INDEX": - outf.write(section, "(\n") - processIndex(r, r.index, outf) - outf.write(")\n") - of "IMPORTS": - outf.write(section, "(\n") - processIndex(r, r.imports, outf) - outf.write(")\n") - of "CONVERTERS", "METHODS": - inc r.pos - if section == "METHODS": r.methodsIdx = r.pos - else: r.convertersIdx = r.pos - outf.write(section, ":") - while r.s[r.pos] > '\x0A': - let d = decodeVInt(r.s, r.pos) - outf.write(" ", $d) - if r.s[r.pos] == ' ': inc(r.pos) - outf.write("\n") - of "DATA": - inc(r.pos, 2) - r.dataIdx = r.pos - outf.write("DATA(\n") - while r.s[r.pos] != ')': - if r.s[r.pos] == '(': - outf.writeNode decodeNode(r, unknownLineInfo()) - outf.write("\n") - elif r.s[r.pos] == '[': - outf.writeType decodeType(r, unknownLineInfo()) - else: - outf.writeSym decodeSym(r, unknownLineInfo()) - if r.s[r.pos] == '\x0A': - inc(r.pos) - inc(r.line) - if r.s[r.pos] == ')': inc r.pos - outf.write(")\n") - of "INIT": - outf.write("INIT(\n") - inc r.pos, 2 - r.initIdx = r.pos - while r.s[r.pos] > '\x0A' and r.s[r.pos] != ')': - let d = decodeVInt(r.s, r.pos) - inc(r.pos) # #10 - #let p = r.pos - #r.pos = d + r.dataIdx - #outf.writeNode decodeNode(r, UnknownLineInfo()) - #outf.write("\n") - #r.pos = p - if r.s[r.pos] == ')': inc r.pos - outf.write("<not supported by viewer>)\n") - else: - internalError("invalid section: '" & section & - "' at " & $r.line & " in " & r.filename) - skipSection(r) - if r.s[r.pos] == '\x0A': - inc(r.pos) - inc(r.line) - outf.close - -when isMainModule: - viewFile(paramStr(1).addFileExt(RodExt)) diff --git a/compiler/rodutils.nim b/compiler/rodutils.nim index 66d7f63c2..90431999a 100644 --- a/compiler/rodutils.nim +++ b/compiler/rodutils.nim @@ -10,6 +10,27 @@ ## Serialization utilities for the compiler. import strutils, math +# bcc on windows doesn't have C99 functions +when defined(windows) and defined(bcc): + {.emit: """#if defined(_MSC_VER) && _MSC_VER < 1900 + #include <stdarg.h> + static int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) { + int count = -1; + if (size != 0) count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + if (count == -1) count = _vscprintf(format, ap); + return count; + } + int snprintf(char *outBuf, size_t size, const char *format, ...) { + int count; + va_list ap; + va_start(ap, format); + count = c99_vsnprintf(outBuf, size, format, ap); + va_end(ap); + return count; + } + #endif + """.} + proc c_snprintf(s: cstring; n:uint; frmt: cstring): cint {.importc: "snprintf", header: "<stdio.h>", nodecl, varargs.} proc toStrMaxPrecision*(f: BiggestFloat, literalPostfix = ""): string = diff --git a/compiler/rodwrite.nim b/compiler/rodwrite.nim deleted file mode 100644 index 96deb1d5a..000000000 --- a/compiler/rodwrite.nim +++ /dev/null @@ -1,656 +0,0 @@ -# -# -# The Nim Compiler -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -# This module is responsible for writing of rod files. Note that writing of -# rod files is a pass, reading of rod files is not! This is why reading and -# writing of rod files is split into two different modules. - -import - intsets, os, options, strutils, nversion, ast, astalgo, msgs, platform, - condsyms, ropes, idents, std / sha1, rodread, passes, idgen, - rodutils, modulepaths - -from modulegraphs import ModuleGraph - -type - TRodWriter = object of TPassContext - module: PSym - hash: SecureHash - options: TOptions - defines: string - inclDeps: string - modDeps: string - interf: string - compilerProcs: string - index, imports: TIndex - converters, methods: string - init: string - data: string - sstack: TSymSeq # a stack of symbols to process - tstack: TTypeSeq # a stack of types to process - files: TStringSeq - origFile: string - cache: IdentCache - - PRodWriter = ref TRodWriter - -proc getDefines(): string = - result = "" - for d in definedSymbolNames(): - if result.len != 0: add(result, " ") - add(result, d) - -proc fileIdx(w: PRodWriter, filename: string): int = - for i in countup(0, high(w.files)): - if w.files[i] == filename: - return i - result = len(w.files) - setLen(w.files, result + 1) - w.files[result] = filename - -template filename*(w: PRodWriter): string = - w.module.filename - -proc newRodWriter(hash: SecureHash, module: PSym; cache: IdentCache): PRodWriter = - new(result) - result.sstack = @[] - result.tstack = @[] - initIiTable(result.index.tab) - initIiTable(result.imports.tab) - result.index.r = "" - result.imports.r = "" - result.hash = hash - result.module = module - result.defines = getDefines() - result.options = options.gOptions - result.files = @[] - result.inclDeps = "" - result.modDeps = "" - result.interf = newStringOfCap(2_000) - result.compilerProcs = "" - result.converters = "" - result.methods = "" - result.init = "" - result.origFile = module.info.toFullPath - result.data = newStringOfCap(12_000) - result.cache = cache - -proc addModDep(w: PRodWriter, dep: string; info: TLineInfo) = - if w.modDeps.len != 0: add(w.modDeps, ' ') - let resolved = dep.findModule(info.toFullPath) - encodeVInt(fileIdx(w, resolved), w.modDeps) - -const - rodNL = "\x0A" - -proc addInclDep(w: PRodWriter, dep: string; info: TLineInfo) = - let resolved = dep.findModule(info.toFullPath) - encodeVInt(fileIdx(w, resolved), w.inclDeps) - add(w.inclDeps, " ") - encodeStr($secureHashFile(resolved), w.inclDeps) - add(w.inclDeps, rodNL) - -proc pushType(w: PRodWriter, t: PType) = - # check so that the stack does not grow too large: - if iiTableGet(w.index.tab, t.id) == InvalidKey: - w.tstack.add(t) - -proc pushSym(w: PRodWriter, s: PSym) = - # check so that the stack does not grow too large: - if iiTableGet(w.index.tab, s.id) == InvalidKey: - when false: - if s.kind == skMethod: - echo "encoding ", s.id, " ", s.name.s - writeStackTrace() - w.sstack.add(s) - -proc encodeNode(w: PRodWriter, fInfo: TLineInfo, n: PNode, - result: var string) = - if n == nil: - # nil nodes have to be stored too: - result.add("()") - return - result.add('(') - encodeVInt(ord(n.kind), result) - # we do not write comments for now - # Line information takes easily 20% or more of the filesize! Therefore we - # omit line information if it is the same as the father's line information: - if fInfo.fileIndex != n.info.fileIndex: - result.add('?') - encodeVInt(n.info.col, result) - result.add(',') - encodeVInt(n.info.line, result) - result.add(',') - encodeVInt(fileIdx(w, toFullPath(n.info)), result) - elif fInfo.line != n.info.line: - result.add('?') - encodeVInt(n.info.col, result) - result.add(',') - encodeVInt(n.info.line, result) - elif fInfo.col != n.info.col: - result.add('?') - encodeVInt(n.info.col, result) - # No need to output the file index, as this is the serialization of one - # file. - var f = n.flags * PersistentNodeFlags - if f != {}: - result.add('$') - encodeVInt(cast[int32](f), result) - if n.typ != nil: - result.add('^') - encodeVInt(n.typ.id, result) - pushType(w, n.typ) - case n.kind - of nkCharLit..nkUInt64Lit: - if n.intVal != 0: - result.add('!') - encodeVBiggestInt(n.intVal, result) - of nkFloatLit..nkFloat64Lit: - if n.floatVal != 0.0: - result.add('!') - encodeStr($n.floatVal, result) - of nkStrLit..nkTripleStrLit: - if n.strVal != "": - result.add('!') - encodeStr(n.strVal, result) - of nkIdent: - result.add('!') - encodeStr(n.ident.s, result) - of nkSym: - result.add('!') - encodeVInt(n.sym.id, result) - pushSym(w, n.sym) - else: - for i in countup(0, sonsLen(n) - 1): - encodeNode(w, n.info, n.sons[i], result) - add(result, ')') - -proc encodeLoc(w: PRodWriter, loc: TLoc, result: var string) = - var oldLen = result.len - result.add('<') - if loc.k != low(loc.k): encodeVInt(ord(loc.k), result) - if loc.storage != low(loc.storage): - add(result, '*') - encodeVInt(ord(loc.storage), result) - if loc.flags != {}: - add(result, '$') - encodeVInt(cast[int32](loc.flags), result) - if loc.lode != nil: - add(result, '^') - encodeNode(w, unknownLineInfo(), loc.lode, result) - #encodeVInt(cast[int32](loc.t.id), result) - #pushType(w, loc.t) - if loc.r != nil: - add(result, '!') - encodeStr($loc.r, result) - if oldLen + 1 == result.len: - # no data was necessary, so remove the '<' again: - setLen(result, oldLen) - else: - add(result, '>') - -proc encodeType(w: PRodWriter, t: PType, result: var string) = - if t == nil: - # nil nodes have to be stored too: - result.add("[]") - return - # we need no surrounding [] here because the type is in a line of its own - if t.kind == tyForward: internalError("encodeType: tyForward") - # for the new rodfile viewer we use a preceding [ so that the data section - # can easily be disambiguated: - add(result, '[') - encodeVInt(ord(t.kind), result) - add(result, '+') - encodeVInt(t.id, result) - if t.n != nil: - encodeNode(w, unknownLineInfo(), t.n, result) - if t.flags != {}: - add(result, '$') - encodeVInt(cast[int32](t.flags), result) - if t.callConv != low(t.callConv): - add(result, '?') - encodeVInt(ord(t.callConv), result) - if t.owner != nil: - add(result, '*') - encodeVInt(t.owner.id, result) - pushSym(w, t.owner) - if t.sym != nil: - add(result, '&') - encodeVInt(t.sym.id, result) - pushSym(w, t.sym) - if t.size != - 1: - add(result, '/') - encodeVBiggestInt(t.size, result) - if t.align != 2: - add(result, '=') - encodeVInt(t.align, result) - if t.lockLevel.ord != UnspecifiedLockLevel.ord: - add(result, '\14') - encodeVInt(t.lockLevel.int16, result) - if t.destructor != nil and t.destructor.id != 0: - add(result, '\15') - encodeVInt(t.destructor.id, result) - pushSym(w, t.destructor) - if t.deepCopy != nil: - add(result, '\16') - encodeVInt(t.deepcopy.id, result) - pushSym(w, t.deepcopy) - if t.assignment != nil: - add(result, '\17') - encodeVInt(t.assignment.id, result) - pushSym(w, t.assignment) - if t.sink != nil: - add(result, '\18') - encodeVInt(t.sink.id, result) - pushSym(w, t.sink) - for i, s in items(t.methods): - add(result, '\19') - encodeVInt(i, result) - add(result, '\20') - encodeVInt(s.id, result) - pushSym(w, s) - encodeLoc(w, t.loc, result) - for i in countup(0, sonsLen(t) - 1): - if t.sons[i] == nil: - add(result, "^()") - else: - add(result, '^') - encodeVInt(t.sons[i].id, result) - pushType(w, t.sons[i]) - -proc encodeLib(w: PRodWriter, lib: PLib, info: TLineInfo, result: var string) = - add(result, '|') - encodeVInt(ord(lib.kind), result) - add(result, '|') - encodeStr($lib.name, result) - add(result, '|') - encodeNode(w, info, lib.path, result) - -proc encodeInstantiations(w: PRodWriter; s: seq[PInstantiation]; - result: var string) = - for t in s: - result.add('\15') - encodeVInt(t.sym.id, result) - pushSym(w, t.sym) - for tt in t.concreteTypes: - result.add('\17') - encodeVInt(tt.id, result) - pushType(w, tt) - result.add('\20') - encodeVInt(t.compilesId, result) - -proc encodeSym(w: PRodWriter, s: PSym, result: var string) = - if s == nil: - # nil nodes have to be stored too: - result.add("{}") - return - # we need no surrounding {} here because the symbol is in a line of its own - encodeVInt(ord(s.kind), result) - result.add('+') - encodeVInt(s.id, result) - result.add('&') - encodeStr(s.name.s, result) - if s.typ != nil: - result.add('^') - encodeVInt(s.typ.id, result) - pushType(w, s.typ) - result.add('?') - if s.info.col != -1'i16: encodeVInt(s.info.col, result) - result.add(',') - if s.info.line != -1'i16: encodeVInt(s.info.line, result) - result.add(',') - encodeVInt(fileIdx(w, toFullPath(s.info)), result) - if s.owner != nil: - result.add('*') - encodeVInt(s.owner.id, result) - pushSym(w, s.owner) - if s.flags != {}: - result.add('$') - encodeVInt(cast[int32](s.flags), result) - if s.magic != mNone: - result.add('@') - encodeVInt(ord(s.magic), result) - if s.options != w.options: - result.add('!') - encodeVInt(cast[int32](s.options), result) - if s.position != 0: - result.add('%') - encodeVInt(s.position, result) - if s.offset != - 1: - result.add('`') - encodeVInt(s.offset, result) - encodeLoc(w, s.loc, result) - if s.annex != nil: encodeLib(w, s.annex, s.info, result) - if s.constraint != nil: - add(result, '#') - encodeNode(w, unknownLineInfo(), s.constraint, result) - case s.kind - of skType, skGenericParam: - for t in s.typeInstCache: - result.add('\14') - encodeVInt(t.id, result) - pushType(w, t) - of routineKinds: - encodeInstantiations(w, s.procInstCache, result) - if s.gcUnsafetyReason != nil: - result.add('\16') - encodeVInt(s.gcUnsafetyReason.id, result) - pushSym(w, s.gcUnsafetyReason) - of skModule, skPackage: - encodeInstantiations(w, s.usedGenerics, result) - # we don't serialize: - #tab*: TStrTable # interface table for modules - of skLet, skVar, skField, skForVar: - if s.guard != nil: - result.add('\18') - encodeVInt(s.guard.id, result) - pushSym(w, s.guard) - if s.bitsize != 0: - result.add('\19') - encodeVInt(s.bitsize, result) - else: discard - # lazy loading will soon reload the ast lazily, so the ast needs to be - # the last entry of a symbol: - if s.ast != nil: - # we used to attempt to save space here by only storing a dummy AST if - # it is not necessary, but Nim's heavy compile-time evaluation features - # make that unfeasible nowadays: - encodeNode(w, s.info, s.ast, result) - -proc addToIndex(w: var TIndex, key, val: int) = - if key - w.lastIdxKey == 1: - # we do not store a key-diff of 1 to safe space - encodeVInt(val - w.lastIdxVal, w.r) - else: - encodeVInt(key - w.lastIdxKey, w.r) - add(w.r, ' ') - encodeVInt(val - w.lastIdxVal, w.r) - add(w.r, rodNL) - w.lastIdxKey = key - w.lastIdxVal = val - iiTablePut(w.tab, key, val) - -const debugWrittenIds = false - -when debugWrittenIds: - var debugWritten = initIntSet() - -proc symStack(w: PRodWriter): int = - var i = 0 - while i < len(w.sstack): - var s = w.sstack[i] - if sfForward in s.flags: - w.sstack[result] = s - inc result - elif iiTableGet(w.index.tab, s.id) == InvalidKey: - var m = getModule(s) - #if m == nil and s.kind != skPackage and sfGenSym notin s.flags: - # internalError("symStack: module nil: " & s.name.s & " " & $s.kind & " ID " & $s.id) - if m == nil or s.kind == skPackage or {sfFromGeneric, sfGenSym} * s.flags != {} or m.id == w.module.id: - # put definition in here - var L = w.data.len - addToIndex(w.index, s.id, L) - when debugWrittenIds: incl(debugWritten, s.id) - encodeSym(w, s, w.data) - add(w.data, rodNL) - # put into interface section if appropriate: - if {sfExported, sfFromGeneric} * s.flags == {sfExported} and - s.kind in ExportableSymKinds: - encodeStr(s.name.s, w.interf) - add(w.interf, ' ') - encodeVInt(s.id, w.interf) - add(w.interf, rodNL) - if sfCompilerProc in s.flags: - encodeStr(s.name.s, w.compilerProcs) - add(w.compilerProcs, ' ') - encodeVInt(s.id, w.compilerProcs) - add(w.compilerProcs, rodNL) - if s.kind == skConverter or (s.ast != nil and hasPattern(s)): - if w.converters.len != 0: add(w.converters, ' ') - encodeVInt(s.id, w.converters) - if s.kind == skMethod and sfDispatcher notin s.flags: - if w.methods.len != 0: add(w.methods, ' ') - encodeVInt(s.id, w.methods) - elif iiTableGet(w.imports.tab, s.id) == InvalidKey: - addToIndex(w.imports, s.id, m.id) - when debugWrittenIds: - if not contains(debugWritten, s.id): - echo(w.filename) - debug(s) - debug(s.owner) - debug(m) - internalError("Symbol referred to but never written") - inc(i) - setLen(w.sstack, result) - -proc typeStack(w: PRodWriter): int = - var i = 0 - while i < len(w.tstack): - var t = w.tstack[i] - if t.kind == tyForward: - w.tstack[result] = t - inc result - elif iiTableGet(w.index.tab, t.id) == InvalidKey: - var L = w.data.len - addToIndex(w.index, t.id, L) - encodeType(w, t, w.data) - add(w.data, rodNL) - inc(i) - setLen(w.tstack, result) - -proc processStacks(w: PRodWriter, finalPass: bool) = - var oldS = 0 - var oldT = 0 - while true: - var slen = symStack(w) - var tlen = typeStack(w) - if slen == oldS and tlen == oldT: break - oldS = slen - oldT = tlen - if finalPass and (oldS != 0 or oldT != 0): - internalError("could not serialize some forwarded symbols/types") - -proc rawAddInterfaceSym(w: PRodWriter, s: PSym) = - pushSym(w, s) - processStacks(w, false) - -proc addInterfaceSym(w: PRodWriter, s: PSym) = - if w == nil: return - if s.kind in ExportableSymKinds and - {sfExported, sfCompilerProc} * s.flags != {}: - rawAddInterfaceSym(w, s) - -proc addStmt(w: PRodWriter, n: PNode) = - encodeVInt(w.data.len, w.init) - add(w.init, rodNL) - encodeNode(w, unknownLineInfo(), n, w.data) - add(w.data, rodNL) - processStacks(w, false) - -proc writeRod(w: PRodWriter) = - processStacks(w, true) - var f: File - if not open(f, completeGeneratedFilePath(changeFileExt( - w.filename.withPackageName, RodExt)), - fmWrite): - #echo "couldn't write rod file for: ", w.filename - return - # write header: - f.write("NIM:") - f.write(RodFileVersion) - f.write(rodNL) - var id = "ID:" - encodeVInt(w.module.id, id) - f.write(id) - f.write(rodNL) - - var orig = "ORIGFILE:" - encodeStr(w.origFile, orig) - f.write(orig) - f.write(rodNL) - - var hash = "HASH:" - encodeStr($w.hash, hash) - f.write(hash) - f.write(rodNL) - - var options = "OPTIONS:" - encodeVInt(cast[int32](w.options), options) - f.write(options) - f.write(rodNL) - - var goptions = "GOPTIONS:" - encodeVInt(cast[int32](gGlobalOptions), goptions) - f.write(goptions) - f.write(rodNL) - - var cmd = "CMD:" - encodeVInt(cast[int32](gCmd), cmd) - f.write(cmd) - f.write(rodNL) - - f.write("DEFINES:") - f.write(w.defines) - f.write(rodNL) - - var files = "FILES(" & rodNL - for i in countup(0, high(w.files)): - encodeStr(w.files[i], files) - files.add(rodNL) - f.write(files) - f.write(')' & rodNL) - - f.write("INCLUDES(" & rodNL) - f.write(w.inclDeps) - f.write(')' & rodNL) - - f.write("DEPS:") - f.write(w.modDeps) - f.write(rodNL) - - f.write("INTERF(" & rodNL) - f.write(w.interf) - f.write(')' & rodNL) - - f.write("COMPILERPROCS(" & rodNL) - f.write(w.compilerProcs) - f.write(')' & rodNL) - - f.write("INDEX(" & rodNL) - f.write(w.index.r) - f.write(')' & rodNL) - - f.write("IMPORTS(" & rodNL) - f.write(w.imports.r) - f.write(')' & rodNL) - - f.write("CONVERTERS:") - f.write(w.converters) - f.write(rodNL) - - f.write("METHODS:") - f.write(w.methods) - f.write(rodNL) - - f.write("INIT(" & rodNL) - f.write(w.init) - f.write(')' & rodNL) - - f.write("DATA(" & rodNL) - f.write(w.data) - f.write(')' & rodNL) - # write trailing zero which is necessary because we use memory mapped files - # for reading: - f.write("\0") - f.close() - - #echo "interf: ", w.interf.len - #echo "index: ", w.index.r.len - #echo "init: ", w.init.len - #echo "data: ", w.data.len - -proc process(c: PPassContext, n: PNode): PNode = - result = n - if c == nil: return - var w = PRodWriter(c) - case n.kind - of nkStmtList: - for i in countup(0, sonsLen(n) - 1): discard process(c, n.sons[i]) - #var s = n.sons[namePos].sym - #addInterfaceSym(w, s) - of nkProcDef, nkFuncDef, nkIteratorDef, nkConverterDef, - nkTemplateDef, nkMacroDef: - let s = n.sons[namePos].sym - if s == nil: internalError(n.info, "rodwrite.process") - if n.sons[bodyPos] == nil: - internalError(n.info, "rodwrite.process: body is nil") - if n.sons[bodyPos].kind != nkEmpty or s.magic != mNone or - sfForward notin s.flags: - addInterfaceSym(w, s) - of nkMethodDef: - let s = n.sons[namePos].sym - if s == nil: internalError(n.info, "rodwrite.process") - if n.sons[bodyPos] == nil: - internalError(n.info, "rodwrite.process: body is nil") - if n.sons[bodyPos].kind != nkEmpty or s.magic != mNone or - sfForward notin s.flags: - pushSym(w, s) - processStacks(w, false) - - of nkVarSection, nkLetSection, nkConstSection: - for i in countup(0, sonsLen(n) - 1): - var a = n.sons[i] - if a.kind == nkCommentStmt: continue - addInterfaceSym(w, a.sons[0].sym) - of nkTypeSection: - for i in countup(0, sonsLen(n) - 1): - var a = n.sons[i] - if a.kind == nkCommentStmt: continue - if a.sons[0].kind != nkSym: internalError(a.info, "rodwrite.process") - var s = a.sons[0].sym - addInterfaceSym(w, s) - # this takes care of enum fields too - # Note: The check for ``s.typ.kind = tyEnum`` is wrong for enum - # type aliasing! Otherwise the same enum symbol would be included - # several times! - # - # if (a.sons[2] <> nil) and (a.sons[2].kind = nkEnumTy) then begin - # a := s.typ.n; - # for j := 0 to sonsLen(a)-1 do - # addInterfaceSym(w, a.sons[j].sym); - # end - of nkImportStmt: - for i in countup(0, sonsLen(n) - 1): - addModDep(w, getModuleName(n.sons[i]), n.info) - addStmt(w, n) - of nkFromStmt, nkImportExceptStmt: - addModDep(w, getModuleName(n.sons[0]), n.info) - addStmt(w, n) - of nkIncludeStmt: - for i in countup(0, sonsLen(n) - 1): - addInclDep(w, getModuleName(n.sons[i]), n.info) - of nkPragma: - addStmt(w, n) - else: - discard - -proc myOpen(g: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = - if module.id < 0: internalError("rodwrite: module ID not set") - var w = newRodWriter(rodread.getHash module.fileIdx, module, cache) - rawAddInterfaceSym(w, module) - result = w - -proc myClose(graph: ModuleGraph; c: PPassContext, n: PNode): PNode = - result = process(c, n) - var w = PRodWriter(c) - writeRod(w) - idgen.saveMaxIds(options.gProjectPath / options.gProjectName) - -const rodwritePass* = makePass(open = myOpen, close = myClose, process = process) - diff --git a/compiler/ropes.nim b/compiler/ropes.nim index 358ce8a53..81ee01dbf 100644 --- a/compiler/ropes.nim +++ b/compiler/ropes.nim @@ -56,7 +56,7 @@ # To cache them they are inserted in a `cache` array. import - platform, hashes + hashes type FormatStr* = string # later we may change it to CString for better @@ -66,43 +66,22 @@ type Rope* = ref RopeObj RopeObj*{.acyclic.} = object of RootObj # the empty rope is represented # by nil to safe space - left*, right*: Rope - length*: int - data*: string # != nil if a leaf - - RopeSeq* = seq[Rope] - - RopesError* = enum - rCannotOpenFile - rInvalidFormatStr - -# implementation - -var errorHandler*: proc(err: RopesError, msg: string, useWarning = false) - # avoid dependency on msgs.nim + left, right: Rope + L: int # <= 0 if a leaf + data*: string proc len*(a: Rope): int = ## the rope's length if a == nil: result = 0 - else: result = a.length + else: result = abs a.L -proc newRope(data: string = nil): Rope = +proc newRope(data: string = ""): Rope = new(result) - if data != nil: - result.length = len(data) - result.data = data - -proc newMutableRope*(capacity = 30): Rope = - ## creates a new rope that supports direct modifications of the rope's - ## 'data' and 'length' fields. - new(result) - result.data = newStringOfCap(capacity) - -proc freezeMutableRope*(r: Rope) {.inline.} = - r.length = r.data.len + result.L = -len(data) + result.data = data var - cache: array[0..2048*2 - 1, Rope] + cache: array[0..2048*2 - 1, Rope] # XXX Global here! proc resetRopeCache* = for i in low(cache)..high(cache): @@ -158,7 +137,7 @@ proc `&`*(a, b: Rope): Rope = result = a else: result = newRope() - result.length = a.length + b.length + result.L = abs(a.L) + abs(b.L) result.left = a result.right = b @@ -188,11 +167,11 @@ iterator leaves*(r: Rope): string = var stack = @[r] while stack.len > 0: var it = stack.pop - while isNil(it.data): + while it.left != nil: + assert it.right != nil stack.add(it.right) it = it.left assert(it != nil) - assert(it.data != nil) yield it.data iterator items*(r: Rope): char = @@ -204,13 +183,14 @@ proc writeRope*(f: File, r: Rope) = ## writes a rope to a file. for s in leaves(r): write(f, s) -proc writeRope*(head: Rope, filename: string, useWarning = false) = +proc writeRope*(head: Rope, filename: string): bool = var f: File if open(f, filename, fmWrite): if head != nil: writeRope(f, head) close(f) + result = true else: - errorHandler(rCannotOpenFile, filename, useWarning) + result = false proc `$`*(r: Rope): string = ## converts a rope back to a string. @@ -225,11 +205,6 @@ proc ropeConcat*(a: varargs[Rope]): Rope = proc prepend*(a: var Rope, b: Rope) = a = b & a proc prepend*(a: var Rope, b: string) = a = b & a -var - rnl* = tnl.newRope - softRnl* = tnl.newRope - noRnl* = "".newRope - proc `%`*(frmt: FormatStr, args: openArray[Rope]): Rope = var i = 0 var length = len(frmt) @@ -251,10 +226,10 @@ proc `%`*(frmt: FormatStr, args: openArray[Rope]): Rope = while true: j = j * 10 + ord(frmt[i]) - ord('0') inc(i) - if frmt[i] notin {'0'..'9'}: break + if i >= frmt.len or frmt[i] notin {'0'..'9'}: break num = j if j > high(args) + 1: - errorHandler(rInvalidFormatStr, $(j)) + doAssert false, "invalid format string: " & frmt else: add(result, args[j-1]) of '{': @@ -265,20 +240,21 @@ proc `%`*(frmt: FormatStr, args: openArray[Rope]): Rope = inc(i) num = j if frmt[i] == '}': inc(i) - else: errorHandler(rInvalidFormatStr, $(frmt[i])) + else: + doAssert false, "invalid format string: " & frmt if j > high(args) + 1: - errorHandler(rInvalidFormatStr, $(j)) + doAssert false, "invalid format string: " & frmt else: add(result, args[j-1]) of 'n': - add(result, softRnl) + add(result, "\n") inc(i) of 'N': - add(result, rnl) + add(result, "\n") inc(i) else: - errorHandler(rInvalidFormatStr, $(frmt[i])) + doAssert false, "invalid format string: " & frmt var start = i while i < length: if frmt[i] != '$': inc(i) @@ -350,7 +326,6 @@ proc equalsFile*(r: Rope, filename: string): bool = proc writeRopeIfNotEqual*(r: Rope, filename: string): bool = # returns true if overwritten if not equalsFile(r, filename): - writeRope(r, filename) - result = true + result = writeRope(r, filename) else: result = false diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim index 8eb76457c..184b60733 100644 --- a/compiler/scriptconfig.nim +++ b/compiler/scriptconfig.nim @@ -13,7 +13,7 @@ import ast, modules, idents, passes, passaux, condsyms, options, nimconf, sem, semdata, llstream, vm, vmdef, commands, msgs, - os, times, osproc, wordrecg, strtabs, modulegraphs + os, times, osproc, wordrecg, strtabs, modulegraphs, lineinfos # we support 'cmpIgnoreStyle' natively for efficiency: from strutils import cmpIgnoreStyle, contains @@ -26,11 +26,12 @@ proc listDirs(a: VmArgs, filter: set[PathComponent]) = setResult(a, result) proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; - config: ConfigRef = nil): PEvalContext = + graph: ModuleGraph): PEvalContext = # For Nimble we need to export 'setupVM'. - result = newCtx(module, cache) + result = newCtx(module, cache, graph) result.mode = emRepl registerAdditionalOps(result) + let conf = graph.config # captured vars: var errorMsg: string @@ -44,7 +45,7 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; template cbos(name, body) {.dirty.} = result.registerCallback "stdlib.system." & astToStr(name), proc (a: VmArgs) = - errorMsg = nil + errorMsg = "" try: body except OSError: @@ -70,11 +71,16 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; setResult(a, os.getCurrentDir()) cbos moveFile: os.moveFile(getString(a, 0), getString(a, 1)) + cbos moveDir: + os.moveDir(getString(a, 0), getString(a, 1)) cbos copyFile: os.copyFile(getString(a, 0), getString(a, 1)) + cbos copyDir: + os.copyDir(getString(a, 0), getString(a, 1)) cbos getLastModificationTime: - # depends on Time's implementation! - setResult(a, int64(getLastModificationTime(getString(a, 0)))) + setResult(a, getLastModificationTime(getString(a, 0)).toUnix) + cbos findExe: + setResult(a, os.findExe(getString(a, 0))) cbos rawExec: setResult(a, osproc.execCmd getString(a, 0)) @@ -83,6 +89,8 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; setResult(a, os.getEnv(a.getString 0, a.getString 1)) cbconf existsEnv: setResult(a, os.existsEnv(a.getString 0)) + cbconf putEnv: + os.putEnv(a.getString 0, a.getString 1) cbconf dirExists: setResult(a, os.dirExists(a.getString 0)) cbconf fileExists: @@ -91,13 +99,13 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; cbconf thisDir: setResult(a, vthisDir) cbconf put: - options.setConfigVar(getString(a, 0), getString(a, 1)) + options.setConfigVar(conf, getString(a, 0), getString(a, 1)) cbconf get: - setResult(a, options.getConfigVar(a.getString 0)) + setResult(a, options.getConfigVar(conf, a.getString 0)) cbconf exists: - setResult(a, options.existsConfigVar(a.getString 0)) + setResult(a, options.existsConfigVar(conf, a.getString 0)) cbconf nimcacheDir: - setResult(a, options.getNimcacheDir()) + setResult(a, options.getNimcacheDir(conf)) cbconf paramStr: setResult(a, os.paramStr(int a.getInt 0)) cbconf paramCount: @@ -107,67 +115,68 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; cbconf cmpIgnoreCase: setResult(a, strutils.cmpIgnoreCase(a.getString 0, a.getString 1)) cbconf setCommand: - options.command = a.getString 0 + conf.command = a.getString 0 let arg = a.getString 1 if arg.len > 0: - gProjectName = arg + conf.projectName = arg let path = - if gProjectName.isAbsolute: gProjectName - else: gProjectPath / gProjectName + if conf.projectName.isAbsolute: conf.projectName + else: conf.projectPath / conf.projectName try: - gProjectFull = canonicalizePath(path) + conf.projectFull = canonicalizePath(conf, path) except OSError: - gProjectFull = path + conf.projectFull = path cbconf getCommand: - setResult(a, options.command) + setResult(a, conf.command) cbconf switch: - processSwitch(a.getString 0, a.getString 1, passPP, module.info) + processSwitch(a.getString 0, a.getString 1, passPP, module.info, conf) cbconf hintImpl: processSpecificNote(a.getString 0, wHint, passPP, module.info, - a.getString 1) + a.getString 1, conf) cbconf warningImpl: processSpecificNote(a.getString 0, wWarning, passPP, module.info, - a.getString 1) + a.getString 1, conf) cbconf patchFile: let key = a.getString(0) & "_" & a.getString(1) var val = a.getString(2).addFileExt(NimExt) if {'$', '~'} in val: - val = pathSubs(val, vthisDir) + val = pathSubs(conf, val, vthisDir) elif not isAbsolute(val): val = vthisDir / val - gModuleOverrides[key] = val + conf.moduleOverrides[key] = val cbconf selfExe: setResult(a, os.getAppFilename()) cbconf cppDefine: - if config != nil: - options.cppDefine(config, a.getString(0)) + options.cppDefine(conf, a.getString(0)) proc runNimScript*(cache: IdentCache; scriptName: string; - freshDefines=true; config: ConfigRef=nil) = - rawMessage(hintConf, scriptName) - passes.gIncludeFile = includeModule - passes.gImportModule = importModule - let graph = newModuleGraph(config) - if freshDefines: initDefines() + freshDefines=true; conf: ConfigRef) = + rawMessage(conf, hintConf, scriptName) - defineSymbol("nimscript") - defineSymbol("nimconfig") - registerPass(semPass) - registerPass(evalPass) + let graph = newModuleGraph(cache, conf) + connectCallbacks(graph) + if freshDefines: initDefines(conf.symbols) - searchPaths.add(options.libpath) + defineSymbol(conf.symbols, "nimscript") + defineSymbol(conf.symbols, "nimconfig") + var registeredPasses {.global.} = false + if not registeredPasses: + registerPass(graph, semPass) + registerPass(graph, evalPass) + registeredPasses = true + + conf.searchPaths.add(conf.libpath) var m = graph.makeModule(scriptName) incl(m.flags, sfMainModule) - vm.globalCtx = setupVM(m, cache, scriptName, config) + graph.vm = setupVM(m, cache, scriptName, graph) - graph.compileSystemModule(cache) - discard graph.processModule(m, llStreamOpen(scriptName, fmRead), nil, cache) + graph.compileSystemModule() # TODO: see why this unsets hintConf in conf.notes + discard graph.processModule(m, llStreamOpen(scriptName, fmRead)) # ensure we load 'system.nim' again for the real non-config stuff! - resetSystemArtifacts() - vm.globalCtx = nil + resetSystemArtifacts(graph) # do not remove the defined symbols #initDefines() - undefSymbol("nimscript") - undefSymbol("nimconfig") + undefSymbol(conf.symbols, "nimscript") + undefSymbol(conf.symbols, "nimconfig") diff --git a/compiler/sem.nim b/compiler/sem.nim index 937f1637a..7a83c3079 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -13,15 +13,15 @@ import ast, strutils, hashes, options, lexer, astalgo, trees, treetab, wordrecg, ropes, msgs, os, condsyms, idents, renderer, types, platform, math, magicsys, parser, nversion, nimsets, semfold, modulepaths, importer, - procfind, lookups, rodread, pragmas, passes, semdata, semtypinst, sigmatch, + procfind, lookups, pragmas, passes, semdata, semtypinst, sigmatch, intsets, transf, vmdef, vm, idgen, aliases, cgmeth, lambdalifting, - evaltempl, patterns, parampatterns, sempass2, nimfix.pretty, semmacrosanity, - semparallel, lowerings, pluginsupport, plugins.active, rod + evaltempl, patterns, parampatterns, sempass2, linter, semmacrosanity, + semparallel, lowerings, pluginsupport, plugins/active, rod, lineinfos from modulegraphs import ModuleGraph when defined(nimfix): - import nimfix.prettybase + import nimfix/prettybase # implementation @@ -33,11 +33,12 @@ proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode proc semProcBody(c: PContext, n: PNode): PNode proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode -proc changeType(n: PNode, newType: PType, check: bool) +proc changeType(c: PContext; n: PNode, newType: PType, check: bool) proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode proc semTypeNode(c: PContext, n: PNode, prev: PType): PType proc semStmt(c: PContext, n: PNode): PNode +proc semOpAux(c: PContext, n: PNode) proc semParamList(c: PContext, n, genericParams: PNode, s: PSym) proc addParams(c: PContext, n: PNode, kind: TSymKind) proc maybeAddResult(c: PContext, s: PSym, n: PNode) @@ -47,15 +48,18 @@ proc semQuoteAst(c: PContext, n: PNode): PNode proc finishMethod(c: PContext, s: PSym) proc evalAtCompileTime(c: PContext, n: PNode): PNode proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode - +proc semStaticExpr(c: PContext, n: PNode): PNode +proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType +proc semTypeOf(c: PContext; n: PNode): PNode +proc hasUnresolvedArgs(c: PContext, n: PNode): bool proc isArrayConstr(n: PNode): bool {.inline.} = result = n.kind == nkBracket and n.typ.skipTypes(abstractInst).kind == tyArray -template semIdeForTemplateOrGenericCheck(n, requiresCheck) = +template semIdeForTemplateOrGenericCheck(conf, n, requiresCheck) = # we check quickly if the node is where the cursor is when defined(nimsuggest): - if n.info.fileIndex == gTrackPos.fileIndex and n.info.line == gTrackPos.line: + if n.info.fileIndex == conf.m.trackPos.fileIndex and n.info.line == conf.m.trackPos.line: requiresCheck = true template semIdeForTemplateOrGeneric(c: PContext; n: PNode; @@ -64,14 +68,24 @@ template semIdeForTemplateOrGeneric(c: PContext; n: PNode; # templates perform some quick check whether the cursor is actually in # the generic or template. when defined(nimsuggest): - if gCmd == cmdIdeTools and requiresCheck: + if c.config.cmd == cmdIdeTools and requiresCheck: #if optIdeDebug in gGlobalOptions: # echo "passing to safeSemExpr: ", renderTree(n) discard safeSemExpr(c, n) +proc fitNodePostMatch(c: PContext, formal: PType, arg: PNode): PNode = + result = arg + let x = result.skipConv + if x.kind in {nkPar, nkTupleConstr, nkCurly} and formal.kind != tyExpr: + changeType(c, x, formal, check=true) + else: + result = skipHiddenSubConv(result) + #result.typ = takeType(formal, arg.typ) + #echo arg.info, " picked ", result.typ.typeToString + proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode = if arg.typ.isNil: - localError(arg.info, errExprXHasNoType, + localError(c.config, arg.info, "expression has no type: " & renderTree(arg, {renderNoComments})) # error correction: result = copyTree(arg) @@ -79,23 +93,17 @@ proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode = else: result = indexTypesMatch(c, formal, arg.typ, arg) if result == nil: - typeMismatch(info, formal, arg.typ) + typeMismatch(c.config, info, formal, arg.typ) # error correction: result = copyTree(arg) result.typ = formal else: - let x = result.skipConv - if x.kind == nkPar and formal.kind != tyExpr: - changeType(x, formal, check=true) - else: - result = skipHiddenSubConv(result) - #result.typ = takeType(formal, arg.typ) - #echo arg.info, " picked ", result.typ.typeToString + result = fitNodePostMatch(c, formal, result) proc inferWithMetatype(c: PContext, formal: PType, arg: PNode, coerceDistincts = false): PNode -var commonTypeBegin = PType(kind: tyExpr) +template commonTypeBegin*(): PType = PType(kind: tyExpr) proc commonType*(x, y: PType): PType = # new type relation that is used for array constructors, @@ -111,7 +119,7 @@ proc commonType*(x, y: PType): PType = elif b.kind == tyStmt: result = b elif a.kind == tyTypeDesc: # turn any concrete typedesc into the abstract typedesc type - if a.sons == nil: result = a + if a.len == 0: result = a else: result = newType(tyTypeDesc, a.owner) rawAddSon(result, newType(tyNone, a.owner)) @@ -153,14 +161,17 @@ proc commonType*(x, y: PType): PType = if a.kind in {tyRef, tyPtr}: k = a.kind if b.kind != a.kind: return x - a = a.lastSon - b = b.lastSon + # bug #7601, array construction of ptr generic + a = a.lastSon.skipTypes({tyGenericInst}) + b = b.lastSon.skipTypes({tyGenericInst}) if a.kind == tyObject and b.kind == tyObject: result = commonSuperclass(a, b) # this will trigger an error later: if result.isNil or result == a: return x if result == b: return y - if k != tyNone: + # bug #7906, tyRef/tyPtr + tyGenericInst of ref/ptr object -> + # ill-formed AST, no need for additional tyRef/tyPtr + if k != tyNone and x.kind != tyGenericInst: let r = result result = newType(k, r.owner) result.addSonSkipIntLit(r) @@ -179,7 +190,7 @@ proc commonType*(x: PType, y: PNode): PType = commonType(x, y.typ) proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym = - result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info) + result = newSym(kind, considerQuotedIdent(c, n), getCurrOwner(c), n.info) when defined(nimsuggest): suggestDecl(c, n, result) @@ -191,7 +202,7 @@ proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym = # and sfGenSym in n.sym.flags: result = n.sym if result.kind != kind: - localError(n.info, "cannot use symbol of kind '" & + localError(c.config, n.info, "cannot use symbol of kind '" & $result.kind & "' as a '" & $kind & "'") if sfGenSym in result.flags and result.kind notin {skTemplate, skMacro, skParam}: # declarative context, so produce a fresh gensym: @@ -203,7 +214,7 @@ proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym = # template; we must fix it here: see #909 result.owner = getCurrOwner(c) else: - result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info) + result = newSym(kind, considerQuotedIdent(c, n), getCurrOwner(c), n.info) #if kind in {skForVar, skLet, skVar} and result.owner.kind == skModule: # incl(result.flags, sfGlobal) when defined(nimsuggest): @@ -215,20 +226,20 @@ proc semIdentVis(c: PContext, kind: TSymKind, n: PNode, proc semIdentWithPragma(c: PContext, kind: TSymKind, n: PNode, allowed: TSymFlags): PSym -proc typeAllowedCheck(info: TLineInfo; typ: PType; kind: TSymKind; +proc typeAllowedCheck(conf: ConfigRef; info: TLineInfo; typ: PType; kind: TSymKind; flags: TTypeAllowedFlags = {}) = let t = typeAllowed(typ, kind, flags) if t != nil: if t == typ: - localError(info, "invalid type: '" & typeToString(typ) & + localError(conf, info, "invalid type: '" & typeToString(typ) & "' for " & substr($kind, 2).toLowerAscii) else: - localError(info, "invalid type: '" & typeToString(t) & + localError(conf, info, "invalid type: '" & typeToString(t) & "' in this context: '" & typeToString(typ) & "' for " & substr($kind, 2).toLowerAscii) proc paramsTypeCheck(c: PContext, typ: PType) {.inline.} = - typeAllowedCheck(typ.n.info, typ, skProc) + typeAllowedCheck(c.config, typ.n.info, typ, skProc) proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode @@ -238,14 +249,14 @@ proc semTemplateExpr(c: PContext, n: PNode, s: PSym, proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, flags: TExprFlags = {}): PNode -proc symFromType(t: PType, info: TLineInfo): PSym = +proc symFromType(c: PContext; t: PType, info: TLineInfo): PSym = if t.sym != nil: return t.sym - result = newSym(skType, getIdent"AnonType", t.owner, info) + result = newSym(skType, getIdent(c.cache, "AnonType"), t.owner, info) result.flags.incl sfAnon result.typ = t proc symNodeFromType(c: PContext, t: PType, info: TLineInfo): PNode = - result = newSymNode(symFromType(t, info), info) + result = newSymNode(symFromType(c, t, info), info) result.typ = makeTypeDesc(c, t) when false: @@ -281,10 +292,10 @@ proc fixupTypeAfterEval(c: PContext, evaluated, eOrig: PNode): PNode = result = evaluated let expectedType = eOrig.typ.skipTypes({tyStatic}) if hasCycle(result): - globalError(eOrig.info, "the resulting AST is cyclic and cannot be processed further") + globalError(c.config, eOrig.info, "the resulting AST is cyclic and cannot be processed further") result = errorNode(c, eOrig) else: - semmacrosanity.annotateType(result, expectedType) + semmacrosanity.annotateType(result, expectedType, c.config) else: result = semExprWithType(c, evaluated) #result = fitNode(c, e.typ, result) inlined with special case: @@ -301,18 +312,18 @@ proc tryConstExpr(c: PContext, n: PNode): PNode = var e = semExprWithType(c, n) if e == nil: return - result = getConstExpr(c.module, e) + result = getConstExpr(c.module, e, c.graph) if result != nil: return - let oldErrorCount = msgs.gErrorCounter - let oldErrorMax = msgs.gErrorMax - let oldErrorOutputs = errorOutputs + let oldErrorCount = c.config.errorCounter + let oldErrorMax = c.config.errorMax + let oldErrorOutputs = c.config.m.errorOutputs - errorOutputs = {} - msgs.gErrorMax = high(int) + c.config.m.errorOutputs = {} + c.config.errorMax = high(int) try: - result = evalConstExpr(c.module, c.cache, e) + result = evalConstExpr(c.module, c.graph, e) if result == nil or result.kind == nkEmpty: result = nil else: @@ -321,26 +332,29 @@ proc tryConstExpr(c: PContext, n: PNode): PNode = except ERecoverableError: result = nil - msgs.gErrorCounter = oldErrorCount - msgs.gErrorMax = oldErrorMax - errorOutputs = oldErrorOutputs + c.config.errorCounter = oldErrorCount + c.config.errorMax = oldErrorMax + c.config.m.errorOutputs = oldErrorOutputs + +const + errConstExprExpected = "constant expression expected" proc semConstExpr(c: PContext, n: PNode): PNode = var e = semExprWithType(c, n) if e == nil: - localError(n.info, errConstExprExpected) + localError(c.config, n.info, errConstExprExpected) return n - result = getConstExpr(c.module, e) + result = getConstExpr(c.module, e, c.graph) if result == nil: #if e.kind == nkEmpty: globalError(n.info, errConstExprExpected) - result = evalConstExpr(c.module, c.cache, e) + result = evalConstExpr(c.module, c.graph, e) if result == nil or result.kind == nkEmpty: if e.info != n.info: - pushInfoContext(n.info) - localError(e.info, errConstExprExpected) - popInfoContext() + pushInfoContext(c.config, n.info) + localError(c.config, e.info, errConstExprExpected) + popInfoContext(c.config) else: - localError(e.info, errConstExprExpected) + localError(c.config, e.info, errConstExprExpected) # error correction: result = e else: @@ -355,7 +369,7 @@ proc semExprFlagDispatched(c: PContext, n: PNode, flags: TExprFlags): PNode = else: result = semExprWithType(c, n, flags) if efPreferStatic in flags: - var evaluated = getConstExpr(c.module, result) + var evaluated = getConstExpr(c.module, result, c.graph) if evaluated != nil: return evaluated evaluated = evalAtCompileTime(c, result) if evaluated != nil: return evaluated @@ -376,9 +390,9 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode, ## coherence, making sure that variables declared with 'let' aren't ## reassigned, and binding the unbound identifiers that the macro output ## contains. - inc(evalTemplateCounter) - if evalTemplateCounter > 100: - globalError(s.info, errTemplateInstantiationTooNested) + inc(c.config.evalTemplateCounter) + if c.config.evalTemplateCounter > evalTemplateLimit: + globalError(c.config, s.info, "template instantiation too nested") c.friendModules.add(s.owner.getModule) result = macroResult @@ -398,7 +412,12 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode, of tyTypeDesc: if result.kind == nkStmtList: result.kind = nkStmtListType var typ = semTypeNode(c, result, nil) - result.typ = makeTypeDesc(c, typ) + if typ == nil: + localError(c.config, result.info, "expression has no type: " & + renderTree(result, {renderNoComments})) + result = newSymNode(errorSym(c, result)) + else: + result.typ = makeTypeDesc(c, typ) #result = symNodeFromType(c, typ, n.info) else: var retType = s.typ.sons[0] @@ -416,47 +435,50 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode, result = semExpr(c, result, flags) result = fitNode(c, retType, result, result.info) - #GlobalError(s.info, errInvalidParamKindX, typeToString(s.typ.sons[0])) - dec(evalTemplateCounter) + #globalError(s.info, errInvalidParamKindX, typeToString(s.typ.sons[0])) + dec(c.config.evalTemplateCounter) discard c.friendModules.pop() +const + errMissingGenericParamsForTemplate = "'$1' has unspecified generic parameters" + proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, flags: TExprFlags = {}): PNode = - pushInfoContext(nOrig.info) + pushInfoContext(c.config, nOrig.info) - markUsed(n.info, sym, c.graph.usageSym) + markUsed(c.config, n.info, sym, c.graph.usageSym) styleCheckUse(n.info, sym) if sym == c.p.owner: - globalError(n.info, errRecursiveDependencyX, sym.name.s) + globalError(c.config, n.info, "recursive dependency: '$1'" % sym.name.s) let genericParams = if sfImmediate in sym.flags: 0 else: sym.ast[genericParamsPos].len let suppliedParams = max(n.safeLen - 1, 0) if suppliedParams < genericParams: - globalError(n.info, errMissingGenericParamsForTemplate, n.renderTree) + globalError(c.config, n.info, errMissingGenericParamsForTemplate % n.renderTree) #if c.evalContext == nil: # c.evalContext = c.createEvalContext(emStatic) - result = evalMacroCall(c.module, c.cache, n, nOrig, sym) + result = evalMacroCall(c.module, c.graph, n, nOrig, sym) if efNoSemCheck notin flags: result = semAfterMacroCall(c, n, result, sym, flags) result = wrapInComesFrom(nOrig.info, sym, result) - popInfoContext() + popInfoContext(c.config) proc forceBool(c: PContext, n: PNode): PNode = - result = fitNode(c, getSysType(tyBool), n, n.info) + result = fitNode(c, getSysType(c.graph, n.info, tyBool), n, n.info) if result == nil: result = n proc semConstBoolExpr(c: PContext, n: PNode): PNode = let nn = semExprWithType(c, n) - result = fitNode(c, getSysType(tyBool), nn, nn.info) + result = fitNode(c, getSysType(c.graph, n.info, tyBool), nn, nn.info) if result == nil: - localError(n.info, errConstExprExpected) + localError(c.config, n.info, errConstExprExpected) return nn - result = getConstExpr(c.module, result) + result = getConstExpr(c.module, result, c.graph) if result == nil: - localError(n.info, errConstExprExpected) + localError(c.config, n.info, errConstExprExpected) result = nn proc semGenericStmt(c: PContext, n: PNode): PNode @@ -469,14 +491,14 @@ proc addCodeForGenerics(c: PContext, n: PNode) = var prc = c.generics[i].inst.sym if prc.kind in {skProc, skFunc, skMethod, skConverter} and prc.magic == mNone: if prc.ast == nil or prc.ast.sons[bodyPos] == nil: - internalError(prc.info, "no code for " & prc.name.s) + internalError(c.config, prc.info, "no code for " & prc.name.s) else: addSon(n, prc.ast) c.lastGenericIdx = c.generics.len -proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = - var c = newContext(graph, module, cache) - if c.p != nil: internalError(module.info, "sem.myOpen") +proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = + var c = newContext(graph, module) + if c.p != nil: internalError(graph.config, module.info, "sem.myOpen") c.semConstExpr = semConstExpr c.semExpr = semExpr c.semTryExpr = tryExpr @@ -494,46 +516,40 @@ proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = c.importTable = openScope(c) c.importTable.addSym(module) # a module knows itself if sfSystemModule in module.flags: - magicsys.systemModule = module # set global variable! + graph.systemModule = module c.topLevelScope = openScope(c) # don't be verbose unless the module belongs to the main package: - if module.owner.id == gMainPackageId: - gNotes = gMainPackageNotes + if module.owner.id == graph.config.mainPackageId: + graph.config.notes = graph.config.mainPackageNotes else: - if gMainPackageNotes == {}: gMainPackageNotes = gNotes - gNotes = ForeignPackageNotes + if graph.config.mainPackageNotes == {}: graph.config.mainPackageNotes = graph.config.notes + graph.config.notes = graph.config.foreignPackageNotes result = c -proc myOpenCached(graph: ModuleGraph; module: PSym; rd: PRodReader): PPassContext = - result = myOpen(graph, module, rd.cache) - -proc replayMethodDefs(graph: ModuleGraph; rd: PRodReader) = - for m in items(rd.methods): methodDef(graph, m, true) - -proc isImportSystemStmt(n: PNode): bool = - if magicsys.systemModule == nil: return false +proc isImportSystemStmt(g: ModuleGraph; n: PNode): bool = + if g.systemModule == nil: return false case n.kind of nkImportStmt: for x in n: if x.kind == nkIdent: - let f = checkModuleName(x, false) - if f == magicsys.systemModule.info.fileIndex: + let f = checkModuleName(g.config, x, false) + if f == g.systemModule.info.fileIndex: return true of nkImportExceptStmt, nkFromStmt: if n[0].kind == nkIdent: - let f = checkModuleName(n[0], false) - if f == magicsys.systemModule.info.fileIndex: + let f = checkModuleName(g.config, n[0], false) + if f == g.systemModule.info.fileIndex: return true else: discard proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode = if n.kind == nkDefer: - localError(n.info, "defer statement not supported at top level") - if c.topStmts == 0 and not isImportSystemStmt(n): + localError(c.config, n.info, "defer statement not supported at top level") + if c.topStmts == 0 and not isImportSystemStmt(c.graph, n): if sfSystemModule notin c.module.flags and n.kind notin {nkEmpty, nkCommentStmt}: - c.importTable.addSym magicsys.systemModule # import the "System" identifier - importAllSymbols(c, magicsys.systemModule) + c.importTable.addSym c.graph.systemModule # import the "System" identifier + importAllSymbols(c, c.graph.systemModule) inc c.topStmts else: inc c.topStmts @@ -555,11 +571,11 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode = if result.kind != nkEmpty: addSon(a, result) result = a result = hloStmt(c, result) - if gCmd == cmdInteractive and not isEmptyType(result.typ): + if c.config.cmd == cmdInteractive and not isEmptyType(result.typ): result = buildEchoStmt(c, result) - if gCmd == cmdIdeTools: + if c.config.cmd == cmdIdeTools: appendToModule(c.module, result) - result = transformStmt(c.module, result) + result = transformStmt(c.graph, c.module, result) proc recoverContext(c: PContext) = # clean up in case of a semantic error: We clean up the stacks, etc. This is @@ -572,55 +588,40 @@ proc recoverContext(c: PContext) = proc myProcess(context: PPassContext, n: PNode): PNode = var c = PContext(context) # no need for an expensive 'try' if we stop after the first error anyway: - if msgs.gErrorMax <= 1: + if c.config.errorMax <= 1: result = semStmtAndGenerateGenerics(c, n) else: - let oldContextLen = msgs.getInfoContextLen() + let oldContextLen = msgs.getInfoContextLen(c.config) let oldInGenericInst = c.inGenericInst try: result = semStmtAndGenerateGenerics(c, n) except ERecoverableError, ESuggestDone: recoverContext(c) c.inGenericInst = oldInGenericInst - msgs.setInfoContextLen(oldContextLen) + msgs.setInfoContextLen(c.config, oldContextLen) if getCurrentException() of ESuggestDone: c.suggestionsMade = true result = nil else: - result = ast.emptyNode - #if gCmd == cmdIdeTools: findSuggest(c, n) - rod.storeNode(c.module, result) - -proc testExamples(c: PContext) = - let inp = toFullPath(c.module.info) - let outp = inp.changeFileExt"" & "_examples.nim" - renderModule(c.runnableExamples, inp, outp) - let backend = if isDefined("js"): "js" - elif isDefined("cpp"): "cpp" - elif isDefined("objc"): "objc" - else: "c" - if os.execShellCmd("nim " & backend & " -r " & outp) != 0: - quit "[Examples] failed" - removeFile(outp) + result = newNodeI(nkEmpty, n.info) + #if c.config.cmd == cmdIdeTools: findSuggest(c, n) + rod.storeNode(c.graph, c.module, result) proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode = var c = PContext(context) - if gCmd == cmdIdeTools and not c.suggestionsMade: + if c.config.cmd == cmdIdeTools and not c.suggestionsMade: suggestSentinel(c) closeScope(c) # close module's scope rawCloseScope(c) # imported symbols; don't check for unused ones! result = newNode(nkStmtList) if n != nil: - internalError(n.info, "n is not nil") #result := n; + internalError(c.config, n.info, "n is not nil") #result := n; addCodeForGenerics(c, result) if c.module.ast != nil: result.add(c.module.ast) - if c.rd != nil: - replayMethodDefs(graph, c.rd) popOwner(c) popProcCon(c) - storeRemaining(c.module) - if c.runnableExamples != nil: testExamples(c) + storeRemaining(c.graph, c.module) -const semPass* = makePass(myOpen, myOpenCached, myProcess, myClose, +const semPass* = makePass(myOpen, myProcess, myClose, isFrontend = true) diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim index bbd2baf6e..8b2e20efc 100644 --- a/compiler/semasgn.nim +++ b/compiler/semasgn.nim @@ -33,7 +33,7 @@ proc at(a, i: PNode, elemType: PType): PNode = proc liftBodyTup(c: var TLiftCtx; t: PType; body, x, y: PNode) = for i in 0 ..< t.len: - let lit = lowerings.newIntLit(i) + let lit = lowerings.newIntLit(c.c.graph, x.info, i) liftBodyAux(c, t.sons[i], body, x.at(lit, t.sons[i]), y.at(lit, t.sons[i])) proc dotField(x: PNode, f: PSym): PNode = @@ -66,15 +66,15 @@ proc liftBodyObj(c: var TLiftCtx; n, body, x, y: PNode) = liftBodyObj(c, n[i].lastSon, branch.sons[L-1], x, y) caseStmt.add(branch) body.add(caseStmt) - localError(c.info, "cannot lift assignment operator to 'case' object") + localError(c.c.config, c.info, "cannot lift assignment operator to 'case' object") of nkRecList: for t in items(n): liftBodyObj(c, t, body, x, y) else: - illFormedAstLocal(n) + illFormedAstLocal(n, c.c.config) proc genAddr(c: PContext; x: PNode): PNode = if x.kind == nkHiddenDeref: - checkSonsLen(x, 1) + checkSonsLen(x, 1, c.config) result = x.sons[0] else: result = newNodeIT(nkHiddenAddr, x.info, makeVarType(c, x.typ)) @@ -82,7 +82,7 @@ proc genAddr(c: PContext; x: PNode): PNode = proc newAsgnCall(c: PContext; op: PSym; x, y: PNode): PNode = if sfError in op.flags: - localError(x.info, errWrongSymbolX, op.name.s) + localError(c.config, x.info, "usage of '$1' is a user-defined error" % op.name.s) result = newNodeI(nkCall, x.info) result.add newSymNode(op) result.add genAddr(c, x) @@ -101,10 +101,7 @@ proc newOpCall(op: PSym; x: PNode): PNode = proc destructorCall(c: PContext; op: PSym; x: PNode): PNode = result = newNodeIT(nkCall, x.info, op.typ.sons[0]) result.add(newSymNode(op)) - if newDestructors: - result.add genAddr(c, x) - else: - result.add x + result.add genAddr(c, x) proc newDeepCopyCall(op: PSym; x, y: PNode): PNode = result = newAsgnStmt(x, newOpCall(op, y)) @@ -124,7 +121,7 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode; op = field if op == nil: op = liftBody(c.c, t, c.kind, c.info) - markUsed(c.info, op, c.c.graph.usageSym) + markUsed(c.c.config, c.info, op, c.c.graph.usageSym) styleCheckUse(c.info, op) body.add newAsgnCall(c.c, op, x, y) result = true @@ -134,7 +131,7 @@ proc considerOverloadedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool = of attachedDestructor: let op = t.destructor if op != nil: - markUsed(c.info, op, c.c.graph.usageSym) + markUsed(c.c.config, c.info, op, c.c.graph.usageSym) styleCheckUse(c.info, op) body.add destructorCall(c.c, op, x) result = true @@ -145,7 +142,7 @@ proc considerOverloadedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool = of attachedDeepCopy: let op = t.deepCopy if op != nil: - markUsed(c.info, op, c.c.graph.usageSym) + markUsed(c.c.config, c.info, op, c.c.graph.usageSym) styleCheckUse(c.info, op) body.add newDeepCopyCall(op, x, y) result = true @@ -157,65 +154,83 @@ proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = proc addVar(father, v, value: PNode) = var vpart = newNodeI(nkIdentDefs, v.info, 3) vpart.sons[0] = v - vpart.sons[1] = ast.emptyNode + vpart.sons[1] = newNodeI(nkEmpty, v.info) vpart.sons[2] = value addSon(father, vpart) proc declareCounter(c: var TLiftCtx; body: PNode; first: BiggestInt): PNode = - var temp = newSym(skTemp, getIdent(lowerings.genPrefix), c.fn, c.info) - temp.typ = getSysType(tyInt) + var temp = newSym(skTemp, getIdent(c.c.cache, lowerings.genPrefix), c.fn, c.info) + temp.typ = getSysType(c.c.graph, body.info, tyInt) incl(temp.flags, sfFromGeneric) var v = newNodeI(nkVarSection, c.info) result = newSymNode(temp) - v.addVar(result, lowerings.newIntLit(first)) + v.addVar(result, lowerings.newIntLit(c.c.graph, body.info, first)) body.add v -proc genBuiltin(magic: TMagic; name: string; i: PNode): PNode = +proc genBuiltin(g: ModuleGraph; magic: TMagic; name: string; i: PNode): PNode = result = newNodeI(nkCall, i.info) - result.add createMagic(name, magic).newSymNode + result.add createMagic(g, name, magic).newSymNode result.add i proc genWhileLoop(c: var TLiftCtx; i, dest: PNode): PNode = result = newNodeI(nkWhileStmt, c.info, 2) - let cmp = genBuiltin(mLeI, "<=", i) - cmp.add genHigh(dest) - cmp.typ = getSysType(tyBool) + let cmp = genBuiltin(c.c.graph, mLeI, "<=", i) + cmp.add genHigh(c.c.graph, dest) + cmp.typ = getSysType(c.c.graph, c.info, tyBool) result.sons[0] = cmp result.sons[1] = newNodeI(nkStmtList, c.info) -proc addIncStmt(body, i: PNode) = - let incCall = genBuiltin(mInc, "inc", i) - incCall.add lowerings.newIntLit(1) +proc addIncStmt(c: var TLiftCtx; body, i: PNode) = + let incCall = genBuiltin(c.c.graph, mInc, "inc", i) + incCall.add lowerings.newIntLit(c.c.graph, c.info, 1) body.add incCall proc newSeqCall(c: PContext; x, y: PNode): PNode = # don't call genAddr(c, x) here: - result = genBuiltin(mNewSeq, "newSeq", x) - let lenCall = genBuiltin(mLengthSeq, "len", y) - lenCall.typ = getSysType(tyInt) + result = genBuiltin(c.graph, mNewSeq, "newSeq", x) + let lenCall = genBuiltin(c.graph, mLengthSeq, "len", y) + lenCall.typ = getSysType(c.graph, x.info, tyInt) result.add lenCall proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = case t.kind of tyNone, tyEmpty, tyVoid: discard of tyPointer, tySet, tyBool, tyChar, tyEnum, tyInt..tyUInt64, tyCString, - tyPtr, tyString, tyRef, tyOpt: + tyPtr, tyRef, tyOpt: defaultOp(c, t, body, x, y) - of tyArray, tySequence: + of tyArray: if {tfHasAsgn, tfUncheckedArray} * t.flags == {tfHasAsgn}: - if t.kind == tySequence: - # XXX add 'nil' handling here - body.add newSeqCall(c.c, x, y) - let i = declareCounter(c, body, firstOrd(t)) + let i = declareCounter(c, body, firstOrd(c.c.config, t)) let whileLoop = genWhileLoop(c, i, x) let elemType = t.lastSon liftBodyAux(c, elemType, whileLoop.sons[1], x.at(i, elemType), y.at(i, elemType)) - addIncStmt(whileLoop.sons[1], i) + addIncStmt(c, whileLoop.sons[1], i) body.add whileLoop else: defaultOp(c, t, body, x, y) + of tySequence: + # note that tfHasAsgn is propagated so we need the check on + # 'selectedGC' here to determine if we have the new runtime. + if c.c.config.selectedGC == gcDestructors: + discard considerOverloadedOp(c, t, body, x, y) + elif tfHasAsgn in t.flags: + body.add newSeqCall(c.c, x, y) + let i = declareCounter(c, body, firstOrd(c.c.config, t)) + let whileLoop = genWhileLoop(c, i, x) + let elemType = t.lastSon + liftBodyAux(c, elemType, whileLoop.sons[1], x.at(i, elemType), + y.at(i, elemType)) + addIncStmt(c, whileLoop.sons[1], i) + body.add whileLoop + else: + defaultOp(c, t, body, x, y) + of tyString: + if tfHasAsgn in t.flags: + discard considerOverloadedOp(c, t, body, x, y) + else: + defaultOp(c, t, body, x, y) of tyObject, tyDistinct: if not considerOverloadedOp(c, t, body, x, y): if t.sons[0] != nil: @@ -231,20 +246,20 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = # have to go through some indirection; we delegate this to the codegen: let call = newNodeI(nkCall, c.info, 2) call.typ = t - call.sons[0] = newSymNode(createMagic("deepCopy", mDeepCopy)) + call.sons[0] = newSymNode(createMagic(c.c.graph, "deepCopy", mDeepCopy)) call.sons[1] = y body.add newAsgnStmt(x, call) of tyVarargs, tyOpenArray: - localError(c.info, errGenerated, "cannot copy openArray") + localError(c.c.config, c.info, "cannot copy openArray") of tyFromExpr, tyProxy, tyBuiltInTypeClass, tyUserTypeClass, tyUserTypeClassInst, tyCompositeTypeClass, tyAnd, tyOr, tyNot, tyAnything, tyGenericParam, tyGenericBody, tyNil, tyExpr, tyStmt, tyTypeDesc, tyGenericInvocation, tyForward: - internalError(c.info, "assignment requested for type: " & typeToString(t)) + internalError(c.c.config, c.info, "assignment requested for type: " & typeToString(t)) of tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyVar, tyLent, tyAlias, tySink: liftBodyAux(c, lastSon(t), body, x, y) - of tyUnused, tyOptAsRef: internalError("liftBodyAux") + of tyUnused, tyOptAsRef: internalError(c.c.config, "liftBodyAux") proc newProcType(info: TLineInfo; owner: PSym): PType = result = newType(tyProc, owner) @@ -268,17 +283,17 @@ proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp; a.kind = kind let body = newNodeI(nkStmtList, info) let procname = case kind - of attachedAsgn: getIdent"=" - of attachedSink: getIdent"=sink" - of attachedDeepCopy: getIdent"=deepcopy" - of attachedDestructor: getIdent"=destroy" + of attachedAsgn: getIdent(c.cache, "=") + of attachedSink: getIdent(c.cache, "=sink") + of attachedDeepCopy: getIdent(c.cache, "=deepcopy") + of attachedDestructor: getIdent(c.cache, "=destroy") result = newSym(skProc, procname, typ.owner, info) a.fn = result a.asgnForType = typ - let dest = newSym(skParam, getIdent"dest", result, info) - let src = newSym(skParam, getIdent"src", result, info) + let dest = newSym(skParam, getIdent(c.cache, "dest"), result, info) + let src = newSym(skParam, getIdent(c.cache, "src"), result, info) dest.typ = makeVarType(c, typ) src.typ = typ @@ -297,7 +312,7 @@ proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp; of attachedDestructor: typ.destructor = result var n = newNodeI(nkProcDef, info, bodyPos+1) - for i in 0 ..< n.len: n.sons[i] = emptyNode + for i in 0 ..< n.len: n.sons[i] = newNodeI(nkEmpty, info) n.sons[namePos] = newSymNode(result) n.sons[paramsPos] = result.typ.n n.sons[bodyPos] = body @@ -319,7 +334,7 @@ proc liftTypeBoundOps*(c: PContext; typ: PType; info: TLineInfo) = ## In the semantic pass this is called in strategic places ## to ensure we lift assignment, destructors and moves properly. ## The later 'destroyer' pass depends on it. - if not newDestructors or not hasDestructor(typ): return + if not hasDestructor(typ): return when false: # do not produce wrong liftings while we're still instantiating generics: # now disabled; breaks topttree.nim! diff --git a/compiler/semcall.nim b/compiler/semcall.nim index ba38ed215..53f7045dd 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -59,7 +59,8 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, filter: TSymKinds, best, alt: var TCandidate, errors: var CandidateErrors, - diagnosticsFlag = false) = + diagnosticsFlag: bool, + errorsEnabled: bool) = var o: TOverloadIter var sym = initOverloadIter(o, c, headSymbol) var scope = o.lastOverloadScope @@ -68,6 +69,7 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, # This can occur in cases like 'init(a, 1, (var b = new(Type2); b))' let counterInitial = c.currentScope.symbols.counter var syms: seq[tuple[s: PSym, scope: int]] + var noSyms = true var nextSymIndex = 0 while sym != nil: if sym.kind in filter: @@ -87,7 +89,7 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, continue determineType(c, sym) initCandidate(c, z, sym, initialBinding, scope, diagnosticsFlag) - if c.currentScope.symbols.counter == counterInitial or syms != nil: + if c.currentScope.symbols.counter == counterInitial or syms.len != 0: matches(c, n, orig, z) if z.state == csMatch: #if sym.name.s == "==" and (n.info ?? "temp3"): @@ -102,7 +104,7 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, var cmp = cmpCandidates(best, z) if cmp < 0: best = z # x is better than the best so far elif cmp == 0: alt = z # x is as good as the best so far - elif errors != nil or z.diagnostics != nil: + elif errorsEnabled or z.diagnosticsEnabled: errors.safeAdd(CandidateError( sym: sym, unmatchedVarParam: int z.mutabilityProblem, @@ -113,7 +115,8 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, # before any further candidate init and compare. SLOW, but rare case. syms = initCandidateSymbols(c, headSymbol, initialBinding, filter, best, alt, o, diagnosticsFlag) - if syms == nil: + noSyms = false + if noSyms: sym = nextOverloadIter(o, c, headSymbol) scope = o.lastOverloadScope elif nextSymIndex < syms.len: @@ -135,7 +138,9 @@ proc effectProblem(f, a: PType; result: var string) = proc renderNotLValue(n: PNode): string = result = $n - if n.kind in {nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv} and n.len == 2: + if n.kind == nkHiddenCallConv and n.len > 1: + result = $n[0] & "(" & result & ")" + elif n.kind in {nkHiddenStdConv, nkHiddenSubConv} and n.len == 2: result = typeToString(n.typ.skipTypes(abstractVar)) & "(" & result & ")" proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors): @@ -161,26 +166,41 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors): prefer = preferModuleInfo break + when false: + # we pretend procs are attached to the type of the first + # argument in order to remove plenty of candidates. This is + # comparable to what C# does and C# is doing fine. + var filterOnlyFirst = false + for err in errors: + if err.firstMismatch > 1: + filterOnlyFirst = true + break + var candidates = "" for err in errors: + when false: + if filterOnlyFirst and err.firstMismatch == 1: continue if err.sym.kind in routineKinds and err.sym.ast != nil: add(candidates, renderTree(err.sym.ast, {renderNoBody, renderNoComments, renderNoPragmas})) else: - add(candidates, err.sym.getProcHeader(prefer)) + add(candidates, getProcHeader(c.config, err.sym, prefer)) add(candidates, "\n") if err.firstMismatch != 0 and n.len > 1: let cond = n.len > 2 if cond: - candidates.add(" first type mismatch at position: " & $err.firstMismatch & - "\n required type: ") + candidates.add(" first type mismatch at position: " & $abs(err.firstMismatch)) + if err.firstMismatch >= 0: candidates.add("\n required type: ") + else: candidates.add("\n unknown named parameter: " & $n[-err.firstMismatch][0]) var wanted, got: PType = nil - if err.firstMismatch < err.sym.typ.len: + if err.firstMismatch < 0: + discard + elif err.firstMismatch < err.sym.typ.len: wanted = err.sym.typ.sons[err.firstMismatch] if cond: candidates.add typeToString(wanted) else: if cond: candidates.add "none" - if err.firstMismatch < n.len: + if err.firstMismatch > 0 and err.firstMismatch < n.len: if cond: candidates.add "\n but expression '" candidates.add renderTree(n[err.firstMismatch]) @@ -190,33 +210,40 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors): if wanted != nil and got != nil: effectProblem(wanted, got, candidates) if cond: candidates.add "\n" - elif err.unmatchedVarParam != 0 and err.unmatchedVarParam < n.len: - add(candidates, "for a 'var' type a variable needs to be passed, but '" & + if err.unmatchedVarParam != 0 and err.unmatchedVarParam < n.len: + candidates.add(" for a 'var' type a variable needs to be passed, but '" & renderNotLValue(n[err.unmatchedVarParam]) & "' is immutable\n") for diag in err.diagnostics: - add(candidates, diag & "\n") + candidates.add(diag & "\n") result = (prefer, candidates) +const + errTypeMismatch = "type mismatch: got <" + errButExpected = "but expected one of: " + errUndeclaredField = "undeclared field: '$1'" + errUndeclaredRoutine = "attempting to call undeclared routine: '$1'" + errAmbiguousCallXYZ = "ambiguous call; both $1 and $2 match for: $3" + proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) = # Gives a detailed error message; this is separated from semOverloadedCall, # as semOverlodedCall is already pretty slow (and we need this information # only in case of an error). - if errorOutputs == {}: + if c.config.m.errorOutputs == {}: # fail fast: - globalError(n.info, errTypeMismatch, "") - if errors.isNil or errors.len == 0: - localError(n.info, errExprXCannotBeCalled, n[0].renderTree) + globalError(c.config, n.info, "type mismatch") + if errors.len == 0: + localError(c.config, n.info, "expression '$1' cannot be called" % n[0].renderTree) return let (prefer, candidates) = presentFailedCandidates(c, n, errors) - var result = msgKindToString(errTypeMismatch) + var result = errTypeMismatch add(result, describeArgs(c, n, 1, prefer)) add(result, '>') if candidates != "": - add(result, "\n" & msgKindToString(errButExpected) & "\n" & candidates) - localError(n.info, errGenerated, result & "\nexpression: " & $n) + add(result, "\n" & errButExpected & "\n" & candidates) + localError(c.config, n.info, result & "\nexpression: " & $n) proc bracketNotFoundError(c: PContext; n: PNode) = var errors: CandidateErrors = @[] @@ -227,21 +254,24 @@ proc bracketNotFoundError(c: PContext; n: PNode) = if symx.kind in routineKinds: errors.add(CandidateError(sym: symx, unmatchedVarParam: 0, firstMismatch: 0, - diagnostics: nil)) + diagnostics: @[], + enabled: false)) symx = nextOverloadIter(o, c, headSymbol) if errors.len == 0: - localError(n.info, "could not resolve: " & $n) + localError(c.config, n.info, "could not resolve: " & $n) else: notFoundError(c, n, errors) proc resolveOverloads(c: PContext, n, orig: PNode, filter: TSymKinds, flags: TExprFlags, - errors: var CandidateErrors): TCandidate = + errors: var CandidateErrors, + errorsEnabled: bool): TCandidate = var initialBinding: PNode var alt: TCandidate var f = n.sons[0] if f.kind == nkBracketExpr: # fill in the bindings: + semOpAux(c, f) initialBinding = f f = f.sons[0] else: @@ -249,7 +279,8 @@ proc resolveOverloads(c: PContext, n, orig: PNode, template pickBest(headSymbol) = pickBestCandidate(c, headSymbol, n, orig, initialBinding, - filter, result, alt, errors, efExplain in flags) + filter, result, alt, errors, efExplain in flags, + errorsEnabled) pickBest(f) let overloadsState = result.state @@ -271,7 +302,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode, else: return if nfDotField in n.flags: - internalAssert f.kind == nkIdent and n.sonsLen >= 2 + internalAssert c.config, f.kind == nkIdent and n.len >= 2 # leave the op head symbol empty, # we are going to try multiple variants @@ -279,7 +310,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode, orig.sons[0..1] = [nil, orig[1], f] template tryOp(x) = - let op = newIdentNode(getIdent(x), n.info) + let op = newIdentNode(getIdent(c.cache, x), n.info) n.sons[0] = op orig.sons[0] = op pickBest(op) @@ -292,21 +323,21 @@ proc resolveOverloads(c: PContext, n, orig: PNode, elif nfDotSetter in n.flags and f.kind == nkIdent and n.len == 3: # we need to strip away the trailing '=' here: - let calleeName = newIdentNode(getIdent(f.ident.s[0..f.ident.s.len-2]), n.info) - let callOp = newIdentNode(getIdent".=", n.info) + let calleeName = newIdentNode(getIdent(c.cache, f.ident.s[0..f.ident.s.len-2]), n.info) + let callOp = newIdentNode(getIdent(c.cache, ".="), n.info) n.sons[0..1] = [callOp, n[1], calleeName] orig.sons[0..1] = [callOp, orig[1], calleeName] pickBest(callOp) if overloadsState == csEmpty and result.state == csEmpty: if nfDotField in n.flags and nfExplicitCall notin n.flags: - localError(n.info, errUndeclaredField, considerQuotedIdent(f, n).s) + localError(c.config, n.info, errUndeclaredField % considerQuotedIdent(c, f, n).s) else: - localError(n.info, errUndeclaredRoutine, considerQuotedIdent(f, n).s) + localError(c.config, n.info, errUndeclaredRoutine % considerQuotedIdent(c, f, n).s) return elif result.state != csMatch: if nfExprCall in n.flags: - localError(n.info, errExprXCannotBeCalled, + localError(c.config, n.info, "expression '$1' cannot be called" % renderTree(n, {renderNoComments})) else: if {nfDotField, nfDotSetter} * n.flags != {}: @@ -316,13 +347,13 @@ proc resolveOverloads(c: PContext, n, orig: PNode, return if alt.state == csMatch and cmpCandidates(result, alt) == 0 and not sameMethodDispatcher(result.calleeSym, alt.calleeSym): - internalAssert result.state == csMatch + internalAssert c.config, result.state == csMatch #writeMatches(result) #writeMatches(alt) - if errorOutputs == {}: + if c.config.m.errorOutputs == {}: # quick error message for performance of 'compiles' built-in: - globalError(n.info, errGenerated, "ambiguous call") - elif gErrorCounter == 0: + globalError(c.config, n.info, errGenerated, "ambiguous call") + elif c.config.errorCounter == 0: # don't cascade errors var args = "(" for i in countup(1, sonsLen(n) - 1): @@ -330,8 +361,9 @@ proc resolveOverloads(c: PContext, n, orig: PNode, add(args, typeToString(n.sons[i].typ)) add(args, ")") - localError(n.info, errGenerated, msgKindToString(errAmbiguousCallXYZ) % [ - getProcHeader(result.calleeSym), getProcHeader(alt.calleeSym), + localError(c.config, n.info, errAmbiguousCallXYZ % [ + getProcHeader(c.config, result.calleeSym), + getProcHeader(c.config, alt.calleeSym), args]) proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) = @@ -371,15 +403,30 @@ proc inferWithMetatype(c: PContext, formal: PType, result.typ = generateTypeInstance(c, m.bindings, arg.info, formal.skipTypes({tyCompositeTypeClass})) else: - typeMismatch(arg.info, formal, arg.typ) + typeMismatch(c.config, arg.info, formal, arg.typ) # error correction: result = copyTree(arg) result.typ = formal -proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode = +proc updateDefaultParams(call: PNode) = + # In generic procs, the default parameter may be unique for each + # instantiation (see tlateboundgenericparams). + # After a call is resolved, we need to re-assign any default value + # that was used during sigmatch. sigmatch is responsible for marking + # the default params with `nfDefaultParam` and `instantiateProcType` + # computes correctly the default values for each instantiation. + let calleeParams = call[0].sym.typ.n + for i in 1..<call.len: + if nfDefaultParam in call[i].flags: + let def = calleeParams[i].sym.ast + if nfDefaultRefsParam in def.flags: call.flags.incl nfDefaultRefsParam + call[i] = def + +proc semResolvedCall(c: PContext, x: TCandidate, + n: PNode, flags: TExprFlags): PNode = assert x.state == csMatch var finalCallee = x.calleeSym - markUsed(n.sons[0].info, finalCallee, c.graph.usageSym) + markUsed(c.config, n.sons[0].info, finalCallee, c.graph.usageSym) styleCheckUse(n.sons[0].info, finalCallee) assert finalCallee.ast != nil if x.hasFauxMatch: @@ -405,12 +452,13 @@ proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode = of skType: x.call.add newSymNode(s, n.info) else: - internalAssert false + internalAssert c.config, false result = x.call instGenericConvertersSons(c, result, x) - result.sons[0] = newSymNode(finalCallee, result.sons[0].info) + result[0] = newSymNode(finalCallee, result[0].info) result.typ = finalCallee.typ.sons[0] + updateDefaultParams(result) proc canDeref(n: PNode): bool {.inline.} = result = n.len >= 2 and (let t = n[1].typ; @@ -423,18 +471,17 @@ proc tryDeref(n: PNode): PNode = proc semOverloadedCall(c: PContext, n, nOrig: PNode, filter: TSymKinds, flags: TExprFlags): PNode = - var errors: CandidateErrors = if efExplain in flags: @[] - else: nil - var r = resolveOverloads(c, n, nOrig, filter, flags, errors) + var errors: CandidateErrors = @[] # if efExplain in flags: @[] else: nil + var r = resolveOverloads(c, n, nOrig, filter, flags, errors, efExplain in flags) if r.state == csMatch: # this may be triggered, when the explain pragma is used if errors.len > 0: let (_, candidates) = presentFailedCandidates(c, n, errors) - message(n.info, hintUserRaw, + message(c.config, n.info, hintUserRaw, "Non-matching candidates for " & renderTree(n) & "\n" & candidates) - result = semResolvedCall(c, n, r) - elif experimentalMode(c) and canDeref(n): + result = semResolvedCall(c, r, n, flags) + elif implicitDeref in c.features and canDeref(n): # try to deref the first argument and then try overloading resolution again: # # XXX: why is this here? @@ -443,8 +490,8 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode, # into sigmatch with hidden conversion produced there # n.sons[1] = n.sons[1].tryDeref - var r = resolveOverloads(c, n, nOrig, filter, flags, errors) - if r.state == csMatch: result = semResolvedCall(c, n, r) + var r = resolveOverloads(c, n, nOrig, filter, flags, errors, efExplain in flags) + if r.state == csMatch: result = semResolvedCall(c, r, n, flags) else: # get rid of the deref again for a better error message: n.sons[1] = n.sons[1].sons[0] @@ -463,8 +510,8 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode, else: notFoundError(c, n, errors) -proc explicitGenericInstError(n: PNode): PNode = - localError(n.info, errCannotInstantiateX, renderTree(n)) +proc explicitGenericInstError(c: PContext; n: PNode): PNode = + localError(c.config, n.info, errCannotInstantiateX % renderTree(n)) result = n proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode = @@ -474,12 +521,20 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode = for i in 1..sonsLen(n)-1: let formal = s.ast.sons[genericParamsPos].sons[i-1].typ - let arg = n[i].typ + var arg = n[i].typ + # try transforming the argument into a static one before feeding it into + # typeRel + if formal.kind == tyStatic and arg.kind != tyStatic: + let evaluated = c.semTryConstExpr(c, n[i]) + if evaluated != nil: + arg = newTypeS(tyStatic, c) + arg.sons = @[evaluated.typ] + arg.n = evaluated let tm = typeRel(m, formal, arg) if tm in {isNone, isConvertible}: return nil var newInst = generateInstance(c, s, m.bindings, n.info) newInst.typ.flags.excl tfUnresolved - markUsed(n.info, s, c.graph.usageSym) + markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(newInst, n.info) @@ -495,11 +550,11 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = # number of generic type parameters: if safeLen(s.ast.sons[genericParamsPos]) != n.len-1: let expected = safeLen(s.ast.sons[genericParamsPos]) - localError(n.info, errGenerated, "cannot instantiate: " & renderTree(n) & - "; got " & $(n.len-1) & " type(s) but expected " & $expected) + localError(c.config, n.info, errGenerated, "cannot instantiate: '" & renderTree(n) & + "'; got " & $(n.len-1) & " type(s) but expected " & $expected) return n result = explicitGenericSym(c, n, s) - if result == nil: result = explicitGenericInstError(n) + if result == nil: result = explicitGenericInstError(c, n) elif a.kind in {nkClosedSymChoice, nkOpenSymChoice}: # choose the generic proc with the proper number of type parameters. # XXX I think this could be improved by reusing sigmatch.paramTypesMatch. @@ -517,10 +572,10 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = # get rid of nkClosedSymChoice if not ambiguous: if result.len == 1 and a.kind == nkClosedSymChoice: result = result[0] - elif result.len == 0: result = explicitGenericInstError(n) - # candidateCount != 1: return explicitGenericInstError(n) + elif result.len == 0: result = explicitGenericInstError(c, n) + # candidateCount != 1: return explicitGenericInstError(c, n) else: - result = explicitGenericInstError(n) + result = explicitGenericInstError(c, n) proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym = # Searchs for the fn in the symbol table. If the parameter lists are suitable diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 3996188dc..6d6627690 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -13,8 +13,8 @@ import strutils, intsets, options, lexer, ast, astalgo, trees, treetab, wordrecg, ropes, msgs, platform, os, condsyms, idents, renderer, types, extccomp, math, - magicsys, nversion, nimsets, parser, times, passes, rodread, vmdef, - modulegraphs + magicsys, nversion, nimsets, parser, times, passes, vmdef, + modulegraphs, lineinfos type TOptionEntry* = object # entries to put on a stack for pragma parsing @@ -22,6 +22,7 @@ type defaultCC*: TCallingConvention dynlib*: PLib notes*: TNoteKinds + features*: set[Feature] otherPragmas*: PNode # every pragma can be pushed POptionEntry* = ref TOptionEntry @@ -37,6 +38,7 @@ type # in standalone ``except`` and ``finally`` next*: PProcCon # used for stacking procedure contexts wasForwarded*: bool # whether the current proc has a separate header + mappingExists*: bool mapping*: TIdTable TMatchedConcept* = object @@ -63,7 +65,7 @@ type # to the user. efWantStmt, efAllowStmt, efDetermineType, efExplain, efAllowDestructor, efWantValue, efOperand, efNoSemCheck, - efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo, + efNoEvaluateGeneric, efInCall, efFromHlo TExprFlags* = set[TExprFlag] @@ -75,6 +77,7 @@ type PContext* = ref TContext TContext* = object of TPassContext # a context represents a module + enforceVoidContext*: PType module*: PSym # the module sym belonging to the context currentScope*: PScope # current scope importTable*: PScope # scope for all imported symbols @@ -89,6 +92,7 @@ type ambiguousSymbols*: IntSet # ids of all ambiguous symbols (cannot # store this info in the syms themselves!) inGenericContext*: int # > 0 if we are in a generic type + inStaticContext*: int # > 0 if we are inside a static: block inUnrolledContext*: int # > 0 if we are unrolling a loop compilesContextId*: int # > 0 if we are in a ``compiles`` magic compilesContextIdGenerator*: int @@ -130,13 +134,15 @@ type signatures*: TStrTable recursiveDep*: string suggestionsMade*: bool + features*: set[Feature] inTypeContext*: int typesWithOps*: seq[(PType, PType)] #\ # We need to instantiate the type bound ops lazily after # the generic type has been constructed completely. See # tests/destructor/topttree.nim for an example that # would otherwise fail. - runnableExamples*: PNode + +template config*(c: PContext): ConfigRef = c.graph.config proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair = result.genericSym = s @@ -144,7 +150,7 @@ proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair = proc filename*(c: PContext): string = # the module's filename - return c.module.filename + return toFilename(c.config, FileIndex c.module.position) proc scopeDepth*(c: PContext): int {.inline.} = result = if c.currentScope != nil: c.currentScope.depthLevel @@ -163,7 +169,7 @@ proc pushOwner*(c: PContext; owner: PSym) = proc popOwner*(c: PContext) = var length = len(c.graph.owners) if length > 0: setLen(c.graph.owners, length - 1) - else: internalError("popOwner") + else: internalError(c.config, "popOwner") proc lastOptionEntry*(c: PContext): POptionEntry = result = c.optionStack[^1] @@ -171,12 +177,14 @@ proc lastOptionEntry*(c: PContext): POptionEntry = proc popProcCon*(c: PContext) {.inline.} = c.p = c.p.next proc put*(p: PProcCon; key, val: PSym) = - if p.mapping.data == nil: initIdTable(p.mapping) + if not p.mappingExists: + initIdTable(p.mapping) + p.mappingExists = true #echo "put into table ", key.info p.mapping.idTablePut(key, val) proc get*(p: PProcCon; key: PSym): PSym = - if p.mapping.data == nil: return nil + if not p.mappingExists: return nil result = PSym(p.mapping.idTableGet(key)) proc getGenSym*(c: PContext; s: PSym): PSym = @@ -199,19 +207,20 @@ proc considerGenSyms*(c: PContext; n: PNode) = for i in 0..<n.safeLen: considerGenSyms(c, n.sons[i]) -proc newOptionEntry*(): POptionEntry = +proc newOptionEntry*(conf: ConfigRef): POptionEntry = new(result) - result.options = gOptions + result.options = conf.options result.defaultCC = ccDefault result.dynlib = nil - result.notes = gNotes + result.notes = conf.notes -proc newContext*(graph: ModuleGraph; module: PSym; cache: IdentCache): PContext = +proc newContext*(graph: ModuleGraph; module: PSym): PContext = new(result) + result.enforceVoidContext = PType(kind: tyStmt) result.ambiguousSymbols = initIntSet() result.optionStack = @[] result.libs = @[] - result.optionStack.add(newOptionEntry()) + result.optionStack.add(newOptionEntry(graph.config)) result.module = module result.friendModules = @[module] result.converters = @[] @@ -221,11 +230,11 @@ proc newContext*(graph: ModuleGraph; module: PSym; cache: IdentCache): PContext initStrTable(result.userPragmas) result.generics = @[] result.unknownIdents = initIntSet() - result.cache = cache + result.cache = graph.cache result.graph = graph initStrTable(result.signatures) result.typesWithOps = @[] - + result.features = graph.config.features proc inclSym(sq: var TSymSeq, s: PSym) = var L = len(sq) @@ -254,7 +263,7 @@ proc newTypeS*(kind: TTypeKind, c: PContext): PType = proc makePtrType*(c: PContext, baseType: PType): PType = result = newTypeS(tyPtr, c) - addSonSkipIntLit(result, baseType.assertNotNil) + addSonSkipIntLit(result, baseType) proc makeTypeWithModifier*(c: PContext, modifier: TTypeKind, @@ -265,25 +274,27 @@ proc makeTypeWithModifier*(c: PContext, result = baseType else: result = newTypeS(modifier, c) - addSonSkipIntLit(result, baseType.assertNotNil) + addSonSkipIntLit(result, baseType) proc makeVarType*(c: PContext, baseType: PType; kind = tyVar): PType = if baseType.kind == kind: result = baseType else: result = newTypeS(kind, c) - addSonSkipIntLit(result, baseType.assertNotNil) + addSonSkipIntLit(result, baseType) proc makeTypeDesc*(c: PContext, typ: PType): PType = if typ.kind == tyTypeDesc: result = typ else: result = newTypeS(tyTypeDesc, c) - result.addSonSkipIntLit(typ.assertNotNil) + result.addSonSkipIntLit(typ) proc makeTypeSymNode*(c: PContext, typ: PType, info: TLineInfo): PNode = - let typedesc = makeTypeDesc(c, typ) - let sym = newSym(skType, c.cache.idAnon, getCurrOwner(c), info).linkTo(typedesc) + let typedesc = newTypeS(tyTypeDesc, c) + typedesc.addSonSkipIntLit(assertNotNil(c.config, typ)) + let sym = newSym(skType, c.cache.idAnon, getCurrOwner(c), info, + c.config.options).linkTo(typedesc) return newSymNode(sym, info) proc makeTypeFromExpr*(c: PContext, n: PNode): PType = @@ -338,21 +349,20 @@ proc makeNotType*(c: PContext, t1: PType): PType = result.flags.incl(t1.flags * {tfHasStatic}) result.flags.incl tfHasMeta -proc nMinusOne*(n: PNode): PNode = +proc nMinusOne(c: PContext; n: PNode): PNode = result = newNode(nkCall, n.info, @[ - newSymNode(getSysMagic("pred", mPred)), - n]) + newSymNode(getSysMagic(c.graph, n.info, "pred", mPred)), n]) # Remember to fix the procs below this one when you make changes! proc makeRangeWithStaticExpr*(c: PContext, n: PNode): PType = - let intType = getSysType(tyInt) + let intType = getSysType(c.graph, n.info, tyInt) result = newTypeS(tyRange, c) result.sons = @[intType] if n.typ != nil and n.typ.n == nil: result.flags.incl tfUnresolved result.n = newNode(nkRange, n.info, @[ newIntTypeNode(nkIntLit, 0, intType), - makeStaticExpr(c, n.nMinusOne)]) + makeStaticExpr(c, nMinusOne(c, n))]) template rangeHasUnresolvedStatic*(t: PType): bool = tfUnresolved in t.flags @@ -371,7 +381,8 @@ proc fillTypeS*(dest: PType, kind: TTypeKind, c: PContext) = dest.size = - 1 proc makeRangeType*(c: PContext; first, last: BiggestInt; - info: TLineInfo; intType = getSysType(tyInt)): PType = + info: TLineInfo; intType: PType = nil): PType = + let intType = if intType != nil: intType else: getSysType(c.graph, info, tyInt) var n = newNodeI(nkRange, info) addSon(n, newIntTypeNode(nkIntLit, first, intType)) addSon(n, newIntTypeNode(nkIntLit, last, intType)) @@ -384,20 +395,17 @@ proc markIndirect*(c: PContext, s: PSym) {.inline.} = incl(s.flags, sfAddrTaken) # XXX add to 'c' for global analysis -proc illFormedAst*(n: PNode) = - globalError(n.info, errIllFormedAstX, renderTree(n, {renderNoComments})) +proc illFormedAst*(n: PNode; conf: ConfigRef) = + globalError(conf, n.info, errIllFormedAstX, renderTree(n, {renderNoComments})) -proc illFormedAstLocal*(n: PNode) = - localError(n.info, errIllFormedAstX, renderTree(n, {renderNoComments})) +proc illFormedAstLocal*(n: PNode; conf: ConfigRef) = + localError(conf, n.info, errIllFormedAstX, renderTree(n, {renderNoComments})) -proc checkSonsLen*(n: PNode, length: int) = - if sonsLen(n) != length: illFormedAst(n) +proc checkSonsLen*(n: PNode, length: int; conf: ConfigRef) = + if sonsLen(n) != length: illFormedAst(n, conf) -proc checkMinSonsLen*(n: PNode, length: int) = - if sonsLen(n) < length: illFormedAst(n) +proc checkMinSonsLen*(n: PNode, length: int; conf: ConfigRef) = + if sonsLen(n) < length: illFormedAst(n, conf) proc isTopLevel*(c: PContext): bool {.inline.} = result = c.currentScope.depthLevel <= 2 - -proc experimentalMode*(c: PContext): bool {.inline.} = - result = gExperimentalMode or sfExperimental in c.module.flags diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index f1d226160..e527b06cc 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -10,14 +10,29 @@ # this module does the semantic checking for expressions # included from sem.nim +const + errExprXHasNoType = "expression '$1' has no type (or is ambiguous)" + errXExpectsTypeOrValue = "'$1' expects a type or value" + errVarForOutParamNeededX = "for a 'var' type a variable needs to be passed; but '$1' is immutable" + errXStackEscape = "address of '$1' may not escape its stack frame" + errExprHasNoAddress = "expression has no address; maybe use 'unsafeAddr'" + errCannotInterpretNodeX = "cannot evaluate '$1'" + errNamedExprExpected = "named expression expected" + errNamedExprNotAllowed = "named expression not allowed here" + errFieldInitTwice = "field initialized twice: '$1'" + errUndeclaredFieldX = "undeclared field: '$1'" + proc semTemplateExpr(c: PContext, n: PNode, s: PSym, flags: TExprFlags = {}): PNode = - markUsed(n.info, s, c.graph.usageSym) + markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) - pushInfoContext(n.info) - result = evalTemplate(n, s, getCurrOwner(c), efFromHlo in flags) + pushInfoContext(c.config, n.info) + result = evalTemplate(n, s, getCurrOwner(c), c.config, efFromHlo in flags) if efNoSemCheck notin flags: result = semAfterMacroCall(c, n, result, s, flags) - popInfoContext() + popInfoContext(c.config) + + # XXX: A more elaborate line info rewrite might be needed + result.info = n.info proc semFieldAccess(c: PContext, n: PNode, flags: TExprFlags = {}): PNode @@ -31,12 +46,12 @@ proc semOperand(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = if result.typ != nil: # XXX tyGenericInst here? if result.typ.kind == tyProc and tfUnresolved in result.typ.flags: - localError(n.info, errProcHasNoConcreteType, n.renderTree) + localError(c.config, n.info, errProcHasNoConcreteType % n.renderTree) if result.typ.kind in {tyVar, tyLent}: result = newDeref(result) elif {efWantStmt, efAllowStmt} * flags != {}: result.typ = newTypeS(tyVoid, c) else: - localError(n.info, errExprXHasNoType, + localError(c.config, n.info, errExprXHasNoType % renderTree(result, {renderNoComments})) result.typ = errorType(c) @@ -46,12 +61,11 @@ proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = # do not produce another redundant error message: #raiseRecoverableError("") result = errorNode(c, n) - if result.typ == nil or result.typ == enforceVoidContext: - localError(n.info, errExprXHasNoType, + if result.typ == nil or result.typ == c.enforceVoidContext: + localError(c.config, n.info, errExprXHasNoType % renderTree(result, {renderNoComments})) result.typ = errorType(c) else: - if efNoProcvarCheck notin flags: semProcvarCheck(c, result) if result.typ.kind in {tyVar, tyLent}: result = newDeref(result) proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = @@ -60,19 +74,17 @@ proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = # do not produce another redundant error message: result = errorNode(c, n) if result.typ == nil: - localError(n.info, errExprXHasNoType, + localError(c.config, n.info, errExprXHasNoType % renderTree(result, {renderNoComments})) result.typ = errorType(c) - else: - semProcvarCheck(c, result) proc semSymGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = result = symChoice(c, n, s, scClosed) -proc inlineConst(n: PNode, s: PSym): PNode {.inline.} = +proc inlineConst(c: PContext, n: PNode, s: PSym): PNode {.inline.} = result = copyTree(s.ast) if result.isNil: - localError(n.info, "constant of type '" & typeToString(s.typ) & "' has no value") + localError(c.config, n.info, "constant of type '" & typeToString(s.typ) & "' has no value") result = newSymNode(s) else: result.typ = s.typ @@ -101,6 +113,7 @@ proc checkConvertible(c: PContext, castDest, src: PType): TConvStatus = if castDest.kind notin IntegralTypes+{tyRange}: result = convNotNeedeed return + # Save for later var d = skipTypes(castDest, abstractVar) var s = src if s.kind in tyUserTypeClasses and s.isResolvedUserTypeClass: @@ -123,12 +136,12 @@ proc checkConvertible(c: PContext, castDest, src: PType): TConvStatus = # we use d, s here to speed up that operation a bit: case cmpTypes(c, d, s) of isNone, isGeneric: - if not compareTypes(castDest, src, dcEqIgnoreDistinct): + if not compareTypes(castDest.skipTypes(abstractVar), src, dcEqIgnoreDistinct): result = convNotLegal else: discard -proc isCastable(dst, src: PType): bool = +proc isCastable(conf: ConfigRef; dst, src: PType): bool = ## Checks whether the source type can be cast to the destination type. ## Casting is very unrestrictive; casts are allowed as long as ## castDest.size >= src.size, and typeAllowed(dst, skParam) @@ -143,8 +156,8 @@ proc isCastable(dst, src: PType): bool = return false var dstSize, srcSize: BiggestInt - dstSize = computeSize(dst) - srcSize = computeSize(src) + dstSize = computeSize(conf, dst) + srcSize = computeSize(conf, src) if dstSize < 0: result = false elif srcSize < 0: @@ -158,7 +171,7 @@ proc isCastable(dst, src: PType): bool = (skipTypes(dst, abstractInst).kind in IntegralTypes) or (skipTypes(src, abstractInst-{tyTypeDesc}).kind in IntegralTypes) if result and src.kind == tyNil: - result = dst.size <= platform.ptrSize + result = dst.size <= conf.target.ptrSize proc isSymChoice(n: PNode): bool {.inline.} = result = n.kind in nkSymChoices @@ -175,11 +188,29 @@ proc maybeLiftType(t: var PType, c: PContext, info: TLineInfo) = proc semConv(c: PContext, n: PNode): PNode = if sonsLen(n) != 2: - localError(n.info, errConvNeedsOneArg) + localError(c.config, n.info, "a type conversion takes exactly one argument") return n result = newNodeI(nkConv, n.info) - var targetType = semTypeNode(c, n.sons[0], nil).skipTypes({tyTypeDesc}) + + var targetType = semTypeNode(c, n.sons[0], nil) + if targetType.kind == tyTypeDesc: + internalAssert c.config, targetType.len > 0 + if targetType.base.kind == tyNone: + return semTypeOf(c, n[1]) + else: + targetType = targetType.base + elif targetType.kind == tyStatic: + var evaluated = semStaticExpr(c, n[1]) + if evaluated.kind == nkType or evaluated.typ.kind == tyTypeDesc: + result = n + result.typ = c.makeTypeDesc semStaticType(c, evaluated, nil) + return + elif targetType.base.kind == tyNone: + return evaluated + else: + targetType = targetType.base + maybeLiftType(targetType, c, n[0].info) if targetType.kind in {tySink, tyLent}: @@ -192,6 +223,10 @@ proc semConv(c: PContext, n: PNode): PNode = result.addSon copyTree(n.sons[0]) + # special case to make MyObject(x = 3) produce a nicer error message: + if n[1].kind == nkExprEqExpr and + targetType.skipTypes(abstractPtrs).kind == tyObject: + localError(c.config, n.info, "object contruction uses ':', not '='") var op = semExprWithType(c, n.sons[1]) if targetType.isMetaType: let final = inferWithMetatype(c, targetType, op, true) @@ -211,21 +246,21 @@ proc semConv(c: PContext, n: PNode): PNode = # handle SomeProcType(SomeGenericProc) if op.kind == nkSym and op.sym.isGenericRoutine: result.sons[1] = fitNode(c, result.typ, result.sons[1], result.info) - elif op.kind == nkPar and targetType.kind == tyTuple: + elif op.kind in {nkPar, nkTupleConstr} and targetType.kind == tyTuple: op = fitNode(c, targetType, op, result.info) of convNotNeedeed: - message(n.info, hintConvFromXtoItselfNotNeeded, result.typ.typeToString) + message(c.config, n.info, hintConvFromXtoItselfNotNeeded, result.typ.typeToString) of convNotLegal: result = fitNode(c, result.typ, result.sons[1], result.info) if result == nil: - localError(n.info, errGenerated, msgKindToString(errIllegalConvFromXtoY)% + localError(c.config, n.info, "illegal conversion from '$1' to '$2'" % [op.typ.typeToString, result.typ.typeToString]) else: for i in countup(0, sonsLen(op) - 1): let it = op.sons[i] let status = checkConvertible(c, result.typ, it.typ) if status in {convOK, convNotNeedeed}: - markUsed(n.info, it.sym, c.graph.usageSym) + markUsed(c.config, n.info, it.sym, c.graph.usageSym) styleCheckUse(n.info, it.sym) markIndirect(c, it.sym) return it @@ -233,16 +268,16 @@ proc semConv(c: PContext, n: PNode): PNode = proc semCast(c: PContext, n: PNode): PNode = ## Semantically analyze a casting ("cast[type](param)") - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) let targetType = semTypeNode(c, n.sons[0], nil) let castedExpr = semExprWithType(c, n.sons[1]) if tfHasMeta in targetType.flags: - localError(n.sons[0].info, errCastToANonConcreteType, $targetType) - if not isCastable(targetType, castedExpr.typ): + localError(c.config, n.sons[0].info, "cannot cast to a non concrete type: '$1'" % $targetType) + if not isCastable(c.config, targetType, castedExpr.typ): let tar = $targetType let alt = typeToString(targetType, preferDesc) let msg = if tar != alt: tar & "=" & alt else: tar - localError(n.info, errExprCannotBeCastToX, msg) + localError(c.config, n.info, "expression cannot be cast to " & msg) result = newNodeI(nkCast, n.info) result.typ = targetType addSon(result, copyTree(n.sons[0])) @@ -252,13 +287,13 @@ proc semLowHigh(c: PContext, n: PNode, m: TMagic): PNode = const opToStr: array[mLow..mHigh, string] = ["low", "high"] if sonsLen(n) != 2: - localError(n.info, errXExpectsTypeOrValue, opToStr[m]) + localError(c.config, n.info, errXExpectsTypeOrValue % opToStr[m]) else: n.sons[1] = semExprWithType(c, n.sons[1], {efDetermineType}) - var typ = skipTypes(n.sons[1].typ, abstractVarRange + {tyTypeDesc}) + var typ = skipTypes(n.sons[1].typ, abstractVarRange + {tyTypeDesc, tyUserTypeClassInst}) case typ.kind of tySequence, tyString, tyCString, tyOpenArray, tyVarargs: - n.typ = getSysType(tyInt) + n.typ = getSysType(c.graph, n.info, tyInt) of tyArray: n.typ = typ.sons[0] # indextype of tyInt..tyInt64, tyChar, tyBool, tyEnum, tyUInt8, tyUInt16, tyUInt32: @@ -270,75 +305,125 @@ proc semLowHigh(c: PContext, n: PNode, m: TMagic): PNode = # that could easily turn into an infinite recursion in semtypinst n.typ = makeTypeFromExpr(c, n.copyTree) else: - localError(n.info, errInvalidArgForX, opToStr[m]) + localError(c.config, n.info, "invalid argument for: " & opToStr[m]) result = n proc semSizeof(c: PContext, n: PNode): PNode = if sonsLen(n) != 2: - localError(n.info, errXExpectsTypeOrValue, "sizeof") + localError(c.config, n.info, errXExpectsTypeOrValue % "sizeof") else: n.sons[1] = semExprWithType(c, n.sons[1], {efDetermineType}) #restoreOldStyleType(n.sons[1]) - n.typ = getSysType(tyInt) + n.typ = getSysType(c.graph, n.info, tyInt) result = n +proc fixupStaticType(c: PContext, n: PNode) = + # This proc can be applied to evaluated expressions to assign + # them a static type. + # + # XXX: with implicit static, this should not be necessary, + # because the output type of operations such as `semConstExpr` + # should be a static type (as well as the type of any other + # expression that can be implicitly evaluated). For now, we + # apply this measure only in code that is enlightened to work + # with static types. + if n.typ.kind != tyStatic: + n.typ = newTypeWithSons(getCurrOwner(c), tyStatic, @[n.typ]) + n.typ.n = n # XXX: cycles like the one here look dangerous. + # Consider using `n.copyTree` + proc isOpImpl(c: PContext, n: PNode, flags: TExprFlags): PNode = - internalAssert n.sonsLen == 3 and - n[1].typ != nil and n[1].typ.kind == tyTypeDesc and + internalAssert c.config, + n.sonsLen == 3 and + n[1].typ != nil and n[2].kind in {nkStrLit..nkTripleStrLit, nkType} - let t1 = n[1].typ.skipTypes({tyTypeDesc}) + var + res = false + t1 = n[1].typ + t2 = n[2].typ + + if t1.kind == tyTypeDesc and t2.kind != tyTypeDesc: + t1 = t1.base if n[2].kind in {nkStrLit..nkTripleStrLit}: case n[2].strVal.normalize of "closure": let t = skipTypes(t1, abstractRange) - result = newIntNode(nkIntLit, ord(t.kind == tyProc and - t.callConv == ccClosure and - tfIterator notin t.flags)) + res = t.kind == tyProc and + t.callConv == ccClosure and + tfIterator notin t.flags + of "iterator": + let t = skipTypes(t1, abstractRange) + res = t.kind == tyProc and + t.callConv == ccClosure and + tfIterator in t.flags else: - result = newIntNode(nkIntLit, 0) + res = false else: - var rhsOrigType = n[2].typ - var t2 = rhsOrigType.skipTypes({tyTypeDesc}) maybeLiftType(t2, c, n.info) var m: TCandidate initCandidate(c, m, t2) - if efExplain in flags: m.diagnostics = @[] - let match = typeRel(m, t2, t1) >= isSubtype # isNone - result = newIntNode(nkIntLit, ord(match)) + if efExplain in flags: + m.diagnostics = @[] + m.diagnosticsEnabled = true + res = typeRel(m, t2, t1) >= isSubtype # isNone + result = newIntNode(nkIntLit, ord(res)) result.typ = n.typ proc semIs(c: PContext, n: PNode, flags: TExprFlags): PNode = if sonsLen(n) != 3: - localError(n.info, errXExpectsTwoArguments, "is") + localError(c.config, n.info, "'is' operator takes 2 arguments") + let boolType = getSysType(c.graph, n.info, tyBool) result = n - n.typ = getSysType(tyBool) + n.typ = boolType + var liftLhs = true n.sons[1] = semExprWithType(c, n[1], {efDetermineType, efWantIterator}) if n[2].kind notin {nkStrLit..nkTripleStrLit}: let t2 = semTypeNode(c, n[2], nil) n.sons[2] = newNodeIT(nkType, n[2].info, t2) + if t2.kind == tyStatic: + let evaluated = tryConstExpr(c, n[1]) + if evaluated != nil: + c.fixupStaticType(evaluated) + n[1] = evaluated + else: + result = newIntNode(nkIntLit, 0) + result.typ = boolType + return + elif t2.kind == tyTypeDesc and + (t2.base.kind == tyNone or tfExplicit in t2.flags): + # When the right-hand side is an explicit type, we must + # not allow regular values to be matched against the type: + liftLhs = false + else: + n.sons[2] = semExpr(c, n[2]) - let lhsType = n[1].typ + var lhsType = n[1].typ if lhsType.kind != tyTypeDesc: - n.sons[1] = makeTypeSymNode(c, lhsType, n[1].info) - elif lhsType.base.kind == tyNone: - # this is a typedesc variable, leave for evals - return + if liftLhs: + n[1] = makeTypeSymNode(c, lhsType, n[1].info) + lhsType = n[1].typ + else: + if lhsType.base.kind == tyNone: + # this is a typedesc variable, leave for evals + return + if lhsType.base.containsGenericType: + # BUGFIX: don't evaluate this too early: ``T is void`` + return - # BUGFIX: don't evaluate this too early: ``T is void`` - if not n[1].typ.base.containsGenericType: result = isOpImpl(c, n, flags) + result = isOpImpl(c, n, flags) proc semOpAux(c: PContext, n: PNode) = const flags = {efDetermineType} for i in countup(1, n.sonsLen-1): var a = n.sons[i] if a.kind == nkExprEqExpr and sonsLen(a) == 2: - var info = a.sons[0].info - a.sons[0] = newIdentNode(considerQuotedIdent(a.sons[0], a), info) + let info = a.sons[0].info + a.sons[0] = newIdentNode(considerQuotedIdent(c, a.sons[0], a), info) a.sons[1] = semExprWithType(c, a.sons[1], flags) a.typ = a.sons[1].typ else: @@ -346,7 +431,7 @@ proc semOpAux(c: PContext, n: PNode) = proc overloadedCallOpr(c: PContext, n: PNode): PNode = # quick check if there is *any* () operator overloaded: - var par = getIdent("()") + var par = getIdent(c.cache, "()") if searchInScopes(c, par) == nil: result = nil else: @@ -355,34 +440,34 @@ proc overloadedCallOpr(c: PContext, n: PNode): PNode = for i in countup(0, sonsLen(n) - 1): addSon(result, n.sons[i]) result = semExpr(c, result) -proc changeType(n: PNode, newType: PType, check: bool) = +proc changeType(c: PContext; n: PNode, newType: PType, check: bool) = case n.kind of nkCurly, nkBracket: for i in countup(0, sonsLen(n) - 1): - changeType(n.sons[i], elemType(newType), check) - of nkPar: + changeType(c, n.sons[i], elemType(newType), check) + of nkPar, nkTupleConstr: let tup = newType.skipTypes({tyGenericInst, tyAlias, tySink}) if tup.kind != tyTuple: if tup.kind == tyObject: return - globalError(n.info, "no tuple type for constructor") + globalError(c.config, n.info, "no tuple type for constructor") elif sonsLen(n) > 0 and n.sons[0].kind == nkExprColonExpr: # named tuple? for i in countup(0, sonsLen(n) - 1): var m = n.sons[i].sons[0] if m.kind != nkSym: - globalError(m.info, "invalid tuple constructor") + globalError(c.config, m.info, "invalid tuple constructor") return if tup.n != nil: var f = getSymFromList(tup.n, m.sym.name) if f == nil: - globalError(m.info, "unknown identifier: " & m.sym.name.s) + globalError(c.config, m.info, "unknown identifier: " & m.sym.name.s) return - changeType(n.sons[i].sons[1], f.typ, check) + changeType(c, n.sons[i].sons[1], f.typ, check) else: - changeType(n.sons[i].sons[1], tup.sons[i], check) + changeType(c, n.sons[i].sons[1], tup.sons[i], check) else: for i in countup(0, sonsLen(n) - 1): - changeType(n.sons[i], tup.sons[i], check) + changeType(c, n.sons[i], tup.sons[i], check) when false: var m = n.sons[i] var a = newNodeIT(nkExprColonExpr, m.info, newType.sons[i]) @@ -392,8 +477,8 @@ proc changeType(n: PNode, newType: PType, check: bool) = of nkCharLit..nkUInt64Lit: if check and n.kind != nkUInt64Lit: let value = n.intVal - if value < firstOrd(newType) or value > lastOrd(newType): - localError(n.info, errGenerated, "cannot convert " & $value & + if value < firstOrd(c.config, newType) or value > lastOrd(c.config, newType): + localError(c.config, n.info, "cannot convert " & $value & " to " & typeToString(newType)) else: discard n.typ = newType @@ -418,7 +503,7 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = else: var x = n.sons[0] var lastIndex: BiggestInt = 0 - var indexType = getSysType(tyInt) + var indexType = getSysType(c.graph, n.info, tyInt) if x.kind == nkExprColonExpr and sonsLen(x) == 2: var idx = semConstExpr(c, x.sons[0]) lastIndex = getOrdValue(idx) @@ -435,7 +520,7 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = var idx = semConstExpr(c, x.sons[0]) idx = fitNode(c, indexType, idx, x.info) if lastIndex+1 != getOrdValue(idx): - localError(x.info, errInvalidOrderInArrayConstructor) + localError(c.config, x.info, "invalid order in array constructor") x = x.sons[1] let xx = semExprWithType(c, x, flags*{efAllowDestructor}) @@ -459,22 +544,52 @@ proc fixAbstractType(c: PContext, n: PNode) = {tyNil, tyTuple, tySet} or it[1].isArrayConstr: var s = skipTypes(it.typ, abstractVar) if s.kind != tyExpr: - changeType(it.sons[1], s, check=true) + changeType(c, it.sons[1], s, check=true) n.sons[i] = it.sons[1] proc isAssignable(c: PContext, n: PNode; isUnsafeAddr=false): TAssignableResult = result = parampatterns.isAssignable(c.p.owner, n, isUnsafeAddr) +proc isUnresolvedSym(s: PSym): bool = + return s.kind == skGenericParam or + tfInferrableStatic in s.typ.flags or + (s.kind == skParam and s.typ.isMetaType) or + (s.kind == skType and + s.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} != {}) + +proc hasUnresolvedArgs(c: PContext, n: PNode): bool = + # Checks whether an expression depends on generic parameters that + # don't have bound values yet. E.g. this could happen in situations + # such as: + # type Slot[T] = array[T.size, byte] + # proc foo[T](x: default(T)) + # + # Both static parameter and type parameters can be unresolved. + case n.kind + of nkSym: + return isUnresolvedSym(n.sym) + of nkIdent, nkAccQuoted: + let ident = considerQuotedIdent(c, n) + let sym = searchInScopes(c, ident) + if sym != nil: + return isUnresolvedSym(sym) + else: + return false + else: + for i in 0..<n.safeLen: + if hasUnresolvedArgs(c, n.sons[i]): return true + return false + proc newHiddenAddrTaken(c: PContext, n: PNode): PNode = - if n.kind == nkHiddenDeref and not (gCmd == cmdCompileToCpp or + if n.kind == nkHiddenDeref and not (c.config.cmd == cmdCompileToCpp or sfCompileToCpp in c.module.flags): - checkSonsLen(n, 1) + checkSonsLen(n, 1, c.config) result = n.sons[0] else: result = newNodeIT(nkHiddenAddr, n.info, makeVarType(c, n.typ)) addSon(result, n) if isAssignable(c, n) notin {arLValue, arLocalLValue}: - localError(n.info, errVarForOutParamNeededX, renderNotLValue(n)) + localError(c.config, n.info, errVarForOutParamNeededX % renderNotLValue(n)) proc analyseIfAddressTaken(c: PContext, n: PNode): PNode = result = n @@ -486,15 +601,15 @@ proc analyseIfAddressTaken(c: PContext, n: PNode): PNode = incl(n.sym.flags, sfAddrTaken) result = newHiddenAddrTaken(c, n) of nkDotExpr: - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) if n.sons[1].kind != nkSym: - internalError(n.info, "analyseIfAddressTaken") + internalError(c.config, n.info, "analyseIfAddressTaken") return if skipTypes(n.sons[1].sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: incl(n.sons[1].sym.flags, sfAddrTaken) result = newHiddenAddrTaken(c, n) of nkBracketExpr: - checkMinSonsLen(n, 1) + checkMinSonsLen(n, 1, c.config) if skipTypes(n.sons[0].typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: if n.sons[0].kind == nkSym: incl(n.sons[0].sym.flags, sfAddrTaken) result = newHiddenAddrTaken(c, n) @@ -502,7 +617,7 @@ proc analyseIfAddressTaken(c: PContext, n: PNode): PNode = result = newHiddenAddrTaken(c, n) proc analyseIfAddressTakenInCall(c: PContext, n: PNode) = - checkMinSonsLen(n, 1) + checkMinSonsLen(n, 1, c.config) const FakeVarParams = {mNew, mNewFinalize, mInc, ast.mDec, mIncl, mExcl, mSetLengthStr, mSetLengthSeq, mAppendStrCh, mAppendStrStr, mSwap, @@ -521,14 +636,21 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) = let it = n[i] if isAssignable(c, it) notin {arLValue, arLocalLValue}: if it.kind != nkHiddenAddr: - localError(it.info, errVarForOutParamNeededX, $it) + localError(c.config, it.info, errVarForOutParamNeededX % $it) + # bug #5113: disallow newSeq(result) where result is a 'var T': + if n[0].sym.magic in {mNew, mNewFinalize, mNewSeq}: + var arg = n[1] #.skipAddr + if arg.kind == nkHiddenDeref: arg = arg[0] + if arg.kind == nkSym and arg.sym.kind == skResult and + arg.typ.skipTypes(abstractInst).kind in {tyVar, tyLent}: + localError(c.config, n.info, errXStackEscape % renderTree(n[1], {renderNoComments})) + return for i in countup(1, sonsLen(n) - 1): if n.sons[i].kind == nkHiddenCallConv: # we need to recurse explicitly here as converters can create nested # calls and then they wouldn't be analysed otherwise analyseIfAddressTakenInCall(c, n.sons[i]) - semProcvarCheck(c, n.sons[i]) if i < sonsLen(t) and skipTypes(t.sons[i], abstractInst-{tyTypeDesc}).kind == tyVar: if n.sons[i].kind != nkHiddenAddr: @@ -550,14 +672,14 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode = call.add(n.sons[0]) var allConst = true for i in 1 ..< n.len: - var a = getConstExpr(c.module, n.sons[i]) + var a = getConstExpr(c.module, n.sons[i], c.graph) if a == nil: allConst = false a = n.sons[i] if a.kind == nkHiddenStdConv: a = a.sons[1] call.add(a) if allConst: - result = semfold.getConstExpr(c.module, call) + result = semfold.getConstExpr(c.module, call, c.graph) if result.isNil: result = n else: return result @@ -579,7 +701,7 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode = if {sfNoSideEffect, sfCompileTime} * callee.flags != {} and {sfForward, sfImportc} * callee.flags == {} and n.typ != nil: if sfCompileTime notin callee.flags and - optImplicitStatic notin gOptions: return + optImplicitStatic notin c.config.options: return if callee.magic notin ctfeWhitelist: return if callee.kind notin {skProc, skFunc, skConverter} or callee.isGenericRoutine: @@ -590,29 +712,29 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode = var call = newNodeIT(nkCall, n.info, n.typ) call.add(n.sons[0]) for i in 1 ..< n.len: - let a = getConstExpr(c.module, n.sons[i]) + let a = getConstExpr(c.module, n.sons[i], c.graph) if a == nil: return n call.add(a) #echo "NOW evaluating at compile time: ", call.renderTree if sfCompileTime in callee.flags: - result = evalStaticExpr(c.module, c.cache, call, c.p.owner) + result = evalStaticExpr(c.module, c.graph, call, c.p.owner) if result.isNil: - localError(n.info, errCannotInterpretNodeX, renderTree(call)) + localError(c.config, n.info, errCannotInterpretNodeX % renderTree(call)) else: result = fixupTypeAfterEval(c, result, n) else: - result = evalConstExpr(c.module, c.cache, call) + result = evalConstExpr(c.module, c.graph, call) if result.isNil: result = n else: result = fixupTypeAfterEval(c, result, n) #if result != n: # echo "SUCCESS evaluated at compile time: ", call.renderTree proc semStaticExpr(c: PContext, n: PNode): PNode = - let a = semExpr(c, n.sons[0]) + let a = semExpr(c, n) if a.findUnresolvedStatic != nil: return a - result = evalStaticExpr(c.module, c.cache, a, c.p.owner) + result = evalStaticExpr(c.module, c.graph, a, c.p.owner) if result.isNil: - localError(n.info, errCannotInterpretNodeX, renderTree(n)) - result = emptyNode + localError(c.config, n.info, errCannotInterpretNodeX % renderTree(n)) + result = c.graph.emptyNode else: result = fixupTypeAfterEval(c, result, a) @@ -631,14 +753,14 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode, if result != nil: if result.sons[0].kind != nkSym: - internalError("semOverloadedCallAnalyseEffects") + internalError(c.config, "semOverloadedCallAnalyseEffects") return let callee = result.sons[0].sym case callee.kind of skMacro, skTemplate: discard else: if callee.kind == skIterator and callee.id == c.p.owner.id: - localError(n.info, errRecursiveDependencyX, callee.name.s) + localError(c.config, n.info, errRecursiveDependencyX % callee.name.s) # error correction, prevents endless for loop elimination in transf. # See bug #2051: result.sons[0] = newSymNode(errorSym(c, n)) @@ -651,7 +773,7 @@ proc resolveIndirectCall(c: PContext; n, nOrig: PNode; matches(c, n, nOrig, result) if result.state != csMatch: # try to deref the first argument: - if experimentalMode(c) and canDeref(n): + if implicitDeref in c.features and canDeref(n): n.sons[1] = n.sons[1].tryDeref initCandidate(c, result, t) matches(c, n, nOrig, result) @@ -679,17 +801,19 @@ proc afterCallActions(c: PContext; n, orig: PNode, flags: TExprFlags): PNode = analyseIfAddressTakenInCall(c, result) if callee.magic != mNone: result = magicsAfterOverloadResolution(c, result, flags) - if result.typ != nil: liftTypeBoundOps(c, result.typ, n.info) + if result.typ != nil and + not (result.typ.kind == tySequence and result.typ.sons[0].kind == tyEmpty): + liftTypeBoundOps(c, result.typ, n.info) #result = patchResolvedTypeBoundOp(c, result) if c.matchedConcept == nil: result = evalAtCompileTime(c, result) proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = result = nil - checkMinSonsLen(n, 1) + checkMinSonsLen(n, 1, c.config) var prc = n.sons[0] if n.sons[0].kind == nkDotExpr: - checkSonsLen(n.sons[0], 2) + checkSonsLen(n.sons[0], 2, c.config) let n0 = semFieldAccess(c, n.sons[0]) if n0.kind == nkDotCall: # it is a static call! @@ -720,13 +844,13 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = # This is a proc variable, apply normal overload resolution let m = resolveIndirectCall(c, n, nOrig, t) if m.state != csMatch: - if errorOutputs == {}: + if c.config.m.errorOutputs == {}: # speed up error generation: - globalError(n.info, errTypeMismatch, "") - return emptyNode + globalError(c.config, n.info, "type mismatch") + return c.graph.emptyNode else: var hasErrorType = false - var msg = msgKindToString(errTypeMismatch) + var msg = "type mismatch: got <" for i in countup(1, sonsLen(n) - 1): if i > 1: add(msg, ", ") let nt = n.sons[i].typ @@ -735,14 +859,15 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = hasErrorType = true break if not hasErrorType: - add(msg, ">\n" & msgKindToString(errButExpected) & "\n" & + add(msg, ">\nbut expected one of: \n" & typeToString(n.sons[0].typ)) - localError(n.info, errGenerated, msg) + localError(c.config, n.info, msg) return errorNode(c, n) result = nil else: result = m.call instGenericConvertersSons(c, result, m) + elif t != nil and t.kind == tyTypeDesc: if n.len == 1: return semObjConstr(c, n, flags) return semConv(c, n) @@ -780,31 +905,21 @@ proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = proc buildEchoStmt(c: PContext, n: PNode): PNode = # we MUST not check 'n' for semantics again here! But for now we give up: result = newNodeI(nkCall, n.info) - var e = strTableGet(magicsys.systemModule.tab, getIdent"echo") + var e = strTableGet(c.graph.systemModule.tab, getIdent(c.cache, "echo")) if e != nil: add(result, newSymNode(e)) else: - localError(n.info, errSystemNeeds, "echo") + localError(c.config, n.info, "system needs: echo") add(result, errorNode(c, n)) add(result, n) result = semExpr(c, result) proc semExprNoType(c: PContext, n: PNode): PNode = + let isPush = hintExtendedContext in c.config.notes + if isPush: pushInfoContext(c.config, n.info) result = semExpr(c, n, {efWantStmt}) - # make an 'if' expression an 'if' statement again for backwards - # compatibility (.discardable was a bad idea!); bug #6980 - var isStmt = false - if result.kind == nkIfExpr: - isStmt = true - for condActionPair in result: - let action = condActionPair.lastSon - if not implicitlyDiscardable(action) and not - endsInNoReturn(action): - isStmt = false - if isStmt: - result.kind = nkIfStmt - result.typ = nil discardCheck(c, result) + if isPush: popInfoContext(c.config) proc isTypeExpr(n: PNode): bool = case n.kind @@ -828,8 +943,8 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent, result = lookupInRecordAndBuildCheck(c, n, r.sons[i], field, check) if result != nil: return of nkRecCase: - checkMinSonsLen(r, 2) - if (r.sons[0].kind != nkSym): illFormedAst(r) + checkMinSonsLen(r, 2, c.config) + if (r.sons[0].kind != nkSym): illFormedAst(r, c.config) result = lookupInRecordAndBuildCheck(c, n, r.sons[0], field, check) if result != nil: return let setType = createSetType(c, r.sons[0].typ) @@ -844,11 +959,11 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent, else: if check == nil: check = newNodeI(nkCheckedFieldExpr, n.info) - addSon(check, ast.emptyNode) # make space for access node + addSon(check, c.graph.emptyNode) # make space for access node s = newNodeIT(nkCurly, n.info, setType) for j in countup(0, sonsLen(it) - 2): addSon(s, copyTree(it.sons[j])) - var inExpr = newNodeIT(nkCall, n.info, getSysType(tyBool)) - addSon(inExpr, newSymNode(opContains, n.info)) + var inExpr = newNodeIT(nkCall, n.info, getSysType(c.graph, n.info, tyBool)) + addSon(inExpr, newSymNode(c.graph.opContains, n.info)) addSon(inExpr, s) addSon(inExpr, copyTree(r.sons[0])) addSon(check, inExpr) @@ -859,20 +974,20 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent, if result != nil: if check == nil: check = newNodeI(nkCheckedFieldExpr, n.info) - addSon(check, ast.emptyNode) # make space for access node - var inExpr = newNodeIT(nkCall, n.info, getSysType(tyBool)) - addSon(inExpr, newSymNode(opContains, n.info)) + addSon(check, c.graph.emptyNode) # make space for access node + var inExpr = newNodeIT(nkCall, n.info, getSysType(c.graph, n.info, tyBool)) + addSon(inExpr, newSymNode(c.graph.opContains, n.info)) addSon(inExpr, s) addSon(inExpr, copyTree(r.sons[0])) - var notExpr = newNodeIT(nkCall, n.info, getSysType(tyBool)) - addSon(notExpr, newSymNode(opNot, n.info)) + var notExpr = newNodeIT(nkCall, n.info, getSysType(c.graph, n.info, tyBool)) + addSon(notExpr, newSymNode(c.graph.opNot, n.info)) addSon(notExpr, inExpr) addSon(check, notExpr) return - else: illFormedAst(it) + else: illFormedAst(it, c.config) of nkSym: if r.sym.name.id == field.id: result = r.sym - else: illFormedAst(n) + else: illFormedAst(n, c.config) const tyTypeParamsHolders = {tyGenericInst, tyCompositeTypeClass} @@ -880,6 +995,9 @@ const proc readTypeParameter(c: PContext, typ: PType, paramName: PIdent, info: TLineInfo): PNode = + # Note: This function will return emptyNode when attempting to read + # a static type parameter that is not yet resolved (e.g. this may + # happen in proc signatures such as `proc(x: T): array[T.sizeParam, U]` if typ.kind in {tyUserTypeClass, tyUserTypeClassInst}: for statement in typ.n: case statement.kind @@ -910,7 +1028,10 @@ proc readTypeParameter(c: PContext, typ: PType, if tParam.sym.name.id == paramName.id: let rawTyp = ty.sons[s + 1] if rawTyp.kind == tyStatic: - return rawTyp.n + if rawTyp.n != nil: + return rawTyp.n + else: + return c.graph.emptyNode else: let foundTyp = makeTypeDesc(c, rawTyp) return newSymNode(copySym(tParam.sym).linkTo(foundTyp), info) @@ -921,12 +1042,12 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = let s = getGenSym(c, sym) case s.kind of skConst: - markUsed(n.info, s, c.graph.usageSym) + markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) case skipTypes(s.typ, abstractInst-{tyTypeDesc}).kind of tyNil, tyChar, tyInt..tyInt64, tyFloat..tyFloat128, tyTuple, tySet, tyUInt..tyUInt64: - if s.magic == mNone: result = inlineConst(n, s) + if s.magic == mNone: result = inlineConst(c, n, s) else: result = newSymNode(s, n.info) of tyArray, tySequence: # Consider:: @@ -939,27 +1060,29 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = # It is clear that ``[]`` means two totally different things. Thus, we # copy `x`'s AST into each context, so that the type fixup phase can # deal with two different ``[]``. - if s.ast.len == 0: result = inlineConst(n, s) + if s.ast.len == 0: result = inlineConst(c, n, s) else: result = newSymNode(s, n.info) else: result = newSymNode(s, n.info) of skMacro: - if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0: - markUsed(n.info, s, c.graph.usageSym) + if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or + (n.kind notin nkCallKinds and s.requiredParams > 0): + markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) - result = newSymNode(s, n.info) + result = symChoice(c, n, s, scClosed) else: result = semMacroExpr(c, n, n, s, flags) of skTemplate: if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or + (n.kind notin nkCallKinds and s.requiredParams > 0) or sfCustomPragma in sym.flags: - markUsed(n.info, s, c.graph.usageSym) + markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) - result = newSymNode(s, n.info) + result = symChoice(c, n, s, scClosed) else: result = semTemplateExpr(c, n, s, flags) of skParam: - markUsed(n.info, s, c.graph.usageSym) + markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) if s.typ != nil and s.typ.kind == tyStatic and s.typ.n != nil: # XXX see the hack in sigmatch.nim ... @@ -969,19 +1092,19 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = # gensym'ed parameters that nevertheless have been forward declared # need a special fixup: let realParam = c.p.owner.typ.n[s.position+1] - internalAssert realParam.kind == nkSym and realParam.sym.kind == skParam + internalAssert c.config, realParam.kind == nkSym and realParam.sym.kind == skParam return newSymNode(c.p.owner.typ.n[s.position+1].sym, n.info) elif c.p.owner.kind == skMacro: # gensym'ed macro parameters need a similar hack (see bug #1944): var u = searchInScopes(c, s.name) - internalAssert u != nil and u.kind == skParam and u.owner == s.owner + internalAssert c.config, u != nil and u.kind == skParam and u.owner == s.owner return newSymNode(u, n.info) result = newSymNode(s, n.info) of skVar, skLet, skResult, skForVar: if s.magic == mNimvm: - localError(n.info, "illegal context for 'nimvm' magic") + localError(c.config, n.info, "illegal context for 'nimvm' magic") - markUsed(n.info, s, c.graph.usageSym) + markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(s, n.info) # We cannot check for access to outer vars for example because it's still @@ -999,9 +1122,9 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = n.typ = s.typ return n of skType: - markUsed(n.info, s, c.graph.usageSym) + markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) - if s.typ.kind == tyStatic and s.typ.n != nil: + if s.typ.kind == tyStatic and s.typ.base.kind != tyNone and s.typ.n != nil: return s.typ.n result = newSymNode(s, n.info) result.typ = makeTypeDesc(c, s.typ) @@ -1021,7 +1144,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = if f != nil and fieldVisible(c, f): # is the access to a public field or in the same module or in a friend? doAssert f == s - markUsed(n.info, f, c.graph.usageSym) + markUsed(c.config, n.info, f, c.graph.usageSym) styleCheckUse(n.info, f) result = newNodeIT(nkDotExpr, n.info, f.typ) result.add makeDeref(newSymNode(p.selfSym)) @@ -1034,25 +1157,25 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = if ty.sons[0] == nil: break ty = skipTypes(ty.sons[0], skipPtrs) # old code, not sure if it's live code: - markUsed(n.info, s, c.graph.usageSym) + markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(s, n.info) else: - markUsed(n.info, s, c.graph.usageSym) + markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(s, n.info) proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = ## returns nil if it's not a built-in field access - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) # tests/bind/tbindoverload.nim wants an early exit here, but seems to # work without now. template/tsymchoicefield doesn't like an early exit # here at all! #if isSymChoice(n.sons[1]): return when defined(nimsuggest): - if gCmd == cmdIdeTools: + if c.config.cmd == cmdIdeTools: suggestExpr(c, n) - if exactEquals(gTrackPos, n[1].info): suggestExprNoCheck(c, n) + if exactEquals(c.config.m.trackPos, n[1].info): suggestExprNoCheck(c, n) var s = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared, checkModule}) if s != nil: @@ -1060,14 +1183,14 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = result = symChoice(c, n, s, scClosed) if result.kind == nkSym: result = semSym(c, n, s, flags) else: - markUsed(n.sons[1].info, s, c.graph.usageSym) + markUsed(c.config, n.sons[1].info, s, c.graph.usageSym) result = semSym(c, n, s, flags) styleCheckUse(n.sons[1].info, s) return n.sons[0] = semExprWithType(c, n.sons[0], flags+{efDetermineType}) #restoreOldStyleType(n.sons[0]) - var i = considerQuotedIdent(n.sons[1], n) + var i = considerQuotedIdent(c, n.sons[1], n) var ty = n.sons[0].typ var f: PSym = nil result = nil @@ -1075,21 +1198,43 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = template tryReadingGenericParam(t: PType) = case t.kind of tyTypeParamsHolders: - return readTypeParameter(c, t, i, n.info) + result = readTypeParameter(c, t, i, n.info) + if result == c.graph.emptyNode: + result = n + n.typ = makeTypeFromExpr(c, n.copyTree) + return of tyUserTypeClasses: if t.isResolvedUserTypeClass: return readTypeParameter(c, t, i, n.info) else: n.typ = makeTypeFromExpr(c, copyTree(n)) return n - of tyGenericParam: + of tyGenericParam, tyAnything: n.typ = makeTypeFromExpr(c, copyTree(n)) return n else: discard - if isTypeExpr(n.sons[0]) or (ty.kind == tyTypeDesc and ty.base.kind != tyNone): - if ty.kind == tyTypeDesc: ty = ty.base + var argIsType = false + + if ty.kind == tyTypeDesc: + if ty.base.kind == tyNone: + # This is a still unresolved typedesc parameter. + # If this is a regular proc, then all bets are off and we must return + # tyFromExpr, but when this happen in a macro this is not a built-in + # field access and we leave the compiler to compile a normal call: + if getCurrOwner(c).kind != skMacro: + n.typ = makeTypeFromExpr(c, n.copyTree) + return n + else: + return nil + else: + ty = ty.base + argIsType = true + else: + argIsType = isTypeExpr(n.sons[0]) + + if argIsType: ty = ty.skipTypes(tyDotOpTransparent) case ty.kind of tyEnum: @@ -1102,7 +1247,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = result = newSymNode(f) result.info = n.info result.typ = ty - markUsed(n.info, f, c.graph.usageSym) + markUsed(c.config, n.info, f, c.graph.usageSym) styleCheckUse(n.info, f) return of tyObject, tyTuple: @@ -1133,7 +1278,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = if f != nil: if fieldVisible(c, f): # is the access to a public field or in the same module or in a friend? - markUsed(n.sons[1].info, f, c.graph.usageSym) + markUsed(c.config, n.sons[1].info, f, c.graph.usageSym) styleCheckUse(n.sons[1].info, f) n.sons[0] = makeDeref(n.sons[0]) n.sons[1] = newSymNode(f) # we now have the correct field @@ -1147,7 +1292,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = elif ty.kind == tyTuple and ty.n != nil: f = getSymFromList(ty.n, i) if f != nil: - markUsed(n.sons[1].info, f, c.graph.usageSym) + markUsed(c.config, n.sons[1].info, f, c.graph.usageSym) styleCheckUse(n.sons[1].info, f) n.sons[0] = makeDeref(n.sons[0]) n.sons[1] = newSymNode(f) @@ -1165,7 +1310,7 @@ proc dotTransformation(c: PContext, n: PNode): PNode = addSon(result, n.sons[1]) addSon(result, copyTree(n[0])) else: - var i = considerQuotedIdent(n.sons[1], n) + var i = considerQuotedIdent(c, n.sons[1], n) result = newNodeI(nkDotCall, n.info) result.flags.incl nfDotField addSon(result, newIdentNode(i, n[1].info)) @@ -1184,7 +1329,7 @@ proc buildOverloadedSubscripts(n: PNode, ident: PIdent): PNode = for i in 0 .. n.len-1: result.add(n[i]) proc semDeref(c: PContext, n: PNode): PNode = - checkSonsLen(n, 1) + checkSonsLen(n, 1, c.config) n.sons[0] = semExprWithType(c, n.sons[0]) result = n var t = skipTypes(n.sons[0].typ, {tyGenericInst, tyVar, tyLent, tyAlias, tySink}) @@ -1202,12 +1347,22 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = result = newNodeIT(nkDerefExpr, x.info, x.typ) result.add(x[0]) return - checkMinSonsLen(n, 2) + checkMinSonsLen(n, 2, c.config) # make sure we don't evaluate generic macros/templates n.sons[0] = semExprWithType(c, n.sons[0], - {efNoProcvarCheck, efNoEvaluateGeneric}) - let arr = skipTypes(n.sons[0].typ, {tyGenericInst, + {efNoEvaluateGeneric}) + var arr = skipTypes(n.sons[0].typ, {tyGenericInst, tyUserTypeClassInst, tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink}) + if arr.kind == tyStatic: + if arr.base.kind == tyNone: + result = n + result.typ = semStaticType(c, n[1], nil) + return + elif arr.n != nil: + return semSubscript(c, arr.n, flags) + else: + arr = arr.base + case arr.kind of tyArray, tyOpenArray, tyVarargs, tySequence, tyString, tyCString: @@ -1216,7 +1371,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = for i in countup(1, sonsLen(n) - 1): n.sons[i] = semExprWithType(c, n.sons[i], flags*{efInTypeof, efDetermineType}) - var indexType = if arr.kind == tyArray: arr.sons[0] else: getSysType(tyInt) + var indexType = if arr.kind == tyArray: arr.sons[0] else: getSysType(c.graph, n.info, tyInt) var arg = indexTypesMatch(c, indexType, n.sons[1].typ, n.sons[1]) if arg != nil: n.sons[1] = arg @@ -1237,9 +1392,9 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = n.sons[1] = semConstExpr(c, n.sons[1]) if skipTypes(n.sons[1].typ, {tyGenericInst, tyRange, tyOrdinal, tyAlias, tySink}).kind in {tyInt..tyInt64}: - var idx = getOrdValue(n.sons[1]) - if idx >= 0 and idx < sonsLen(arr): n.typ = arr.sons[int(idx)] - else: localError(n.info, errInvalidIndexValueForTuple) + let idx = getOrdValue(n.sons[1]) + if idx >= 0 and idx < len(arr): n.typ = arr.sons[int(idx)] + else: localError(c.config, n.info, "invalid index value for tuple subscript") result = n else: result = nil @@ -1276,11 +1431,11 @@ proc semArrayAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = result = semSubscript(c, n, flags) if result == nil: # overloaded [] operator: - result = semExpr(c, buildOverloadedSubscripts(n, getIdent"[]")) + result = semExpr(c, buildOverloadedSubscripts(n, getIdent(c.cache, "[]"))) proc propertyWriteAccess(c: PContext, n, nOrig, a: PNode): PNode = - var id = considerQuotedIdent(a[1], a) - var setterId = newIdentNode(getIdent(id.s & '='), n.info) + var id = considerQuotedIdent(c, a[1], a) + var setterId = newIdentNode(getIdent(c.cache, id.s & '='), n.info) # a[0] is already checked for semantics, that does ``builtinFieldAccess`` # this is ugly. XXX Semantic checking should use the ``nfSem`` flag for # nodes? @@ -1297,18 +1452,28 @@ proc propertyWriteAccess(c: PContext, n, nOrig, a: PNode): PNode = #analyseIfAddressTakenInCall(c, result) proc takeImplicitAddr(c: PContext, n: PNode; isLent: bool): PNode = + # See RFC #7373, calls returning 'var T' are assumed to + # return a view into the first argument (if there is one): + let root = exprRoot(n) + if root != nil and root.owner == c.p.owner: + if root.kind in {skLet, skVar, skTemp} and sfGlobal notin root.flags: + localError(c.config, n.info, "'$1' escapes its stack frame; context: '$2'; see $3/var_t_return.html" % [ + root.name.s, renderTree(n, {renderNoComments}), explanationsBaseUrl]) + elif root.kind == skParam and root.position != 0: + localError(c.config, n.info, "'$1' is not the first parameter; context: '$2'; see $3/var_t_return.html" % [ + root.name.s, renderTree(n, {renderNoComments}), explanationsBaseUrl]) case n.kind of nkHiddenAddr, nkAddr: return n of nkHiddenDeref, nkDerefExpr: return n.sons[0] of nkBracketExpr: if len(n) == 1: return n.sons[0] else: discard - var valid = isAssignable(c, n) + let valid = isAssignable(c, n) if valid != arLValue: if valid == arLocalLValue: - localError(n.info, errXStackEscape, renderTree(n, {renderNoComments})) + localError(c.config, n.info, errXStackEscape % renderTree(n, {renderNoComments})) elif not isLent: - localError(n.info, errExprHasNoAddress) + localError(c.config, n.info, errExprHasNoAddress) result = newNodeIT(nkHiddenAddr, n.info, makePtrType(c, n.typ)) result.add(n) @@ -1325,7 +1490,7 @@ template resultTypeIsInferrable(typ: PType): untyped = typ.isMetaType and typ.kind != tyTypeDesc proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) var a = n.sons[0] case a.kind of nkDotExpr: @@ -1347,7 +1512,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = # --> `[]=`(a, i, x) a = semSubscript(c, a, {efLValue}) if a == nil: - result = buildOverloadedSubscripts(n.sons[0], getIdent"[]=") + result = buildOverloadedSubscripts(n.sons[0], getIdent(c.cache, "[]=")) add(result, n[1]) if mode == noOverloadedSubscript: bracketNotFoundError(c, result) @@ -1357,15 +1522,15 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = return result of nkCurlyExpr: # a{i} = x --> `{}=`(a, i, x) - result = buildOverloadedSubscripts(n.sons[0], getIdent"{}=") + result = buildOverloadedSubscripts(n.sons[0], getIdent(c.cache, "{}=")) add(result, n[1]) return semExprNoType(c, result) - of nkPar: + of nkPar, nkTupleConstr: if a.len >= 2: # unfortunately we need to rewrite ``(x, y) = foo()`` already here so # that overloading of the assignment operator still works. Usually we # prefer to do these rewritings in transf.nim: - return semStmt(c, lowerTupleUnpackingForAsgn(n, c.p.owner)) + return semStmt(c, lowerTupleUnpackingForAsgn(c.graph, n, c.p.owner)) else: a = semExprWithType(c, a, {efLValue}) else: @@ -1378,7 +1543,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = isAssignable(c, a) == arNone) or skipTypes(le, abstractVar).kind in {tyOpenArray, tyVarargs}: # Direct assignment to a discriminant is allowed! - localError(a.info, errXCannotBeAssignedTo, + localError(c.config, a.info, errXCannotBeAssignedTo % renderTree(a, {renderNoComments})) else: let @@ -1388,26 +1553,22 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = rhs = semExprWithType(c, n.sons[1], if lhsIsResult: {efAllowDestructor} else: {}) if lhsIsResult: - n.typ = enforceVoidContext + n.typ = c.enforceVoidContext if c.p.owner.kind != skMacro and resultTypeIsInferrable(lhs.sym.typ): var rhsTyp = rhs.typ if rhsTyp.kind in tyUserTypeClasses and rhsTyp.isResolvedUserTypeClass: rhsTyp = rhsTyp.lastSon if cmpTypes(c, lhs.typ, rhsTyp) in {isGeneric, isEqual}: - internalAssert c.p.resultSym != nil + internalAssert c.config, c.p.resultSym != nil lhs.typ = rhsTyp c.p.resultSym.typ = rhsTyp c.p.owner.typ.sons[0] = rhsTyp else: - typeMismatch(n.info, lhs.typ, rhsTyp) + typeMismatch(c.config, n.info, lhs.typ, rhsTyp) n.sons[1] = fitNode(c, le, rhs, n.info) - if not newDestructors: - if tfHasAsgn in lhs.typ.flags and not lhsIsResult and - mode != noOverloadedAsgn: - return overloadedAsgn(c, lhs, n.sons[1]) - else: - liftTypeBoundOps(c, lhs.typ, lhs.info) + liftTypeBoundOps(c, lhs.typ, lhs.info) + #liftTypeBoundOps(c, n.sons[0].typ, n.sons[0].info) fixAbstractType(c, n) asgnToResultVar(c, n, n.sons[0], n.sons[1]) @@ -1415,7 +1576,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = proc semReturn(c: PContext, n: PNode): PNode = result = n - checkSonsLen(n, 1) + checkSonsLen(n, 1, c.config) if c.p.owner.kind in {skConverter, skMethod, skProc, skFunc, skMacro} or ( c.p.owner.kind == skIterator and c.p.owner.typ.callConv == ccClosure): if n.sons[0].kind != nkEmpty: @@ -1427,11 +1588,11 @@ proc semReturn(c: PContext, n: PNode): PNode = n.sons[0] = semAsgn(c, a) # optimize away ``result = result``: if n[0][1].kind == nkSym and n[0][1].sym == c.p.resultSym: - n.sons[0] = ast.emptyNode + n.sons[0] = c.graph.emptyNode else: - localError(n.info, errNoReturnTypeDeclared) + localError(c.config, n.info, errNoReturnTypeDeclared) else: - localError(n.info, errXNotAllowedHere, "\'return\'") + localError(c.config, n.info, "'return' not allowed here") proc semProcBody(c: PContext, n: PNode): PNode = openScope(c) @@ -1446,7 +1607,7 @@ proc semProcBody(c: PContext, n: PNode): PNode = # nil # # comment # are not expressions: - fixNilType(result) + fixNilType(c, result) else: var a = newNodeI(nkAsgn, n.info, 2) a.sons[0] = newSymNode(c.p.resultSym) @@ -1462,7 +1623,7 @@ proc semProcBody(c: PContext, n: PNode): PNode = c.p.resultSym.typ = errorType(c) c.p.owner.typ.sons[0] = nil else: - localError(c.p.resultSym.info, errCannotInferReturnType) + localError(c.config, c.p.resultSym.info, errCannotInferReturnType) closeScope(c) @@ -1479,23 +1640,21 @@ proc semYieldVarResult(c: PContext, n: PNode, restype: PType) = var e = skipTypes(t.sons[i], {tyGenericInst, tyAlias, tySink}) if e.kind in {tyVar, tyLent}: if e.kind == tyVar: e.flags.incl tfVarIsPtr # bugfix for #4048, #4910, #6892 - if n.sons[0].kind == nkPar: + if n.sons[0].kind in {nkPar, nkTupleConstr}: n.sons[0].sons[i] = takeImplicitAddr(c, n.sons[0].sons[i], e.kind == tyLent) elif n.sons[0].kind in {nkHiddenStdConv, nkHiddenSubConv} and - n.sons[0].sons[1].kind == nkPar: + n.sons[0].sons[1].kind in {nkPar, nkTupleConstr}: var a = n.sons[0].sons[1] a.sons[i] = takeImplicitAddr(c, a.sons[i], false) else: - localError(n.sons[0].info, errXExpected, "tuple constructor") + localError(c.config, n.sons[0].info, errXExpected, "tuple constructor") else: discard proc semYield(c: PContext, n: PNode): PNode = result = n - checkSonsLen(n, 1) + checkSonsLen(n, 1, c.config) if c.p.owner == nil or c.p.owner.kind != skIterator: - localError(n.info, errYieldNotAllowedHere) - elif c.p.inTryStmt > 0 and c.p.owner.typ.callConv != ccInline: - localError(n.info, errYieldNotAllowedInTryStmt) + localError(c.config, n.info, errYieldNotAllowedHere) elif n.sons[0].kind != nkEmpty: n.sons[0] = semExprWithType(c, n.sons[0]) # check for type compatibility: var iterType = c.p.owner.typ @@ -1503,7 +1662,7 @@ proc semYield(c: PContext, n: PNode): PNode = if restype != nil: if restype.kind != tyExpr: n.sons[0] = fitNode(c, restype, n.sons[0], n.info) - if n.sons[0].typ == nil: internalError(n.info, "semYield") + if n.sons[0].typ == nil: internalError(c.config, n.info, "semYield") if resultTypeIsInferrable(restype): let inferred = n.sons[0].typ @@ -1511,9 +1670,9 @@ proc semYield(c: PContext, n: PNode): PNode = semYieldVarResult(c, n, restype) else: - localError(n.info, errCannotReturnExpr) + localError(c.config, n.info, errCannotReturnExpr) elif c.p.owner.typ.sons[0] != nil: - localError(n.info, errGenerated, "yield statement must yield a value") + localError(c.config, n.info, errGenerated, "yield statement must yield a value") proc lookUpForDefined(c: PContext, i: PIdent, onlyCurrentScope: bool): PSym = if onlyCurrentScope: @@ -1528,37 +1687,35 @@ proc lookUpForDefined(c: PContext, n: PNode, onlyCurrentScope: bool): PSym = of nkDotExpr: result = nil if onlyCurrentScope: return - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) var m = lookUpForDefined(c, n.sons[0], onlyCurrentScope) if m != nil and m.kind == skModule: - let ident = considerQuotedIdent(n[1], n) + let ident = considerQuotedIdent(c, n[1], n) if m == c.module: result = strTableGet(c.topLevelScope.symbols, ident) else: result = strTableGet(m.tab, ident) of nkAccQuoted: - result = lookUpForDefined(c, considerQuotedIdent(n), onlyCurrentScope) + result = lookUpForDefined(c, considerQuotedIdent(c, n), onlyCurrentScope) of nkSym: result = n.sym of nkOpenSymChoice, nkClosedSymChoice: result = n.sons[0].sym else: - localError(n.info, errIdentifierExpected, renderTree(n)) + localError(c.config, n.info, "identifier expected, but got: " & renderTree(n)) result = nil proc semDefined(c: PContext, n: PNode, onlyCurrentScope: bool): PNode = - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) # we replace this node by a 'true' or 'false' node: result = newIntNode(nkIntLit, 0) - if not onlyCurrentScope and considerQuotedIdent(n[0], n).s == "defined": - if n.sons[1].kind != nkIdent: - localError(n.info, "obsolete usage of 'defined', use 'declared' instead") - elif condsyms.isDefined(n.sons[1].ident): - result.intVal = 1 + if not onlyCurrentScope and considerQuotedIdent(c, n[0], n).s == "defined": + let d = considerQuotedIdent(c, n[1], n) + result.intVal = ord isDefined(c.config, d.s) elif lookUpForDefined(c, n.sons[1], onlyCurrentScope) != nil: result.intVal = 1 result.info = n.info - result.typ = getSysType(tyBool) + result.typ = getSysType(c.graph, n.info, tyBool) proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym = ## The argument to the proc should be nkCall(...) or similar @@ -1570,12 +1727,12 @@ proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym = return errorSym(c, n[0]) if expandedSym.kind notin {skMacro, skTemplate}: - localError(n.info, errXisNoMacroOrTemplate, expandedSym.name.s) + localError(c.config, n.info, "'$1' is not a macro or template" % expandedSym.name.s) return errorSym(c, n[0]) result = expandedSym else: - localError(n.info, errXisNoMacroOrTemplate, n.renderTree) + localError(c.config, n.info, "'$1' is not a macro or template" % n.renderTree) result = errorSym(c, n) proc expectString(c: PContext, n: PNode): string = @@ -1583,11 +1740,7 @@ proc expectString(c: PContext, n: PNode): string = if n.kind in nkStrKinds: return n.strVal else: - localError(n.info, errStringLiteralExpected) - -proc getMagicSym(magic: TMagic): PSym = - result = newSym(skProc, getIdent($magic), systemModule, gCodegenLineInfo) - result.magic = magic + localError(c.config, n.info, errStringLiteralExpected) proc newAnonSym(c: PContext; kind: TSymKind, info: TLineInfo): PSym = result = newSym(kind, c.cache.idAnon, getCurrOwner(c), info) @@ -1601,7 +1754,7 @@ proc semExpandToAst(c: PContext, n: PNode): PNode = if expandedSym.kind == skError: return n macroCall.sons[0] = newSymNode(expandedSym, macroCall.info) - markUsed(n.info, expandedSym, c.graph.usageSym) + markUsed(c.config, n.info, expandedSym, c.graph.usageSym) styleCheckUse(n.info, expandedSym) if isCallExpr(macroCall): @@ -1620,26 +1773,25 @@ proc semExpandToAst(c: PContext, n: PNode): PNode = inc cands symx = nextOverloadIter(o, c, headSymbol) if cands == 0: - localError(n.info, "expected a template that takes " & $(macroCall.len-1) & " arguments") + localError(c.config, n.info, "expected a template that takes " & $(macroCall.len-1) & " arguments") elif cands >= 2: - localError(n.info, "ambiguous symbol in 'getAst' context: " & $macroCall) + localError(c.config, n.info, "ambiguous symbol in 'getAst' context: " & $macroCall) else: let info = macroCall.sons[0].info macroCall.sons[0] = newSymNode(cand, info) - markUsed(info, cand, c.graph.usageSym) + markUsed(c.config, info, cand, c.graph.usageSym) styleCheckUse(info, cand) # we just perform overloading resolution here: #n.sons[1] = semOverloadedCall(c, macroCall, macroCall, {skTemplate, skMacro}) else: - localError(n.info, "getAst takes a call, but got " & n.renderTree) + localError(c.config, n.info, "getAst takes a call, but got " & n.renderTree) # Preserve the magic symbol in order to be handled in evals.nim - internalAssert n.sons[0].sym.magic == mExpandToAst + internalAssert c.config, n.sons[0].sym.magic == mExpandToAst #n.typ = getSysSym("NimNode").typ # expandedSym.getReturnType if n.kind == nkStmtList and n.len == 1: result = n[0] else: result = n - result.typ = if getCompilerProc("NimNode") != nil: sysTypeFromName"NimNode" - else: sysTypeFromName"PNimrodNode" + result.typ = sysTypeFromName(c.graph, n.info, "NimNode") proc semExpandToAst(c: PContext, n: PNode, magicSym: PSym, flags: TExprFlags = {}): PNode = @@ -1649,31 +1801,31 @@ proc semExpandToAst(c: PContext, n: PNode, magicSym: PSym, else: result = semDirectOp(c, n, flags) -proc processQuotations(n: var PNode, op: string, +proc processQuotations(c: PContext; n: var PNode, op: string, quotes: var seq[PNode], ids: var seq[PNode]) = template returnQuote(q) = quotes.add q - n = newIdentNode(getIdent($quotes.len), n.info) + n = newIdentNode(getIdent(c.cache, $quotes.len), n.info) ids.add n return if n.kind == nkPrefix: - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) if n[0].kind == nkIdent: var examinedOp = n[0].ident.s if examinedOp == op: returnQuote n[1] elif examinedOp.startsWith(op): - n.sons[0] = newIdentNode(getIdent(examinedOp.substr(op.len)), n.info) + n.sons[0] = newIdentNode(getIdent(c.cache, examinedOp.substr(op.len)), n.info) elif n.kind == nkAccQuoted and op == "``": returnQuote n[0] for i in 0 ..< n.safeLen: - processQuotations(n.sons[i], op, quotes, ids) + processQuotations(c, n.sons[i], op, quotes, ids) proc semQuoteAst(c: PContext, n: PNode): PNode = - internalAssert n.len == 2 or n.len == 3 + internalAssert c.config, n.len == 2 or n.len == 3 # We transform the do block into a template with a param for # each interpolation. We'll pass this template to getAst. var @@ -1686,56 +1838,60 @@ proc semQuoteAst(c: PContext, n: PNode): PNode = # this will store the generated param names if quotedBlock.kind != nkStmtList: - localError(n.info, errXExpected, "block") + localError(c.config, n.info, errXExpected, "block") - processQuotations(quotedBlock, op, quotes, ids) + processQuotations(c, quotedBlock, op, quotes, ids) var dummyTemplate = newProcNode( - nkTemplateDef, quotedBlock.info, quotedBlock, - name = newAnonSym(c, skTemplate, n.info).newSymNode) + nkTemplateDef, quotedBlock.info, body = quotedBlock, + params = c.graph.emptyNode, + name = newAnonSym(c, skTemplate, n.info).newSymNode, + pattern = c.graph.emptyNode, genericParams = c.graph.emptyNode, + pragmas = c.graph.emptyNode, exceptions = c.graph.emptyNode) if ids.len > 0: dummyTemplate.sons[paramsPos] = newNodeI(nkFormalParams, n.info) - dummyTemplate[paramsPos].add getSysSym("typed").newSymNode # return type - ids.add getSysSym("untyped").newSymNode # params type - ids.add emptyNode # no default value + dummyTemplate[paramsPos].add getSysSym(c.graph, n.info, "typed").newSymNode # return type + ids.add getSysSym(c.graph, n.info, "untyped").newSymNode # params type + ids.add c.graph.emptyNode # no default value dummyTemplate[paramsPos].add newNode(nkIdentDefs, n.info, ids) var tmpl = semTemplateDef(c, dummyTemplate) quotes[0] = tmpl[namePos] result = newNode(nkCall, n.info, @[ - getMagicSym(mExpandToAst).newSymNode, + createMagic(c.graph, "getAst", mExpandToAst).newSymNode, newNode(nkCall, n.info, quotes)]) result = semExpandToAst(c, result) proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = # watch out, hacks ahead: - let oldErrorCount = msgs.gErrorCounter - let oldErrorMax = msgs.gErrorMax + let oldErrorCount = c.config.errorCounter + let oldErrorMax = c.config.errorMax let oldCompilesId = c.compilesContextId inc c.compilesContextIdGenerator c.compilesContextId = c.compilesContextIdGenerator # do not halt after first error: - msgs.gErrorMax = high(int) + c.config.errorMax = high(int) # open a scope for temporary symbol inclusions: let oldScope = c.currentScope openScope(c) let oldOwnerLen = len(c.graph.owners) let oldGenerics = c.generics - let oldErrorOutputs = errorOutputs - if efExplain notin flags: errorOutputs = {} - let oldContextLen = msgs.getInfoContextLen() + let oldErrorOutputs = c.config.m.errorOutputs + if efExplain notin flags: c.config.m.errorOutputs = {} + let oldContextLen = msgs.getInfoContextLen(c.config) let oldInGenericContext = c.inGenericContext let oldInUnrolledContext = c.inUnrolledContext let oldInGenericInst = c.inGenericInst + let oldInStaticContext = c.inStaticContext let oldProcCon = c.p c.generics = @[] var err: string try: result = semExpr(c, n, flags) - if msgs.gErrorCounter != oldErrorCount: result = nil + if c.config.errorCounter != oldErrorCount: result = nil except ERecoverableError: discard # undo symbol table changes (as far as it's possible): @@ -1744,13 +1900,14 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = c.inGenericContext = oldInGenericContext c.inUnrolledContext = oldInUnrolledContext c.inGenericInst = oldInGenericInst + c.inStaticContext = oldInStaticContext c.p = oldProcCon - msgs.setInfoContextLen(oldContextLen) + msgs.setInfoContextLen(c.config, oldContextLen) setLen(c.graph.owners, oldOwnerLen) c.currentScope = oldScope - errorOutputs = oldErrorOutputs - msgs.gErrorCounter = oldErrorCount - msgs.gErrorMax = oldErrorMax + c.config.m.errorOutputs = oldErrorOutputs + c.config.errorCounter = oldErrorCount + c.config.errorMax = oldErrorMax proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode = # we replace this node by a 'true' or 'false' node: @@ -1758,7 +1915,7 @@ proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode = result = newIntNode(nkIntLit, ord(tryExpr(c, n[1], flags) != nil)) result.info = n.info - result.typ = getSysType(tyBool) + result.typ = getSysType(c.graph, n.info, tyBool) proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode = if sonsLen(n) == 3: @@ -1773,15 +1930,15 @@ proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode = proc createFlowVar(c: PContext; t: PType; info: TLineInfo): PType = result = newType(tyGenericInvocation, c.module) - addSonSkipIntLit(result, magicsys.getCompilerProc("FlowVar").typ) + addSonSkipIntLit(result, magicsys.getCompilerProc(c.graph, "FlowVar").typ) addSonSkipIntLit(result, t) result = instGenericContainer(c, info, result, allowMetaTypes = false) proc instantiateCreateFlowVarCall(c: PContext; t: PType; info: TLineInfo): PSym = - let sym = magicsys.getCompilerProc("nimCreateFlowVar") + let sym = magicsys.getCompilerProc(c.graph, "nimCreateFlowVar") if sym == nil: - localError(info, errSystemNeeds, "nimCreateFlowVar") + localError(c.config, info, "system needs: nimCreateFlowVar") var bindings: TIdTable initIdTable(bindings) bindings.idTablePut(sym.ast[genericParamsPos].sons[0].typ, t) @@ -1797,23 +1954,16 @@ proc setMs(n: PNode, s: PSym): PNode = n.sons[0] = newSymNode(s) n.sons[0].info = n.info -proc extractImports(n: PNode; result: PNode) = - if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}: - result.add copyTree(n) - n.kind = nkEmpty - return - for i in 0..<n.safeLen: extractImports(n[i], result) - proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = # this is a hotspot in the compiler! # DON'T forget to update ast.SpecialSemMagics if you add a magic here! result = n case s.magic # magics that need special treatment of mAddr: - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) result = semAddr(c, n.sons[1], s.name.s == "unsafeAddr") of mTypeOf: - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) result = semTypeOf(c, n.sons[1]) #of mArrGet: result = semArrGet(c, n, flags) #of mArrPut: result = semArrPut(c, n, flags) @@ -1830,12 +1980,12 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = of mExpandToAst: result = semExpandToAst(c, n, s, flags) of mQuoteAst: result = semQuoteAst(c, n) of mAstToStr: - checkSonsLen(n, 2) - result = newStrNodeT(renderTree(n[1], {renderNoComments}), n) - result.typ = getSysType(tyString) + checkSonsLen(n, 2, c.config) + result = newStrNodeT(renderTree(n[1], {renderNoComments}), n, c.graph) + result.typ = getSysType(c.graph, n.info, tyString) of mParallel: - if not experimentalMode(c): - localError(n.info, "use the {.experimental.} pragma to enable 'parallel'") + if parallel notin c.features: + localError(c.config, n.info, "use the {.experimental.} pragma to enable 'parallel'") result = setMs(n, s) var x = n.lastSon if x.kind == nkDo: x = x.sons[bodyPos] @@ -1854,7 +2004,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = result.typ = typ result.add instantiateCreateFlowVarCall(c, typ, n.info).newSymNode else: - result.add emptyNode + result.add c.graph.emptyNode of mProcCall: result = setMs(n, s) result.sons[1] = semExpr(c, n.sons[1]) @@ -1876,19 +2026,21 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = if callee.magic != mNone: result = magicsAfterOverloadResolution(c, result, flags) of mRunnableExamples: - if gCmd == cmdDoc and n.len >= 2 and n.lastSon.kind == nkStmtList: - if sfMainModule in c.module.flags: - let inp = toFullPath(c.module.info) - if c.runnableExamples == nil: - c.runnableExamples = newTree(nkStmtList, - newTree(nkImportStmt, newStrNode(nkStrLit, expandFilename(inp)))) - let imports = newTree(nkStmtList) - extractImports(n.lastSon, imports) - for imp in imports: c.runnableExamples.add imp - c.runnableExamples.add newTree(nkBlockStmt, emptyNode, copyTree n.lastSon) + if c.config.cmd == cmdDoc and n.len >= 2 and n.lastSon.kind == nkStmtList: + when false: + if sfMainModule in c.module.flags: + let inp = toFullPath(c.config, c.module.info) + if c.runnableExamples == nil: + c.runnableExamples = newTree(nkStmtList, + newTree(nkImportStmt, newStrNode(nkStrLit, expandFilename(inp)))) + let imports = newTree(nkStmtList) + var savedLastSon = copyTree n.lastSon + extractImports(savedLastSon, imports) + for imp in imports: c.runnableExamples.add imp + c.runnableExamples.add newTree(nkBlockStmt, c.graph.emptyNode, copyTree savedLastSon) result = setMs(n, s) else: - result = emptyNode + result = c.graph.emptyNode else: result = semDirectOp(c, n, flags) @@ -1921,14 +2073,14 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode = var it = n.sons[i] case it.kind of nkElifBranch, nkElifExpr: - checkSonsLen(it, 2) + checkSonsLen(it, 2, c.config) if whenNimvm: if semCheck: it.sons[1] = semExpr(c, it.sons[1]) typ = commonType(typ, it.sons[1].typ) result = n # when nimvm is not elimited until codegen else: - var e = semConstExpr(c, it.sons[0]) + let e = forceBool(c, semConstExpr(c, it.sons[0])) if e.kind != nkIntLit: # can happen for cascading errors, assume false # InternalError(n.info, "semWhen") @@ -1936,14 +2088,14 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode = elif e.intVal != 0 and result == nil: setResult(it.sons[1]) of nkElse, nkElseExpr: - checkSonsLen(it, 1) + checkSonsLen(it, 1, c.config) if result == nil or whenNimvm: if semCheck: it.sons[0] = semExpr(c, it.sons[0]) typ = commonType(typ, it.sons[0].typ) if result == nil: result = it.sons[0] - else: illFormedAst(n) + else: illFormedAst(n, c.config) if result == nil: result = newNodeI(nkEmpty, n.info) if whenNimvm: result.typ = typ @@ -1962,7 +2114,7 @@ proc semSetConstr(c: PContext, n: PNode): PNode = var typ: PType = nil for i in countup(0, sonsLen(n) - 1): if isRange(n.sons[i]): - checkSonsLen(n.sons[i], 3) + checkSonsLen(n.sons[i], 3, c.config) n.sons[i].sons[1] = semExprWithType(c, n.sons[i].sons[1]) n.sons[i].sons[2] = semExprWithType(c, n.sons[i].sons[2]) if typ == nil: @@ -1978,10 +2130,10 @@ proc semSetConstr(c: PContext, n: PNode): PNode = n.sons[i] = semExprWithType(c, n.sons[i]) if typ == nil: typ = skipTypes(n.sons[i].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) - if not isOrdinalType(typ): - localError(n.info, errOrdinalTypeExpected) + if not isOrdinalType(typ, allowEnumWithHoles=true): + localError(c.config, n.info, errOrdinalTypeExpected) typ = makeRangeType(c, 0, MaxSetElements-1, n.info) - elif lengthOrd(typ) > MaxSetElements: + elif lengthOrd(c.config, typ) > MaxSetElements: typ = makeRangeType(c, 0, MaxSetElements-1, n.info) addSonSkipIntLit(result.typ, typ) for i in countup(0, sonsLen(n) - 1): @@ -2005,31 +2157,32 @@ proc semTableConstr(c: PContext, n: PNode): PNode = var x = n.sons[i] if x.kind == nkExprColonExpr and sonsLen(x) == 2: for j in countup(lastKey, i-1): - var pair = newNodeI(nkPar, x.info) + var pair = newNodeI(nkTupleConstr, x.info) pair.add(n.sons[j]) pair.add(x[1]) result.add(pair) - var pair = newNodeI(nkPar, x.info) + var pair = newNodeI(nkTupleConstr, x.info) pair.add(x[0]) pair.add(x[1]) result.add(pair) lastKey = i+1 - if lastKey != n.len: illFormedAst(n) + if lastKey != n.len: illFormedAst(n, c.config) result = semExpr(c, result) type TParKind = enum paNone, paSingle, paTupleFields, paTuplePositions -proc checkPar(n: PNode): TParKind = +proc checkPar(c: PContext; n: PNode): TParKind = var length = sonsLen(n) if length == 0: result = paTuplePositions # () elif length == 1: if n.sons[0].kind == nkExprColonExpr: result = paTupleFields + elif n.kind == nkTupleConstr: result = paTuplePositions else: result = paSingle # (expr) else: if n.sons[0].kind == nkExprColonExpr: result = paTupleFields @@ -2038,26 +2191,26 @@ proc checkPar(n: PNode): TParKind = if result == paTupleFields: if (n.sons[i].kind != nkExprColonExpr) or not (n.sons[i].sons[0].kind in {nkSym, nkIdent}): - localError(n.sons[i].info, errNamedExprExpected) + localError(c.config, n.sons[i].info, errNamedExprExpected) return paNone else: if n.sons[i].kind == nkExprColonExpr: - localError(n.sons[i].info, errNamedExprNotAllowed) + localError(c.config, n.sons[i].info, errNamedExprNotAllowed) return paNone proc semTupleFieldsConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = - result = newNodeI(nkPar, n.info) + result = newNodeI(nkTupleConstr, n.info) var typ = newTypeS(tyTuple, c) typ.n = newNodeI(nkRecList, n.info) # nkIdentDefs var ids = initIntSet() for i in countup(0, sonsLen(n) - 1): if n[i].kind != nkExprColonExpr or n[i][0].kind notin {nkSym, nkIdent}: - illFormedAst(n.sons[i]) + illFormedAst(n.sons[i], c.config) var id: PIdent if n.sons[i].sons[0].kind == nkIdent: id = n.sons[i].sons[0].ident else: id = n.sons[i].sons[0].sym.name if containsOrIncl(ids, id.id): - localError(n.sons[i].info, errFieldInitTwice, id.s) + localError(c.config, n.sons[i].info, errFieldInitTwice % id.s) n.sons[i].sons[1] = semExprWithType(c, n.sons[i].sons[1], flags*{efAllowDestructor}) var f = newSymS(skField, n.sons[i].sons[0], c) @@ -2071,6 +2224,7 @@ proc semTupleFieldsConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = result = n # we don't modify n, but compute the type: + result.kind = nkTupleConstr var typ = newTypeS(tyTuple, c) # leave typ.n nil! for i in countup(0, sonsLen(n) - 1): n.sons[i] = semExprWithType(c, n.sons[i], flags*{efAllowDestructor}) @@ -2090,15 +2244,15 @@ include semobjconstr proc semBlock(c: PContext, n: PNode): PNode = result = n inc(c.p.nestedBlockCounter) - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) openScope(c) # BUGFIX: label is in the scope of block! if n.sons[0].kind != nkEmpty: var labl = newSymG(skLabel, n.sons[0], c) if sfGenSym notin labl.flags: addDecl(c, labl) n.sons[0] = newSymNode(labl, n.sons[0].info) - suggestSym(n.sons[0].info, labl, c.graph.usageSym) - styleCheckDef(labl) + suggestSym(c.config, n.sons[0].info, labl, c.graph.usageSym) + styleCheckDef(c.config, labl) n.sons[1] = semExpr(c, n.sons[1]) n.typ = n.sons[1].typ if isEmptyType(n.typ): n.kind = nkBlockStmt @@ -2106,15 +2260,31 @@ proc semBlock(c: PContext, n: PNode): PNode = closeScope(c) dec(c.p.nestedBlockCounter) +proc semExportExcept(c: PContext, n: PNode): PNode = + let moduleName = semExpr(c, n[0]) + if moduleName.kind != nkSym or moduleName.sym.kind != skModule: + localError(c.config, n.info, "The export/except syntax expects a module name") + return n + let exceptSet = readExceptSet(c, n) + let exported = moduleName.sym + strTableAdd(c.module.tab, exported) + var i: TTabIter + var s = initTabIter(i, exported.tab) + while s != nil: + if s.kind in ExportableSymKinds+{skModule} and + s.name.id notin exceptSet: + strTableAdd(c.module.tab, s) + s = nextIter(i, exported.tab) + result = n + proc semExport(c: PContext, n: PNode): PNode = var x = newNodeI(n.kind, n.info) - #let L = if n.kind == nkExportExceptStmt: L = 1 else: n.len for i in 0..<n.len: let a = n.sons[i] var o: TOverloadIter var s = initOverloadIter(o, c, a) if s == nil: - localError(a.info, errGenerated, "cannot export: " & renderTree(a)) + localError(c.config, a.info, errGenerated, "cannot export: " & renderTree(a)) elif s.kind == skModule: # forward everything from that module: strTableAdd(c.module.tab, s) @@ -2148,7 +2318,7 @@ proc shouldBeBracketExpr(n: PNode): bool = proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result = n - if gCmd == cmdIdeTools: suggestExpr(c, n) + if c.config.cmd == cmdIdeTools: suggestExpr(c, n) if nfSem in n.flags: return case n.kind of nkIdent, nkAccQuoted: @@ -2167,54 +2337,54 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = if result.kind == nkSym: markIndirect(c, result.sym) # if isGenericRoutine(result.sym): - # localError(n.info, errInstantiateXExplicitly, s.name.s) + # localError(c.config, n.info, errInstantiateXExplicitly, s.name.s) of nkSym: # because of the changed symbol binding, this does not mean that we # don't have to check the symbol for semantics here again! result = semSym(c, n, n.sym, flags) - of nkEmpty, nkNone, nkCommentStmt: + of nkEmpty, nkNone, nkCommentStmt, nkType: discard of nkNilLit: - if result.typ == nil: result.typ = getSysType(tyNil) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyNil) of nkIntLit: - if result.typ == nil: setIntLitType(result) + if result.typ == nil: setIntLitType(c.graph, result) of nkInt8Lit: - if result.typ == nil: result.typ = getSysType(tyInt8) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyInt8) of nkInt16Lit: - if result.typ == nil: result.typ = getSysType(tyInt16) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyInt16) of nkInt32Lit: - if result.typ == nil: result.typ = getSysType(tyInt32) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyInt32) of nkInt64Lit: - if result.typ == nil: result.typ = getSysType(tyInt64) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyInt64) of nkUIntLit: - if result.typ == nil: result.typ = getSysType(tyUInt) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt) of nkUInt8Lit: - if result.typ == nil: result.typ = getSysType(tyUInt8) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt8) of nkUInt16Lit: - if result.typ == nil: result.typ = getSysType(tyUInt16) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt16) of nkUInt32Lit: - if result.typ == nil: result.typ = getSysType(tyUInt32) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt32) of nkUInt64Lit: - if result.typ == nil: result.typ = getSysType(tyUInt64) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt64) #of nkFloatLit: # if result.typ == nil: result.typ = getFloatLitType(result) of nkFloat32Lit: - if result.typ == nil: result.typ = getSysType(tyFloat32) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyFloat32) of nkFloat64Lit, nkFloatLit: - if result.typ == nil: result.typ = getSysType(tyFloat64) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyFloat64) of nkFloat128Lit: - if result.typ == nil: result.typ = getSysType(tyFloat128) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyFloat128) of nkStrLit..nkTripleStrLit: - if result.typ == nil: result.typ = getSysType(tyString) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyString) of nkCharLit: - if result.typ == nil: result.typ = getSysType(tyChar) + if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyChar) of nkDotExpr: result = semFieldAccess(c, n, flags) if result.kind == nkDotCall: result.kind = nkCall result = semExpr(c, result, flags) of nkBind: - message(n.info, warnDeprecated, "bind") + message(c.config, n.info, warnDeprecated, "bind") result = semExpr(c, n.sons[0], flags) of nkTypeOfExpr, nkTupleTy, nkTupleClassTy, nkRefTy..nkEnumTy, nkStaticTy: if c.matchedConcept != nil and n.len == 1: @@ -2227,13 +2397,13 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result.typ = makeTypeDesc(c, typ) of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: # check if it is an expression macro: - checkMinSonsLen(n, 1) + checkMinSonsLen(n, 1, c.config) #when defined(nimsuggest): - # if gIdeCmd == ideCon and gTrackPos == n.info: suggestExprNoCheck(c, n) + # if gIdeCmd == ideCon and c.config.m.trackPos == n.info: suggestExprNoCheck(c, n) let mode = if nfDotField in n.flags: {} else: {checkUndeclared} var s = qualifiedLookUp(c, n.sons[0], mode) if s != nil: - #if gCmd == cmdPretty and n.sons[0].kind == nkDotExpr: + #if c.config.cmd == cmdPretty and n.sons[0].kind == nkDotExpr: # pretty.checkUse(n.sons[0].sons[1].info, s) case s.kind of skMacro: @@ -2283,15 +2453,15 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = else: result = semExpr(c, result, flags) of nkBracketExpr: - checkMinSonsLen(n, 1) + checkMinSonsLen(n, 1, c.config) result = semArrayAccess(c, n, flags) of nkCurlyExpr: - result = semExpr(c, buildOverloadedSubscripts(n, getIdent"{}"), flags) + result = semExpr(c, buildOverloadedSubscripts(n, getIdent(c.cache, "{}")), flags) of nkPragmaExpr: var expr = n[0] pragma = n[1] - pragmaName = considerQuotedIdent(pragma[0]) + pragmaName = considerQuotedIdent(c, pragma[0]) flags = flags case whichKeyword(pragmaName) @@ -2299,11 +2469,11 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = flags.incl efExplain else: # what other pragmas are allowed for expressions? `likely`, `unlikely` - invalidPragma(n) + invalidPragma(c, n) result = semExpr(c, n[0], flags) - of nkPar: - case checkPar(n) + of nkPar, nkTupleConstr: + case checkPar(c, n) of paNone: result = errorNode(c, n) of paTuplePositions: var tupexp = semTuplePositionsConstr(c, n, flags) @@ -2322,24 +2492,24 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkDerefExpr: result = semDeref(c, n) of nkAddr: result = n - checkSonsLen(n, 1) + checkSonsLen(n, 1, c.config) result = semAddr(c, n.sons[0]) of nkHiddenAddr, nkHiddenDeref: - checkSonsLen(n, 1) + checkSonsLen(n, 1, c.config) n.sons[0] = semExpr(c, n.sons[0], flags) of nkCast: result = semCast(c, n) of nkIfExpr, nkIfStmt: result = semIf(c, n) of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkHiddenCallConv: - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) considerGenSyms(c, n) of nkStringToCString, nkCStringToString, nkObjDownConv, nkObjUpConv: - checkSonsLen(n, 1) + checkSonsLen(n, 1, c.config) considerGenSyms(c, n) of nkChckRangeF, nkChckRange64, nkChckRange: - checkSonsLen(n, 3) + checkSonsLen(n, 3, c.config) considerGenSyms(c, n) of nkCheckedFieldExpr: - checkMinSonsLen(n, 2) + checkMinSonsLen(n, 2, c.config) considerGenSyms(c, n) of nkTableConstr: result = semTableConstr(c, n) @@ -2347,8 +2517,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = # handling of sym choices is context dependent # the node is left intact for now discard - of nkStaticExpr: - result = semStaticExpr(c, n) + of nkStaticExpr: result = semStaticExpr(c, n[0]) of nkAsgn: result = semAsgn(c, n) of nkBlockStmt, nkBlockExpr: result = semBlock(c, n) of nkStmtList, nkStmtListExpr: result = semStmtList(c, n, flags) @@ -2376,20 +2545,30 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkMacroDef: result = semMacroDef(c, n) of nkTemplateDef: result = semTemplateDef(c, n) of nkImportStmt: - if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "import") + # this particular way allows 'import' in a 'compiles' context so that + # template canImport(x): bool = + # compiles: + # import x + # + # works: + if c.currentScope.depthLevel > 2 + c.compilesContextId: + localError(c.config, n.info, errXOnlyAtModuleScope % "import") result = evalImport(c, n) of nkImportExceptStmt: - if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "import") + if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "import") result = evalImportExcept(c, n) of nkFromStmt: - if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "from") + if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "from") result = evalFrom(c, n) of nkIncludeStmt: - #if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "include") + #if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "include") result = evalInclude(c, n) - of nkExportStmt, nkExportExceptStmt: - if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "export") + of nkExportStmt: + if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "export") result = semExport(c, n) + of nkExportExceptStmt: + if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "export") + result = semExportExcept(c, n) of nkPragmaBlock: result = semPragmaBlock(c, n) of nkStaticStmt: @@ -2397,14 +2576,14 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkDefer: n.sons[0] = semExpr(c, n.sons[0]) if not n.sons[0].typ.isEmptyType and not implicitlyDiscardable(n.sons[0]): - localError(n.info, errGenerated, "'defer' takes a 'void' expression") - #localError(n.info, errGenerated, "'defer' not allowed in this context") + localError(c.config, n.info, "'defer' takes a 'void' expression") + #localError(c.config, n.info, errGenerated, "'defer' not allowed in this context") of nkGotoState, nkState: - if n.len != 1 and n.len != 2: illFormedAst(n) + if n.len != 1 and n.len != 2: illFormedAst(n, c.config) for i in 0 ..< n.len: n.sons[i] = semExpr(c, n.sons[i]) of nkComesFrom: discard "ignore the comes from information for now" else: - localError(n.info, errInvalidExpressionX, + localError(c.config, n.info, "invalid expression: " & renderTree(n, {renderNoComments})) if result != nil: incl(result.flags, nfSem) diff --git a/compiler/semfields.nim b/compiler/semfields.nim index c5bc07d77..869f5ae74 100644 --- a/compiler/semfields.nim +++ b/compiler/semfields.nim @@ -16,16 +16,17 @@ type tupleIndex: int field: PSym replaceByFieldName: bool + c: PContext proc instFieldLoopBody(c: TFieldInstCtx, n: PNode, forLoop: PNode): PNode = case n.kind of nkEmpty..pred(nkIdent), succ(nkSym)..nkNilLit: result = n of nkIdent, nkSym: result = n - let ident = considerQuotedIdent(n) + let ident = considerQuotedIdent(c.c, n) var L = sonsLen(forLoop) if c.replaceByFieldName: - if ident.id == considerQuotedIdent(forLoop[0]).id: + if ident.id == considerQuotedIdent(c.c, forLoop[0]).id: let fieldName = if c.tupleType.isNil: c.field.name.s elif c.tupleType.n.isNil: "Field" & $c.tupleIndex else: c.tupleType.n.sons[c.tupleIndex].sym.name.s @@ -33,7 +34,7 @@ proc instFieldLoopBody(c: TFieldInstCtx, n: PNode, forLoop: PNode): PNode = return # other fields: for i in ord(c.replaceByFieldName)..L-3: - if ident.id == considerQuotedIdent(forLoop[i]).id: + if ident.id == considerQuotedIdent(c.c, forLoop[i]).id: var call = forLoop.sons[L-2] var tupl = call.sons[i+1-ord(c.replaceByFieldName)] if c.field.isNil: @@ -47,7 +48,7 @@ proc instFieldLoopBody(c: TFieldInstCtx, n: PNode, forLoop: PNode): PNode = break else: if n.kind == nkContinueStmt: - localError(n.info, errGenerated, + localError(c.c.config, n.info, "'continue' not supported in a 'fields' loop") result = copyNode(n) newSons(result, sonsLen(n)) @@ -63,6 +64,7 @@ proc semForObjectFields(c: TFieldsCtx, typ, forLoop, father: PNode) = case typ.kind of nkSym: var fc: TFieldInstCtx # either 'tup[i]' or 'field' is valid + fc.c = c.c fc.field = typ.sym fc.replaceByFieldName = c.m == mFieldPairs openScope(c.c) @@ -76,7 +78,7 @@ proc semForObjectFields(c: TFieldsCtx, typ, forLoop, father: PNode) = let L = forLoop.len let call = forLoop.sons[L-2] if call.len > 2: - localError(forLoop.info, errGenerated, + localError(c.c.config, forLoop.info, "parallel 'fields' iterator does not work for 'case' objects") return # iterate over the selector: @@ -99,17 +101,17 @@ proc semForObjectFields(c: TFieldsCtx, typ, forLoop, father: PNode) = of nkRecList: for t in items(typ): semForObjectFields(c, t, forLoop, father) else: - illFormedAstLocal(typ) + illFormedAstLocal(typ, c.c.config) proc semForFields(c: PContext, n: PNode, m: TMagic): PNode = # so that 'break' etc. work as expected, we produce # a 'while true: stmt; break' loop ... result = newNodeI(nkWhileStmt, n.info, 2) - var trueSymbol = strTableGet(magicsys.systemModule.tab, getIdent"true") + var trueSymbol = strTableGet(c.graph.systemModule.tab, getIdent(c.cache, "true")) if trueSymbol == nil: - localError(n.info, errSystemNeeds, "true") - trueSymbol = newSym(skUnknown, getIdent"true", getCurrOwner(c), n.info) - trueSymbol.typ = getSysType(tyBool) + localError(c.config, n.info, "system needs: 'true'") + trueSymbol = newSym(skUnknown, getIdent(c.cache, "true"), getCurrOwner(c), n.info) + trueSymbol.typ = getSysType(c.graph, n.info, tyBool) result.sons[0] = newSymNode(trueSymbol, n.info) var stmts = newNodeI(nkStmtList, n.info) @@ -118,18 +120,18 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode = var length = sonsLen(n) var call = n.sons[length-2] if length-2 != sonsLen(call)-1 + ord(m==mFieldPairs): - localError(n.info, errWrongNumberOfVariables) + localError(c.config, n.info, errWrongNumberOfVariables) return result const skippedTypesForFields = abstractVar - {tyTypeDesc} + tyUserTypeClasses var tupleTypeA = skipTypes(call.sons[1].typ, skippedTypesForFields) if tupleTypeA.kind notin {tyTuple, tyObject}: - localError(n.info, errGenerated, "no object or tuple type") + localError(c.config, n.info, errGenerated, "no object or tuple type") return result for i in 1..call.len-1: var tupleTypeB = skipTypes(call.sons[i].typ, skippedTypesForFields) if not sameType(tupleTypeA, tupleTypeB): - typeMismatch(call.sons[i].info, tupleTypeA, tupleTypeB) + typeMismatch(c.config, call.sons[i].info, tupleTypeA, tupleTypeB) inc(c.p.nestedLoopCounter) if tupleTypeA.kind == tyTuple: @@ -139,6 +141,7 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode = var fc: TFieldInstCtx fc.tupleType = tupleTypeA fc.tupleIndex = i + fc.c = c fc.replaceByFieldName = m == mFieldPairs var body = instFieldLoopBody(fc, loopBody, n) inc c.inUnrolledContext @@ -159,7 +162,7 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode = # we avoid it now if we can: if containsNode(stmts, {nkBreakStmt}): var b = newNodeI(nkBreakStmt, n.info) - b.add(ast.emptyNode) + b.add(newNodeI(nkEmpty, n.info)) stmts.add(b) else: result = stmts diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 62bab4edb..0018f0755 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -11,29 +11,18 @@ # and evaluation phase import - strutils, options, ast, astalgo, trees, treetab, nimsets, times, + strutils, options, ast, astalgo, trees, treetab, nimsets, nversion, platform, math, msgs, os, condsyms, idents, renderer, types, - commands, magicsys, saturate + commands, magicsys, modulegraphs, strtabs, lineinfos -proc getConstExpr*(m: PSym, n: PNode): PNode - # evaluates the constant expression or returns nil if it is no constant - # expression -proc evalOp*(m: TMagic, n, a, b, c: PNode): PNode -proc leValueConv*(a, b: PNode): bool -proc newIntNodeT*(intVal: BiggestInt, n: PNode): PNode -proc newFloatNodeT(floatVal: BiggestFloat, n: PNode): PNode -proc newStrNodeT*(strVal: string, n: PNode): PNode - -# implementation - -proc newIntNodeT(intVal: BiggestInt, n: PNode): PNode = +proc newIntNodeT*(intVal: BiggestInt, n: PNode; g: ModuleGraph): PNode = case skipTypes(n.typ, abstractVarRange).kind of tyInt: result = newIntNode(nkIntLit, intVal) # See bug #6989. 'pred' et al only produce an int literal type if the # original type was 'int', not a distinct int etc. if n.typ.kind == tyInt: - result.typ = getIntLitType(result) + result.typ = getIntLitType(g, result) else: result.typ = n.typ # hrm, this is not correct: 1 + high(int) shouldn't produce tyInt64 ... @@ -46,20 +35,86 @@ proc newIntNodeT(intVal: BiggestInt, n: PNode): PNode = result.typ = n.typ result.info = n.info -proc newFloatNodeT(floatVal: BiggestFloat, n: PNode): PNode = +proc newFloatNodeT*(floatVal: BiggestFloat, n: PNode; g: ModuleGraph): PNode = result = newFloatNode(nkFloatLit, floatVal) if skipTypes(n.typ, abstractVarRange).kind == tyFloat: - result.typ = getFloatLitType(result) + result.typ = getFloatLitType(g, result) else: result.typ = n.typ result.info = n.info -proc newStrNodeT(strVal: string, n: PNode): PNode = +proc newStrNodeT*(strVal: string, n: PNode; g: ModuleGraph): PNode = result = newStrNode(nkStrLit, strVal) result.typ = n.typ result.info = n.info -proc ordinalValToString*(a: PNode): string = +proc getConstExpr*(m: PSym, n: PNode; g: ModuleGraph): PNode + # evaluates the constant expression or returns nil if it is no constant + # expression +proc evalOp*(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode + +proc checkInRange(conf: ConfigRef; n: PNode, res: BiggestInt): bool = + if res in firstOrd(conf, n.typ)..lastOrd(conf, n.typ): + result = true + +proc foldAdd(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode = + let res = a +% b + if ((res xor a) >= 0'i64 or (res xor b) >= 0'i64) and + checkInRange(g.config, n, res): + result = newIntNodeT(res, n, g) + +proc foldSub*(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode = + let res = a -% b + if ((res xor a) >= 0'i64 or (res xor not b) >= 0'i64) and + checkInRange(g.config, n, res): + result = newIntNodeT(res, n, g) + +proc foldUnarySub(a: BiggestInt, n: PNode, g: ModuleGraph): PNode = + if a != firstOrd(g.config, n.typ): + result = newIntNodeT(-a, n, g) + +proc foldAbs*(a: BiggestInt, n: PNode; g: ModuleGraph): PNode = + if a != firstOrd(g.config, n.typ): + result = newIntNodeT(abs(a), n, g) + +proc foldMod*(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode = + if b != 0'i64: + result = newIntNodeT(a mod b, n, g) + +proc foldModU*(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode = + if b != 0'i64: + result = newIntNodeT(a %% b, n, g) + +proc foldDiv*(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode = + if b != 0'i64 and (a != firstOrd(g.config, n.typ) or b != -1'i64): + result = newIntNodeT(a div b, n, g) + +proc foldDivU*(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode = + if b != 0'i64: + result = newIntNodeT(a /% b, n, g) + +proc foldMul*(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode = + let res = a *% b + let floatProd = toBiggestFloat(a) * toBiggestFloat(b) + let resAsFloat = toBiggestFloat(res) + + # Fast path for normal case: small multiplicands, and no info + # is lost in either method. + if resAsFloat == floatProd and checkInRange(g.config, n, res): + return newIntNodeT(res, n, g) + + # Somebody somewhere lost info. Close enough, or way off? Note + # that a != 0 and b != 0 (else resAsFloat == floatProd == 0). + # The difference either is or isn't significant compared to the + # true value (of which floatProd is a good approximation). + + # abs(diff)/abs(prod) <= 1/32 iff + # 32 * abs(diff) <= abs(prod) -- 5 good bits is "close enough" + if 32.0 * abs(resAsFloat - floatProd) <= abs(floatProd) and + checkInRange(g.config, n, res): + return newIntNodeT(res, n, g) + +proc ordinalValToString*(a: PNode; g: ModuleGraph): string = # because $ has the param ordinal[T], `a` is not necessarily an enum, but an # ordinal var x = getInt(a) @@ -71,14 +126,14 @@ proc ordinalValToString*(a: PNode): string = of tyEnum: var n = t.n for i in countup(0, sonsLen(n) - 1): - if n.sons[i].kind != nkSym: internalError(a.info, "ordinalValToString") + if n.sons[i].kind != nkSym: internalError(g.config, a.info, "ordinalValToString") var field = n.sons[i].sym if field.position == x: if field.ast == nil: return field.name.s else: return field.ast.strVal - internalError(a.info, "no symbol for ordinal value: " & $x) + internalError(g.config, a.info, "no symbol for ordinal value: " & $x) else: result = $x @@ -97,12 +152,12 @@ proc pickIntRange(a, b: PType): PType = proc isIntRangeOrLit(t: PType): bool = result = isIntRange(t) or isIntLit(t) -proc makeRange(typ: PType, first, last: BiggestInt): PType = +proc makeRange(typ: PType, first, last: BiggestInt; g: ModuleGraph): PType = let minA = min(first, last) let maxA = max(first, last) let lowerNode = newIntNode(nkIntLit, minA) if typ.kind == tyInt and minA == maxA: - result = getIntLitType(lowerNode) + result = getIntLitType(g, lowerNode) elif typ.kind in {tyUint, tyUInt64}: # these are not ordinal types, so you get no subrange type for these: result = typ @@ -114,7 +169,7 @@ proc makeRange(typ: PType, first, last: BiggestInt): PType = result.n = n addSonSkipIntLit(result, skipTypes(typ, {tyRange})) -proc makeRangeF(typ: PType, first, last: BiggestFloat): PType = +proc makeRangeF(typ: PType, first, last: BiggestFloat; g: ModuleGraph): PType = var n = newNode(nkRange) addSon(n, newFloatNode(nkFloatLit, min(first.float, last.float))) addSon(n, newFloatNode(nkFloatLit, max(first.float, last.float))) @@ -122,285 +177,332 @@ proc makeRangeF(typ: PType, first, last: BiggestFloat): PType = result.n = n addSonSkipIntLit(result, skipTypes(typ, {tyRange})) -proc evalIs(n, a: PNode): PNode = +proc evalIs(n: PNode, lhs: PSym, g: ModuleGraph): PNode = # XXX: This should use the standard isOpImpl - internalAssert a.kind == nkSym and a.sym.kind == skType - internalAssert n.sonsLen == 3 and + internalAssert g.config, + n.sonsLen == 3 and + lhs.typ != nil and n[2].kind in {nkStrLit..nkTripleStrLit, nkType} - let t1 = a.sym.typ + var + res = false + t1 = lhs.typ + t2 = n[2].typ + + if t1.kind == tyTypeDesc and t2.kind != tyTypeDesc: + t1 = t1.base if n[2].kind in {nkStrLit..nkTripleStrLit}: case n[2].strVal.normalize of "closure": let t = skipTypes(t1, abstractRange) - result = newIntNode(nkIntLit, ord(t.kind == tyProc and - t.callConv == ccClosure and - tfIterator notin t.flags)) + res = t.kind == tyProc and + t.callConv == ccClosure and + tfIterator notin t.flags of "iterator": let t = skipTypes(t1, abstractRange) - result = newIntNode(nkIntLit, ord(t.kind == tyProc and - t.callConv == ccClosure and - tfIterator in t.flags)) - else: discard + res = t.kind == tyProc and + t.callConv == ccClosure and + tfIterator in t.flags + else: + res = false else: # XXX semexprs.isOpImpl is slightly different and requires a context. yay. let t2 = n[2].typ - var match = sameType(t1, t2) - result = newIntNode(nkIntLit, ord(match)) + res = sameType(t1, t2) + + result = newIntNode(nkIntLit, ord(res)) result.typ = n.typ -proc evalOp(m: TMagic, n, a, b, c: PNode): PNode = +proc fitLiteral(c: ConfigRef, n: PNode): PNode = + # Trim the literal value in order to make it fit in the destination type + if n == nil: + # `n` may be nil if the overflow check kicks in + return + + doAssert n.kind in {nkIntLit, nkCharLit} + + result = n + + let typ = n.typ.skipTypes(abstractRange) + if typ.kind in tyUInt..tyUint32: + result.intVal = result.intVal and lastOrd(c, typ, fixedUnsigned=true) + +proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode = + template doAndFit(op: untyped): untyped = + # Implements wrap-around behaviour for unsigned types + fitLiteral(g.config, op) # b and c may be nil result = nil case m - of mOrd: result = newIntNodeT(getOrdValue(a), n) - of mChr: result = newIntNodeT(getInt(a), n) - of mUnaryMinusI, mUnaryMinusI64: result = newIntNodeT(- getInt(a), n) - of mUnaryMinusF64: result = newFloatNodeT(- getFloat(a), n) - of mNot: result = newIntNodeT(1 - getInt(a), n) - of mCard: result = newIntNodeT(nimsets.cardSet(a), n) - of mBitnotI: result = newIntNodeT(not getInt(a), n) - of mLengthArray: result = newIntNodeT(lengthOrd(a.typ), n) + of mOrd: result = newIntNodeT(getOrdValue(a), n, g) + of mChr: result = newIntNodeT(getInt(a), n, g) + of mUnaryMinusI, mUnaryMinusI64: result = foldUnarySub(getInt(a), n, g) + of mUnaryMinusF64: result = newFloatNodeT(- getFloat(a), n, g) + of mNot: result = newIntNodeT(1 - getInt(a), n, g) + of mCard: result = newIntNodeT(nimsets.cardSet(g.config, a), n, g) + of mBitnotI: result = doAndFit(newIntNodeT(not getInt(a), n, g)) + of mLengthArray: result = newIntNodeT(lengthOrd(g.config, a.typ), n, g) of mLengthSeq, mLengthOpenArray, mXLenSeq, mLengthStr, mXLenStr: if a.kind == nkNilLit: - result = newIntNodeT(0, n) + result = newIntNodeT(0, n, g) elif a.kind in {nkStrLit..nkTripleStrLit}: - result = newIntNodeT(len a.strVal, n) + result = newIntNodeT(len a.strVal, n, g) else: - result = newIntNodeT(sonsLen(a), n) # BUGFIX + result = newIntNodeT(sonsLen(a), n, g) of mUnaryPlusI, mUnaryPlusF64: result = a # throw `+` away of mToFloat, mToBiggestFloat: - result = newFloatNodeT(toFloat(int(getInt(a))), n) - of mToInt, mToBiggestInt: result = newIntNodeT(system.toInt(getFloat(a)), n) - of mAbsF64: result = newFloatNodeT(abs(getFloat(a)), n) - of mAbsI: - if getInt(a) >= 0: result = a - else: result = newIntNodeT(- getInt(a), n) + result = newFloatNodeT(toFloat(int(getInt(a))), n, g) + # XXX: Hides overflow/underflow + of mToInt, mToBiggestInt: result = newIntNodeT(system.toInt(getFloat(a)), n, g) + of mAbsF64: result = newFloatNodeT(abs(getFloat(a)), n, g) + of mAbsI: result = foldAbs(getInt(a), n, g) of mZe8ToI, mZe8ToI64, mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64: # byte(-128) = 1...1..1000_0000'64 --> 0...0..1000_0000'64 - result = newIntNodeT(getInt(a) and (`shl`(1, getSize(a.typ) * 8) - 1), n) - of mToU8: result = newIntNodeT(getInt(a) and 0x000000FF, n) - of mToU16: result = newIntNodeT(getInt(a) and 0x0000FFFF, n) - of mToU32: result = newIntNodeT(getInt(a) and 0x00000000FFFFFFFF'i64, n) - of mUnaryLt: result = newIntNodeT(getOrdValue(a) |-| 1, n) - of mSucc: result = newIntNodeT(getOrdValue(a) |+| getInt(b), n) - of mPred: result = newIntNodeT(getOrdValue(a) |-| getInt(b), n) - of mAddI: result = newIntNodeT(getInt(a) |+| getInt(b), n) - of mSubI: result = newIntNodeT(getInt(a) |-| getInt(b), n) - of mMulI: result = newIntNodeT(getInt(a) |*| getInt(b), n) + result = newIntNodeT(getInt(a) and (`shl`(1, getSize(g.config, a.typ) * 8) - 1), n, g) + of mToU8: result = newIntNodeT(getInt(a) and 0x000000FF, n, g) + of mToU16: result = newIntNodeT(getInt(a) and 0x0000FFFF, n, g) + of mToU32: result = newIntNodeT(getInt(a) and 0x00000000FFFFFFFF'i64, n, g) + of mUnaryLt: result = doAndFit(foldSub(getOrdValue(a), 1, n, g)) + of mSucc: result = doAndFit(foldAdd(getOrdValue(a), getInt(b), n, g)) + of mPred: result = doAndFit(foldSub(getOrdValue(a), getInt(b), n, g)) + of mAddI: result = foldAdd(getInt(a), getInt(b), n, g) + of mSubI: result = foldSub(getInt(a), getInt(b), n, g) + of mMulI: result = foldMul(getInt(a), getInt(b), n, g) of mMinI: - if getInt(a) > getInt(b): result = newIntNodeT(getInt(b), n) - else: result = newIntNodeT(getInt(a), n) + if getInt(a) > getInt(b): result = newIntNodeT(getInt(b), n, g) + else: result = newIntNodeT(getInt(a), n, g) of mMaxI: - if getInt(a) > getInt(b): result = newIntNodeT(getInt(a), n) - else: result = newIntNodeT(getInt(b), n) + if getInt(a) > getInt(b): result = newIntNodeT(getInt(a), n, g) + else: result = newIntNodeT(getInt(b), n, g) of mShlI: case skipTypes(n.typ, abstractRange).kind - of tyInt8: result = newIntNodeT(int8(getInt(a)) shl int8(getInt(b)), n) - of tyInt16: result = newIntNodeT(int16(getInt(a)) shl int16(getInt(b)), n) - of tyInt32: result = newIntNodeT(int32(getInt(a)) shl int32(getInt(b)), n) - of tyInt64, tyInt, tyUInt..tyUInt64: - result = newIntNodeT(`shl`(getInt(a), getInt(b)), n) - else: internalError(n.info, "constant folding for shl") + of tyInt8: result = newIntNodeT(int8(getInt(a)) shl int8(getInt(b)), n, g) + of tyInt16: result = newIntNodeT(int16(getInt(a)) shl int16(getInt(b)), n, g) + of tyInt32: result = newIntNodeT(int32(getInt(a)) shl int32(getInt(b)), n, g) + of tyInt64, tyInt: + result = newIntNodeT(`shl`(getInt(a), getInt(b)), n, g) + of tyUInt..tyUInt64: + result = doAndFit(newIntNodeT(`shl`(getInt(a), getInt(b)), n, g)) + else: internalError(g.config, n.info, "constant folding for shl") of mShrI: case skipTypes(n.typ, abstractRange).kind - of tyInt8: result = newIntNodeT(int8(getInt(a)) shr int8(getInt(b)), n) - of tyInt16: result = newIntNodeT(int16(getInt(a)) shr int16(getInt(b)), n) - of tyInt32: result = newIntNodeT(int32(getInt(a)) shr int32(getInt(b)), n) + of tyInt8: result = newIntNodeT(int8(getInt(a)) shr int8(getInt(b)), n, g) + of tyInt16: result = newIntNodeT(int16(getInt(a)) shr int16(getInt(b)), n, g) + of tyInt32: result = newIntNodeT(int32(getInt(a)) shr int32(getInt(b)), n, g) of tyInt64, tyInt, tyUInt..tyUInt64: - result = newIntNodeT(`shr`(getInt(a), getInt(b)), n) - else: internalError(n.info, "constant folding for shr") - of mDivI: - let y = getInt(b) - if y != 0: - result = newIntNodeT(`|div|`(getInt(a), y), n) - of mModI: - let y = getInt(b) - if y != 0: - result = newIntNodeT(`|mod|`(getInt(a), y), n) - of mAddF64: result = newFloatNodeT(getFloat(a) + getFloat(b), n) - of mSubF64: result = newFloatNodeT(getFloat(a) - getFloat(b), n) - of mMulF64: result = newFloatNodeT(getFloat(a) * getFloat(b), n) + result = newIntNodeT(`shr`(getInt(a), getInt(b)), n, g) + else: internalError(g.config, n.info, "constant folding for shr") + of mAshrI: + case skipTypes(n.typ, abstractRange).kind + of tyInt8: result = newIntNodeT(ashr(int8(getInt(a)), int8(getInt(b))), n, g) + of tyInt16: result = newIntNodeT(ashr(int16(getInt(a)), int16(getInt(b))), n, g) + of tyInt32: result = newIntNodeT(ashr(int32(getInt(a)), int32(getInt(b))), n, g) + of tyInt64, tyInt: + result = newIntNodeT(ashr(getInt(a), getInt(b)), n, g) + else: internalError(g.config, n.info, "constant folding for ashr") + of mDivI: result = foldDiv(getInt(a), getInt(b), n, g) + of mModI: result = foldMod(getInt(a), getInt(b), n, g) + of mAddF64: result = newFloatNodeT(getFloat(a) + getFloat(b), n, g) + of mSubF64: result = newFloatNodeT(getFloat(a) - getFloat(b), n, g) + of mMulF64: result = newFloatNodeT(getFloat(a) * getFloat(b), n, g) of mDivF64: if getFloat(b) == 0.0: - if getFloat(a) == 0.0: result = newFloatNodeT(NaN, n) - elif getFloat(b).classify == fcNegZero: result = newFloatNodeT(-Inf, n) - else: result = newFloatNodeT(Inf, n) + if getFloat(a) == 0.0: result = newFloatNodeT(NaN, n, g) + elif getFloat(b).classify == fcNegZero: result = newFloatNodeT(-Inf, n, g) + else: result = newFloatNodeT(Inf, n, g) else: - result = newFloatNodeT(getFloat(a) / getFloat(b), n) + result = newFloatNodeT(getFloat(a) / getFloat(b), n, g) of mMaxF64: - if getFloat(a) > getFloat(b): result = newFloatNodeT(getFloat(a), n) - else: result = newFloatNodeT(getFloat(b), n) + if getFloat(a) > getFloat(b): result = newFloatNodeT(getFloat(a), n, g) + else: result = newFloatNodeT(getFloat(b), n, g) of mMinF64: - if getFloat(a) > getFloat(b): result = newFloatNodeT(getFloat(b), n) - else: result = newFloatNodeT(getFloat(a), n) - of mIsNil: result = newIntNodeT(ord(a.kind == nkNilLit), n) + if getFloat(a) > getFloat(b): result = newFloatNodeT(getFloat(b), n, g) + else: result = newFloatNodeT(getFloat(a), n, g) + of mIsNil: result = newIntNodeT(ord(a.kind == nkNilLit), n, g) of mLtI, mLtB, mLtEnum, mLtCh: - result = newIntNodeT(ord(getOrdValue(a) < getOrdValue(b)), n) + result = newIntNodeT(ord(getOrdValue(a) < getOrdValue(b)), n, g) of mLeI, mLeB, mLeEnum, mLeCh: - result = newIntNodeT(ord(getOrdValue(a) <= getOrdValue(b)), n) + result = newIntNodeT(ord(getOrdValue(a) <= getOrdValue(b)), n, g) of mEqI, mEqB, mEqEnum, mEqCh: - result = newIntNodeT(ord(getOrdValue(a) == getOrdValue(b)), n) - of mLtF64: result = newIntNodeT(ord(getFloat(a) < getFloat(b)), n) - of mLeF64: result = newIntNodeT(ord(getFloat(a) <= getFloat(b)), n) - of mEqF64: result = newIntNodeT(ord(getFloat(a) == getFloat(b)), n) - of mLtStr: result = newIntNodeT(ord(getStr(a) < getStr(b)), n) - of mLeStr: result = newIntNodeT(ord(getStr(a) <= getStr(b)), n) - of mEqStr: result = newIntNodeT(ord(getStr(a) == getStr(b)), n) + result = newIntNodeT(ord(getOrdValue(a) == getOrdValue(b)), n, g) + of mLtF64: result = newIntNodeT(ord(getFloat(a) < getFloat(b)), n, g) + of mLeF64: result = newIntNodeT(ord(getFloat(a) <= getFloat(b)), n, g) + of mEqF64: result = newIntNodeT(ord(getFloat(a) == getFloat(b)), n, g) + of mLtStr: result = newIntNodeT(ord(getStr(a) < getStr(b)), n, g) + of mLeStr: result = newIntNodeT(ord(getStr(a) <= getStr(b)), n, g) + of mEqStr: result = newIntNodeT(ord(getStr(a) == getStr(b)), n, g) of mLtU, mLtU64: - result = newIntNodeT(ord(`<%`(getOrdValue(a), getOrdValue(b))), n) + result = newIntNodeT(ord(`<%`(getOrdValue(a), getOrdValue(b))), n, g) of mLeU, mLeU64: - result = newIntNodeT(ord(`<=%`(getOrdValue(a), getOrdValue(b))), n) - of mBitandI, mAnd: result = newIntNodeT(a.getInt and b.getInt, n) - of mBitorI, mOr: result = newIntNodeT(getInt(a) or getInt(b), n) - of mBitxorI, mXor: result = newIntNodeT(a.getInt xor b.getInt, n) - of mAddU: result = newIntNodeT(`+%`(getInt(a), getInt(b)), n) - of mSubU: result = newIntNodeT(`-%`(getInt(a), getInt(b)), n) - of mMulU: result = newIntNodeT(`*%`(getInt(a), getInt(b)), n) - of mModU: - let y = getInt(b) - if y != 0: - result = newIntNodeT(`%%`(getInt(a), y), n) - of mDivU: - let y = getInt(b) - if y != 0: - result = newIntNodeT(`/%`(getInt(a), y), n) - of mLeSet: result = newIntNodeT(ord(containsSets(a, b)), n) - of mEqSet: result = newIntNodeT(ord(equalSets(a, b)), n) + result = newIntNodeT(ord(`<=%`(getOrdValue(a), getOrdValue(b))), n, g) + of mBitandI, mAnd: result = doAndFit(newIntNodeT(a.getInt and b.getInt, n, g)) + of mBitorI, mOr: result = doAndFit(newIntNodeT(getInt(a) or getInt(b), n, g)) + of mBitxorI, mXor: result = doAndFit(newIntNodeT(a.getInt xor b.getInt, n, g)) + of mAddU: result = doAndFit(newIntNodeT(`+%`(getInt(a), getInt(b)), n, g)) + of mSubU: result = doAndFit(newIntNodeT(`-%`(getInt(a), getInt(b)), n, g)) + of mMulU: result = doAndFit(newIntNodeT(`*%`(getInt(a), getInt(b)), n, g)) + of mModU: result = doAndFit(foldModU(getInt(a), getInt(b), n, g)) + of mDivU: result = doAndFit(foldDivU(getInt(a), getInt(b), n, g)) + of mLeSet: result = newIntNodeT(ord(containsSets(g.config, a, b)), n, g) + of mEqSet: result = newIntNodeT(ord(equalSets(g.config, a, b)), n, g) of mLtSet: - result = newIntNodeT(ord(containsSets(a, b) and not equalSets(a, b)), n) + result = newIntNodeT(ord(containsSets(g.config, a, b) and not equalSets(g.config, a, b)), n, g) of mMulSet: - result = nimsets.intersectSets(a, b) + result = nimsets.intersectSets(g.config, a, b) result.info = n.info of mPlusSet: - result = nimsets.unionSets(a, b) + result = nimsets.unionSets(g.config, a, b) result.info = n.info of mMinusSet: - result = nimsets.diffSets(a, b) + result = nimsets.diffSets(g.config, a, b) result.info = n.info of mSymDiffSet: - result = nimsets.symdiffSets(a, b) + result = nimsets.symdiffSets(g.config, a, b) result.info = n.info - of mConStrStr: result = newStrNodeT(getStrOrChar(a) & getStrOrChar(b), n) - of mInSet: result = newIntNodeT(ord(inSet(a, b)), n) + of mConStrStr: result = newStrNodeT(getStrOrChar(a) & getStrOrChar(b), n, g) + of mInSet: result = newIntNodeT(ord(inSet(a, b)), n, g) of mRepr: # BUGFIX: we cannot eval mRepr here for reasons that I forgot. discard - of mIntToStr, mInt64ToStr: result = newStrNodeT($(getOrdValue(a)), n) + of mIntToStr, mInt64ToStr: result = newStrNodeT($(getOrdValue(a)), n, g) of mBoolToStr: - if getOrdValue(a) == 0: result = newStrNodeT("false", n) - else: result = newStrNodeT("true", n) - of mCopyStr: result = newStrNodeT(substr(getStr(a), int(getOrdValue(b))), n) + if getOrdValue(a) == 0: result = newStrNodeT("false", n, g) + else: result = newStrNodeT("true", n, g) + of mCopyStr: result = newStrNodeT(substr(getStr(a), int(getOrdValue(b))), n, g) of mCopyStrLast: result = newStrNodeT(substr(getStr(a), int(getOrdValue(b)), - int(getOrdValue(c))), n) - of mFloatToStr: result = newStrNodeT($getFloat(a), n) + int(getOrdValue(c))), n, g) + of mFloatToStr: result = newStrNodeT($getFloat(a), n, g) of mCStrToStr, mCharToStr: if a.kind == nkBracket: var s = "" for b in a.sons: s.add b.getStrOrChar - result = newStrNodeT(s, n) + result = newStrNodeT(s, n, g) else: - result = newStrNodeT(getStrOrChar(a), n) + result = newStrNodeT(getStrOrChar(a), n, g) of mStrToStr: result = a - of mEnumToStr: result = newStrNodeT(ordinalValToString(a), n) + of mEnumToStr: result = newStrNodeT(ordinalValToString(a, g), n, g) of mArrToSeq: result = copyTree(a) result.typ = n.typ of mCompileOption: - result = newIntNodeT(ord(commands.testCompileOption(a.getStr, n.info)), n) + result = newIntNodeT(ord(commands.testCompileOption(g.config, a.getStr, n.info)), n, g) of mCompileOptionArg: result = newIntNodeT(ord( - testCompileOptionArg(getStr(a), getStr(b), n.info)), n) + testCompileOptionArg(g.config, getStr(a), getStr(b), n.info)), n, g) of mEqProc: result = newIntNodeT(ord( - exprStructuralEquivalent(a, b, strictSymEquality=true)), n) + exprStructuralEquivalent(a, b, strictSymEquality=true)), n, g) else: discard -proc getConstIfExpr(c: PSym, n: PNode): PNode = +proc getConstIfExpr(c: PSym, n: PNode; g: ModuleGraph): PNode = result = nil for i in countup(0, sonsLen(n) - 1): var it = n.sons[i] if it.len == 2: - var e = getConstExpr(c, it.sons[0]) + var e = getConstExpr(c, it.sons[0], g) if e == nil: return nil if getOrdValue(e) != 0: if result == nil: - result = getConstExpr(c, it.sons[1]) + result = getConstExpr(c, it.sons[1], g) if result == nil: return elif it.len == 1: - if result == nil: result = getConstExpr(c, it.sons[0]) - else: internalError(it.info, "getConstIfExpr()") + if result == nil: result = getConstExpr(c, it.sons[0], g) + else: internalError(g.config, it.info, "getConstIfExpr()") -proc leValueConv(a, b: PNode): bool = +proc leValueConv*(a, b: PNode): bool = result = false case a.kind of nkCharLit..nkUInt64Lit: case b.kind of nkCharLit..nkUInt64Lit: result = a.intVal <= b.intVal of nkFloatLit..nkFloat128Lit: result = a.intVal <= round(b.floatVal).int - else: internalError(a.info, "leValueConv") + else: result = false #internalError(a.info, "leValueConv") of nkFloatLit..nkFloat128Lit: case b.kind of nkFloatLit..nkFloat128Lit: result = a.floatVal <= b.floatVal of nkCharLit..nkUInt64Lit: result = a.floatVal <= toFloat(int(b.intVal)) - else: internalError(a.info, "leValueConv") - else: internalError(a.info, "leValueConv") + else: result = false # internalError(a.info, "leValueConv") + else: result = false # internalError(a.info, "leValueConv") -proc magicCall(m: PSym, n: PNode): PNode = +proc magicCall(m: PSym, n: PNode; g: ModuleGraph): PNode = if sonsLen(n) <= 1: return var s = n.sons[0].sym - var a = getConstExpr(m, n.sons[1]) + var a = getConstExpr(m, n.sons[1], g) var b, c: PNode if a == nil: return if sonsLen(n) > 2: - b = getConstExpr(m, n.sons[2]) + b = getConstExpr(m, n.sons[2], g) if b == nil: return if sonsLen(n) > 3: - c = getConstExpr(m, n.sons[3]) + c = getConstExpr(m, n.sons[3], g) if c == nil: return - result = evalOp(s.magic, n, a, b, c) - -proc getAppType(n: PNode): PNode = - if gGlobalOptions.contains(optGenDynLib): - result = newStrNodeT("lib", n) - elif gGlobalOptions.contains(optGenStaticLib): - result = newStrNodeT("staticlib", n) - elif gGlobalOptions.contains(optGenGuiApp): - result = newStrNodeT("gui", n) + result = evalOp(s.magic, n, a, b, c, g) + +proc getAppType(n: PNode; g: ModuleGraph): PNode = + if g.config.globalOptions.contains(optGenDynLib): + result = newStrNodeT("lib", n, g) + elif g.config.globalOptions.contains(optGenStaticLib): + result = newStrNodeT("staticlib", n, g) + elif g.config.globalOptions.contains(optGenGuiApp): + result = newStrNodeT("gui", n, g) else: - result = newStrNodeT("console", n) + result = newStrNodeT("console", n, g) -proc rangeCheck(n: PNode, value: BiggestInt) = +proc rangeCheck(n: PNode, value: BiggestInt; g: ModuleGraph) = var err = false if n.typ.skipTypes({tyRange}).kind in {tyUInt..tyUInt64}: - err = value <% firstOrd(n.typ) or value >% lastOrd(n.typ, fixedUnsigned=true) + err = value <% firstOrd(g.config, n.typ) or value >% lastOrd(g.config, n.typ, fixedUnsigned=true) else: - err = value < firstOrd(n.typ) or value > lastOrd(n.typ) + err = value < firstOrd(g.config, n.typ) or value > lastOrd(g.config, n.typ) if err: - localError(n.info, errGenerated, "cannot convert " & $value & + localError(g.config, n.info, "cannot convert " & $value & " to " & typeToString(n.typ)) -proc foldConv*(n, a: PNode; check = false): PNode = +proc foldConv(n, a: PNode; g: ModuleGraph; check = false): PNode = + let dstTyp = skipTypes(n.typ, abstractRange) + let srcTyp = skipTypes(a.typ, abstractRange) + # XXX range checks? - case skipTypes(n.typ, abstractRange).kind - of tyInt..tyInt64, tyUInt..tyUInt64: - case skipTypes(a.typ, abstractRange).kind + case dstTyp.kind + of tyInt..tyInt64, tyUint..tyUInt64: + case srcTyp.kind of tyFloat..tyFloat64: - result = newIntNodeT(int(getFloat(a)), n) - of tyChar: result = newIntNodeT(getOrdValue(a), n) + result = newIntNodeT(int(getFloat(a)), n, g) + of tyChar: + result = newIntNodeT(getOrdValue(a), n, g) + of tyUInt..tyUInt64, tyInt..tyInt64: + let toSigned = dstTyp.kind in tyInt..tyInt64 + var val = a.getOrdValue + + if dstTyp.kind in {tyInt, tyInt64, tyUint, tyUInt64}: + # No narrowing needed + discard + elif dstTyp.kind in {tyInt..tyInt64}: + # Signed type: Overflow check (if requested) and conversion + if check: rangeCheck(n, val, g) + let mask = (`shl`(1, getSize(g.config, dstTyp) * 8) - 1) + let valSign = val < 0 + val = abs(val) and mask + if valSign: val = -val + else: + # Unsigned type: Conversion + let mask = (`shl`(1, getSize(g.config, dstTyp) * 8) - 1) + val = val and mask + + result = newIntNodeT(val, n, g) else: result = a result.typ = n.typ if check and result.kind in {nkCharLit..nkUInt64Lit}: - rangeCheck(n, result.intVal) + rangeCheck(n, result.intVal, g) of tyFloat..tyFloat64: - case skipTypes(a.typ, abstractRange).kind + case srcTyp.kind of tyInt..tyInt64, tyEnum, tyBool, tyChar: - result = newFloatNodeT(toBiggestFloat(getOrdValue(a)), n) + result = newFloatNodeT(toBiggestFloat(getOrdValue(a)), n, g) else: result = a result.typ = n.typ @@ -410,47 +512,47 @@ proc foldConv*(n, a: PNode; check = false): PNode = result = a result.typ = n.typ -proc getArrayConstr(m: PSym, n: PNode): PNode = +proc getArrayConstr(m: PSym, n: PNode; g: ModuleGraph): PNode = if n.kind == nkBracket: result = n else: - result = getConstExpr(m, n) + result = getConstExpr(m, n, g) if result == nil: result = n -proc foldArrayAccess(m: PSym, n: PNode): PNode = - var x = getConstExpr(m, n.sons[0]) +proc foldArrayAccess(m: PSym, n: PNode; g: ModuleGraph): PNode = + var x = getConstExpr(m, n.sons[0], g) if x == nil or x.typ.skipTypes({tyGenericInst, tyAlias, tySink}).kind == tyTypeDesc: return - var y = getConstExpr(m, n.sons[1]) + var y = getConstExpr(m, n.sons[1], g) if y == nil: return var idx = getOrdValue(y) case x.kind - of nkPar: + of nkPar, nkTupleConstr: if idx >= 0 and idx < sonsLen(x): result = x.sons[int(idx)] if result.kind == nkExprColonExpr: result = result.sons[1] else: - localError(n.info, errIndexOutOfBounds) + localError(g.config, n.info, "index out of bounds: " & $n) of nkBracket: - idx = idx - x.typ.firstOrd + idx = idx - firstOrd(g.config, x.typ) if idx >= 0 and idx < x.len: result = x.sons[int(idx)] - else: localError(n.info, errIndexOutOfBounds) + else: localError(g.config, n.info, "index out of bounds: " & $n) of nkStrLit..nkTripleStrLit: result = newNodeIT(nkCharLit, x.info, n.typ) if idx >= 0 and idx < len(x.strVal): result.intVal = ord(x.strVal[int(idx)]) - elif idx == len(x.strVal): + elif idx == len(x.strVal) and optLaxStrings in g.config.options: discard else: - localError(n.info, errIndexOutOfBounds) + localError(g.config, n.info, "index out of bounds: " & $n) else: discard -proc foldFieldAccess(m: PSym, n: PNode): PNode = +proc foldFieldAccess(m: PSym, n: PNode; g: ModuleGraph): PNode = # a real field access; proc calls have already been transformed - var x = getConstExpr(m, n.sons[0]) - if x == nil or x.kind notin {nkObjConstr, nkPar}: return + var x = getConstExpr(m, n.sons[0], g) + if x == nil or x.kind notin {nkObjConstr, nkPar, nkTupleConstr}: return var field = n.sons[1].sym for i in countup(ord(x.kind == nkObjConstr), sonsLen(x) - 1): @@ -463,13 +565,13 @@ proc foldFieldAccess(m: PSym, n: PNode): PNode = if it.sons[0].sym.name.id == field.name.id: result = x.sons[i].sons[1] return - localError(n.info, errFieldXNotFound, field.name.s) + localError(g.config, n.info, "field not found: " & field.name.s) -proc foldConStrStr(m: PSym, n: PNode): PNode = +proc foldConStrStr(m: PSym, n: PNode; g: ModuleGraph): PNode = result = newNodeIT(nkStrLit, n.info, n.typ) result.strVal = "" for i in countup(1, sonsLen(n) - 1): - let a = getConstExpr(m, n.sons[i]) + let a = getConstExpr(m, n.sons[i], g) if a == nil: return nil result.strVal.add(getStrOrChar(a)) @@ -481,38 +583,44 @@ proc newSymNodeTypeDesc*(s: PSym; info: TLineInfo): PNode = else: result.typ = s.typ -proc getConstExpr(m: PSym, n: PNode): PNode = +proc getConstExpr(m: PSym, n: PNode; g: ModuleGraph): PNode = result = nil case n.kind of nkSym: var s = n.sym case s.kind of skEnumField: - result = newIntNodeT(s.position, n) + result = newIntNodeT(s.position, n, g) of skConst: case s.magic - of mIsMainModule: result = newIntNodeT(ord(sfMainModule in m.flags), n) - of mCompileDate: result = newStrNodeT(times.getDateStr(), n) - of mCompileTime: result = newStrNodeT(times.getClockStr(), n) - of mCpuEndian: result = newIntNodeT(ord(CPU[targetCPU].endian), n) - of mHostOS: result = newStrNodeT(toLowerAscii(platform.OS[targetOS].name), n) - of mHostCPU: result = newStrNodeT(platform.CPU[targetCPU].name.toLowerAscii, n) - of mBuildOS: result = newStrNodeT(toLowerAscii(platform.OS[platform.hostOS].name), n) - of mBuildCPU: result = newStrNodeT(platform.CPU[platform.hostCPU].name.toLowerAscii, n) - of mAppType: result = getAppType(n) - of mNaN: result = newFloatNodeT(NaN, n) - of mInf: result = newFloatNodeT(Inf, n) - of mNegInf: result = newFloatNodeT(NegInf, n) + of mIsMainModule: result = newIntNodeT(ord(sfMainModule in m.flags), n, g) + of mCompileDate: result = newStrNodeT(getDateStr(), n, g) + of mCompileTime: result = newStrNodeT(getClockStr(), n, g) + of mCpuEndian: result = newIntNodeT(ord(CPU[g.config.target.targetCPU].endian), n, g) + of mHostOS: result = newStrNodeT(toLowerAscii(platform.OS[g.config.target.targetOS].name), n, g) + of mHostCPU: result = newStrNodeT(platform.CPU[g.config.target.targetCPU].name.toLowerAscii, n, g) + of mBuildOS: result = newStrNodeT(toLowerAscii(platform.OS[g.config.target.hostOS].name), n, g) + of mBuildCPU: result = newStrNodeT(platform.CPU[g.config.target.hostCPU].name.toLowerAscii, n, g) + of mAppType: result = getAppType(n, g) + of mNaN: result = newFloatNodeT(NaN, n, g) + of mInf: result = newFloatNodeT(Inf, n, g) + of mNegInf: result = newFloatNodeT(NegInf, n, g) of mIntDefine: - if isDefined(s.name): - result = newIntNodeT(lookupSymbol(s.name).parseInt, n) + if isDefined(g.config, s.name.s): + try: + result = newIntNodeT(g.config.symbols[s.name.s].parseInt, n, g) + except ValueError: + localError(g.config, n.info, "expression is not an integer literal") of mStrDefine: - if isDefined(s.name): - result = newStrNodeT(lookupSymbol(s.name), n) + if isDefined(g.config, s.name.s): + result = newStrNodeT(g.config.symbols[s.name.s], n, g) else: result = copyTree(s.ast) - of {skProc, skFunc, skMethod}: + of skProc, skFunc, skMethod: result = n + of skParam: + if s.typ != nil and s.typ.kind == tyTypeDesc: + result = newSymNodeTypeDesc(s, n.info) of skType: # XXX gensym'ed symbols can come here and cannot be resolved. This is # dirty, but correct. @@ -531,7 +639,7 @@ proc getConstExpr(m: PSym, n: PNode): PNode = of nkCharLit..nkNilLit: result = copyNode(n) of nkIfExpr: - result = getConstIfExpr(m, n) + result = getConstIfExpr(m, n, g) of nkCallKinds: if n.sons[0].kind != nkSym: return var s = n.sons[0].sym @@ -543,69 +651,68 @@ proc getConstExpr(m: PSym, n: PNode): PNode = return of mSizeOf: var a = n.sons[1] - if computeSize(a.typ) < 0: - localError(a.info, errCannotEvalXBecauseIncompletelyDefined, - "sizeof") + if computeSize(g.config, a.typ) < 0: + localError(g.config, a.info, "cannot evaluate 'sizeof' because its type is not defined completely") result = nil - elif skipTypes(a.typ, typedescInst+{tyRange}).kind in + elif skipTypes(a.typ, typedescInst+{tyRange, tyArray}).kind in IntegralTypes+NilableTypes+{tySet}: #{tyArray,tyObject,tyTuple}: - result = newIntNodeT(getSize(a.typ), n) + result = newIntNodeT(getSize(g.config, a.typ), n, g) else: result = nil # XXX: size computation for complex types is still wrong of mLow: - result = newIntNodeT(firstOrd(n.sons[1].typ), n) + result = newIntNodeT(firstOrd(g.config, n.sons[1].typ), n, g) of mHigh: - if skipTypes(n.sons[1].typ, abstractVar).kind notin + if skipTypes(n.sons[1].typ, abstractVar+{tyUserTypeClassInst}).kind notin {tySequence, tyString, tyCString, tyOpenArray, tyVarargs}: - result = newIntNodeT(lastOrd(skipTypes(n[1].typ, abstractVar)), n) + result = newIntNodeT(lastOrd(g.config, skipTypes(n[1].typ, abstractVar)), n, g) else: - var a = getArrayConstr(m, n.sons[1]) + var a = getArrayConstr(m, n.sons[1], g) if a.kind == nkBracket: # we can optimize it away: - result = newIntNodeT(sonsLen(a)-1, n) + result = newIntNodeT(sonsLen(a)-1, n, g) of mLengthOpenArray: - var a = getArrayConstr(m, n.sons[1]) + var a = getArrayConstr(m, n.sons[1], g) if a.kind == nkBracket: # we can optimize it away! This fixes the bug ``len(134)``. - result = newIntNodeT(sonsLen(a), n) + result = newIntNodeT(sonsLen(a), n, g) else: - result = magicCall(m, n) + result = magicCall(m, n, g) of mLengthArray: # It doesn't matter if the argument is const or not for mLengthArray. # This fixes bug #544. - result = newIntNodeT(lengthOrd(n.sons[1].typ), n) + result = newIntNodeT(lengthOrd(g.config, n.sons[1].typ), n, g) of mAstToStr: - result = newStrNodeT(renderTree(n[1], {renderNoComments}), n) + result = newStrNodeT(renderTree(n[1], {renderNoComments}), n, g) of mConStrStr: - result = foldConStrStr(m, n) + result = foldConStrStr(m, n, g) of mIs: - let a = getConstExpr(m, n[1]) - if a != nil and a.kind == nkSym and a.sym.kind == skType: - result = evalIs(n, a) + let lhs = getConstExpr(m, n[1], g) + if lhs != nil and lhs.kind == nkSym: + result = evalIs(n, lhs.sym, g) else: - result = magicCall(m, n) + result = magicCall(m, n, g) except OverflowError: - localError(n.info, errOverOrUnderflow) + localError(g.config, n.info, "over- or underflow") except DivByZeroError: - localError(n.info, errConstantDivisionByZero) + localError(g.config, n.info, "division by zero") of nkAddr: - var a = getConstExpr(m, n.sons[0]) + var a = getConstExpr(m, n.sons[0], g) if a != nil: result = n n.sons[0] = a of nkBracket: result = copyTree(n) for i in countup(0, sonsLen(n) - 1): - var a = getConstExpr(m, n.sons[i]) + var a = getConstExpr(m, n.sons[i], g) if a == nil: return nil result.sons[i] = a incl(result.flags, nfAllConst) of nkRange: - var a = getConstExpr(m, n.sons[0]) + var a = getConstExpr(m, n.sons[0], g) if a == nil: return - var b = getConstExpr(m, n.sons[1]) + var b = getConstExpr(m, n.sons[1], g) if b == nil: return result = copyNode(n) addSon(result, a) @@ -613,7 +720,7 @@ proc getConstExpr(m: PSym, n: PNode): PNode = of nkCurly: result = copyTree(n) for i in countup(0, sonsLen(n) - 1): - var a = getConstExpr(m, n.sons[i]) + var a = getConstExpr(m, n.sons[i], g) if a == nil: return nil result.sons[i] = a incl(result.flags, nfAllConst) @@ -624,50 +731,51 @@ proc getConstExpr(m: PSym, n: PNode): PNode = # if a == nil: return nil # result.sons[i].sons[1] = a # incl(result.flags, nfAllConst) - of nkPar: + of nkPar, nkTupleConstr: # tuple constructor result = copyTree(n) if (sonsLen(n) > 0) and (n.sons[0].kind == nkExprColonExpr): for i in countup(0, sonsLen(n) - 1): - var a = getConstExpr(m, n.sons[i].sons[1]) + var a = getConstExpr(m, n.sons[i].sons[1], g) if a == nil: return nil result.sons[i].sons[1] = a else: for i in countup(0, sonsLen(n) - 1): - var a = getConstExpr(m, n.sons[i]) + var a = getConstExpr(m, n.sons[i], g) if a == nil: return nil result.sons[i] = a incl(result.flags, nfAllConst) of nkChckRangeF, nkChckRange64, nkChckRange: - var a = getConstExpr(m, n.sons[0]) + var a = getConstExpr(m, n.sons[0], g) if a == nil: return if leValueConv(n.sons[1], a) and leValueConv(a, n.sons[2]): result = a # a <= x and x <= b result.typ = n.typ else: - localError(n.info, errGenerated, `%`( - msgKindToString(errIllegalConvFromXtoY), - [typeToString(n.sons[0].typ), typeToString(n.typ)])) + localError(g.config, n.info, + "conversion from $1 to $2 is invalid" % + [typeToString(n.sons[0].typ), typeToString(n.typ)]) of nkStringToCString, nkCStringToString: - var a = getConstExpr(m, n.sons[0]) + var a = getConstExpr(m, n.sons[0], g) if a == nil: return result = a result.typ = n.typ of nkHiddenStdConv, nkHiddenSubConv, nkConv: - var a = getConstExpr(m, n.sons[1]) + var a = getConstExpr(m, n.sons[1], g) if a == nil: return - result = foldConv(n, a, check=n.kind == nkHiddenStdConv) + # XXX: we should enable `check` for other conversion types too + result = foldConv(n, a, g, check=n.kind == nkHiddenSubConv) of nkCast: - var a = getConstExpr(m, n.sons[1]) + var a = getConstExpr(m, n.sons[1], g) if a == nil: return if n.typ != nil and n.typ.kind in NilableTypes: # we allow compile-time 'cast' for pointer types: result = a result.typ = n.typ - of nkBracketExpr: result = foldArrayAccess(m, n) - of nkDotExpr: result = foldFieldAccess(m, n) + of nkBracketExpr: result = foldArrayAccess(m, n, g) + of nkDotExpr: result = foldFieldAccess(m, n, g) of nkStmtListExpr: if n.len == 2 and n[0].kind == nkComesFrom: - result = getConstExpr(m, n[1]) + result = getConstExpr(m, n[1], g) else: discard diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index 16da06952..7be0610a2 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -17,13 +17,13 @@ # included from sem.nim -proc getIdentNode(n: PNode): PNode = +proc getIdentNode(c: PContext; n: PNode): PNode = case n.kind - of nkPostfix: result = getIdentNode(n.sons[1]) - of nkPragmaExpr: result = getIdentNode(n.sons[0]) + of nkPostfix: result = getIdentNode(c, n.sons[1]) + of nkPragmaExpr: result = getIdentNode(c, n.sons[0]) of nkIdent, nkAccQuoted, nkSym: result = n else: - illFormedAst(n) + illFormedAst(n, c.config) result = n type @@ -32,9 +32,12 @@ type cursorInBody: bool # only for nimsuggest bracketExpr: PNode -type TSemGenericFlag = enum - withinBind, withinTypeDesc, withinMixin, withinConcept + withinBind, + withinTypeDesc, + withinMixin, + withinConcept + TSemGenericFlags = set[TSemGenericFlag] proc semGenericStmt(c: PContext, n: PNode, @@ -51,11 +54,19 @@ template macroToExpand(s): untyped = s.kind in {skMacro, skTemplate} and (s.typ.len == 1 or sfAllUntyped in s.flags) template macroToExpandSym(s): untyped = - s.kind in {skMacro, skTemplate} and (s.typ.len == 1) and not fromDotExpr + sfCustomPragma notin s.flags and s.kind in {skMacro, skTemplate} and + (s.typ.len == 1) and not fromDotExpr + +template isMixedIn(sym): bool = + let s = sym + s.name.id in ctx.toMixin or (withinConcept in flags and + s.magic == mNone and + s.kind in OverloadableSyms) proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, - ctx: var GenericCtx; fromDotExpr=false): PNode = - semIdeForTemplateOrGenericCheck(n, ctx.cursorInBody) + ctx: var GenericCtx; flags: TSemGenericFlags, + fromDotExpr=false): PNode = + semIdeForTemplateOrGenericCheck(c.config, n, ctx.cursorInBody) incl(s.flags, sfUsed) case s.kind of skUnknown: @@ -103,8 +114,8 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, proc lookup(c: PContext, n: PNode, flags: TSemGenericFlags, ctx: var GenericCtx): PNode = result = n - let ident = considerQuotedIdent(n) - var s = searchInScopes(c, ident).skipAlias(n) + let ident = considerQuotedIdent(c, n) + var s = searchInScopes(c, ident).skipAlias(n, c.config) if s == nil: s = strTableGet(c.pureEnumFields, ident) if s != nil and contains(c.ambiguousSymbols, s.id): @@ -115,10 +126,10 @@ proc lookup(c: PContext, n: PNode, flags: TSemGenericFlags, else: if withinBind in flags: result = symChoice(c, n, s, scClosed) - elif s.name.id in ctx.toMixin: + elif s.isMixedIn: result = symChoice(c, n, s, scForceOpen) else: - result = semGenericStmtSymbol(c, n, s, ctx) + result = semGenericStmtSymbol(c, n, s, ctx, flags) # else: leave as nkIdent proc newDot(n, b: PNode): PNode = @@ -129,27 +140,27 @@ proc newDot(n, b: PNode): PNode = proc fuzzyLookup(c: PContext, n: PNode, flags: TSemGenericFlags, ctx: var GenericCtx; isMacro: var bool): PNode = assert n.kind == nkDotExpr - semIdeForTemplateOrGenericCheck(n, ctx.cursorInBody) + semIdeForTemplateOrGenericCheck(c.config, n, ctx.cursorInBody) let luf = if withinMixin notin flags: {checkUndeclared, checkModule} else: {checkModule} var s = qualifiedLookUp(c, n, luf) if s != nil: - result = semGenericStmtSymbol(c, n, s, ctx) + result = semGenericStmtSymbol(c, n, s, ctx, flags) else: n.sons[0] = semGenericStmt(c, n.sons[0], flags, ctx) result = n let n = n[1] - let ident = considerQuotedIdent(n) - var s = searchInScopes(c, ident).skipAlias(n) + let ident = considerQuotedIdent(c, n) + var s = searchInScopes(c, ident).skipAlias(n, c.config) if s != nil and s.kind in routineKinds: isMacro = s.kind in {skTemplate, skMacro} if withinBind in flags: result = newDot(result, symChoice(c, n, s, scClosed)) - elif s.name.id in ctx.toMixin: + elif s.isMixedIn: result = newDot(result, symChoice(c, n, s, scForceOpen)) else: - let syms = semGenericStmtSymbol(c, n, s, ctx, fromDotExpr=true) + let syms = semGenericStmtSymbol(c, n, s, ctx, flags, fromDotExpr=true) if syms.kind == nkSym: let choice = symChoice(c, n, s, scForceOpen) choice.kind = nkClosedSymChoice @@ -158,9 +169,9 @@ proc fuzzyLookup(c: PContext, n: PNode, flags: TSemGenericFlags, result = newDot(result, syms) proc addTempDecl(c: PContext; n: PNode; kind: TSymKind) = - let s = newSymS(skUnknown, getIdentNode(n), c) + let s = newSymS(skUnknown, getIdentNode(c, n), c) addPrelimDecl(c, s) - styleCheckDef(n.info, s, kind) + styleCheckDef(c.config, n.info, s, kind) proc semGenericStmt(c: PContext, n: PNode, flags: TSemGenericFlags, ctx: var GenericCtx): PNode = @@ -169,8 +180,8 @@ proc semGenericStmt(c: PContext, n: PNode, when defined(nimsuggest): if withinTypeDesc in flags: inc c.inTypeContext - #if gCmd == cmdIdeTools: suggestStmt(c, n) - semIdeForTemplateOrGenericCheck(n, ctx.cursorInBody) + #if conf.cmd == cmdIdeTools: suggestStmt(c, n) + semIdeForTemplateOrGenericCheck(c.config, n, ctx.cursorInBody) case n.kind of nkIdent, nkAccQuoted: @@ -201,22 +212,21 @@ proc semGenericStmt(c: PContext, n: PNode, result = semMixinStmt(c, n, ctx.toMixin) of nkCall, nkHiddenCallConv, nkInfix, nkPrefix, nkCommand, nkCallStrLit: # check if it is an expression macro: - checkMinSonsLen(n, 1) + checkMinSonsLen(n, 1, c.config) let fn = n.sons[0] var s = qualifiedLookUp(c, fn, {}) - if s == nil and + if s == nil and {withinMixin, withinConcept}*flags == {} and fn.kind in {nkIdent, nkAccQuoted} and - considerQuotedIdent(fn).id notin ctx.toMixin: + considerQuotedIdent(c, fn).id notin ctx.toMixin: errorUndeclaredIdentifier(c, n.info, fn.renderTree) var first = int ord(withinConcept in flags) var mixinContext = false if s != nil: incl(s.flags, sfUsed) - mixinContext = s.magic in {mDefined, mDefinedInScope, mCompiles} - let sc = symChoice(c, fn, s, - if s.name.id in ctx.toMixin: scForceOpen else: scOpen) + mixinContext = s.magic in {mDefined, mDefinedInScope, mCompiles, mRunnableExamples} + let sc = symChoice(c, fn, s, if s.isMixedIn: scForceOpen else: scOpen) case s.kind of skMacro: if macroToExpand(s) and sc.safeLen <= 1: @@ -275,17 +285,17 @@ proc semGenericStmt(c: PContext, n: PNode, result.sons[i] = semGenericStmt(c, result.sons[i], flags, ctx) of nkCurlyExpr: result = newNodeI(nkCall, n.info) - result.add newIdentNode(getIdent("{}"), n.info) + result.add newIdentNode(getIdent(c.cache, "{}"), n.info) for i in 0 ..< n.len: result.add(n[i]) result = semGenericStmt(c, result, flags, ctx) of nkBracketExpr: result = newNodeI(nkCall, n.info) - result.add newIdentNode(getIdent("[]"), n.info) + result.add newIdentNode(getIdent(c.cache, "[]"), n.info) for i in 0 ..< n.len: result.add(n[i]) withBracketExpr ctx, n.sons[0]: result = semGenericStmt(c, result, flags, ctx) of nkAsgn, nkFastAsgn: - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) let a = n.sons[0] let b = n.sons[1] @@ -293,13 +303,13 @@ proc semGenericStmt(c: PContext, n: PNode, case k of nkCurlyExpr: result = newNodeI(nkCall, n.info) - result.add newIdentNode(getIdent("{}="), n.info) + result.add newIdentNode(getIdent(c.cache, "{}="), n.info) for i in 0 ..< a.len: result.add(a[i]) result.add(b) result = semGenericStmt(c, result, flags, ctx) of nkBracketExpr: result = newNodeI(nkCall, n.info) - result.add newIdentNode(getIdent("[]="), n.info) + result.add newIdentNode(getIdent(c.cache, "[]="), n.info) for i in 0 ..< a.len: result.add(a[i]) result.add(b) withBracketExpr ctx, a.sons[0]: @@ -323,7 +333,7 @@ proc semGenericStmt(c: PContext, n: PNode, n.sons[0] = semGenericStmt(c, n.sons[0], flags, ctx) for i in countup(1, sonsLen(n)-1): var a = n.sons[i] - checkMinSonsLen(a, 1) + checkMinSonsLen(a, 1, c.config) var L = sonsLen(a) for j in countup(0, L-2): a.sons[j] = semGenericStmt(c, a.sons[j], flags, ctx) @@ -340,23 +350,23 @@ proc semGenericStmt(c: PContext, n: PNode, closeScope(c) closeScope(c) of nkBlockStmt, nkBlockExpr, nkBlockType: - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) openScope(c) if n.sons[0].kind != nkEmpty: addTempDecl(c, n.sons[0], skLabel) n.sons[1] = semGenericStmt(c, n.sons[1], flags, ctx) closeScope(c) of nkTryStmt: - checkMinSonsLen(n, 2) + checkMinSonsLen(n, 2, c.config) n.sons[0] = semGenericStmtScope(c, n.sons[0], flags, ctx) for i in countup(1, sonsLen(n)-1): var a = n.sons[i] - checkMinSonsLen(a, 1) + checkMinSonsLen(a, 1, c.config) var L = sonsLen(a) openScope(c) for j in countup(0, L-2): if a.sons[j].isInfixAs(): - addTempDecl(c, getIdentNode(a.sons[j][2]), skLet) + addTempDecl(c, getIdentNode(c, a.sons[j][2]), skLet) a.sons[j].sons[1] = semGenericStmt(c, a.sons[j][1], flags+{withinTypeDesc}, ctx) else: a.sons[j] = semGenericStmt(c, a.sons[j], flags+{withinTypeDesc}, ctx) @@ -367,44 +377,44 @@ proc semGenericStmt(c: PContext, n: PNode, for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] if a.kind == nkCommentStmt: continue - if (a.kind != nkIdentDefs) and (a.kind != nkVarTuple): illFormedAst(a) - checkMinSonsLen(a, 3) + if (a.kind != nkIdentDefs) and (a.kind != nkVarTuple): illFormedAst(a, c.config) + checkMinSonsLen(a, 3, c.config) var L = sonsLen(a) a.sons[L-2] = semGenericStmt(c, a.sons[L-2], flags+{withinTypeDesc}, ctx) a.sons[L-1] = semGenericStmt(c, a.sons[L-1], flags, ctx) for j in countup(0, L-3): - addTempDecl(c, getIdentNode(a.sons[j]), skVar) + addTempDecl(c, getIdentNode(c, a.sons[j]), skVar) of nkGenericParams: for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] - if (a.kind != nkIdentDefs): illFormedAst(a) - checkMinSonsLen(a, 3) + if (a.kind != nkIdentDefs): illFormedAst(a, c.config) + checkMinSonsLen(a, 3, c.config) var L = sonsLen(a) a.sons[L-2] = semGenericStmt(c, a.sons[L-2], flags+{withinTypeDesc}, ctx) # do not perform symbol lookup for default expressions for j in countup(0, L-3): - addTempDecl(c, getIdentNode(a.sons[j]), skType) + addTempDecl(c, getIdentNode(c, a.sons[j]), skType) of nkConstSection: for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] if a.kind == nkCommentStmt: continue - if (a.kind != nkConstDef): illFormedAst(a) - checkSonsLen(a, 3) - addTempDecl(c, getIdentNode(a.sons[0]), skConst) + if (a.kind != nkConstDef): illFormedAst(a, c.config) + checkSonsLen(a, 3, c.config) + addTempDecl(c, getIdentNode(c, a.sons[0]), skConst) a.sons[1] = semGenericStmt(c, a.sons[1], flags+{withinTypeDesc}, ctx) a.sons[2] = semGenericStmt(c, a.sons[2], flags, ctx) of nkTypeSection: for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] if a.kind == nkCommentStmt: continue - if (a.kind != nkTypeDef): illFormedAst(a) - checkSonsLen(a, 3) - addTempDecl(c, getIdentNode(a.sons[0]), skType) + if (a.kind != nkTypeDef): illFormedAst(a, c.config) + checkSonsLen(a, 3, c.config) + addTempDecl(c, getIdentNode(c, a.sons[0]), skType) for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] if a.kind == nkCommentStmt: continue - if (a.kind != nkTypeDef): illFormedAst(a) - checkSonsLen(a, 3) + if (a.kind != nkTypeDef): illFormedAst(a, c.config) + checkSonsLen(a, 3, c.config) if a.sons[1].kind != nkEmpty: openScope(c) a.sons[1] = semGenericStmt(c, a.sons[1], flags, ctx) @@ -421,34 +431,34 @@ proc semGenericStmt(c: PContext, n: PNode, case n.sons[i].kind of nkEnumFieldDef: a = n.sons[i].sons[0] of nkIdent: a = n.sons[i] - else: illFormedAst(n) - addDecl(c, newSymS(skUnknown, getIdentNode(a), c)) + else: illFormedAst(n, c.config) + addDecl(c, newSymS(skUnknown, getIdentNode(c, a), c)) of nkObjectTy, nkTupleTy, nkTupleClassTy: discard of nkFormalParams: - checkMinSonsLen(n, 1) + checkMinSonsLen(n, 1, c.config) if n.sons[0].kind != nkEmpty: n.sons[0] = semGenericStmt(c, n.sons[0], flags+{withinTypeDesc}, ctx) for i in countup(1, sonsLen(n) - 1): var a = n.sons[i] - if (a.kind != nkIdentDefs): illFormedAst(a) - checkMinSonsLen(a, 3) + if (a.kind != nkIdentDefs): illFormedAst(a, c.config) + checkMinSonsLen(a, 3, c.config) var L = sonsLen(a) a.sons[L-2] = semGenericStmt(c, a.sons[L-2], flags+{withinTypeDesc}, ctx) a.sons[L-1] = semGenericStmt(c, a.sons[L-1], flags, ctx) for j in countup(0, L-3): - addTempDecl(c, getIdentNode(a.sons[j]), skParam) + addTempDecl(c, getIdentNode(c, a.sons[j]), skParam) of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef, nkFuncDef, nkIteratorDef, nkLambdaKinds: - checkSonsLen(n, bodyPos + 1) + checkSonsLen(n, bodyPos + 1, c.config) if n.sons[namePos].kind != nkEmpty: - addTempDecl(c, getIdentNode(n.sons[0]), skProc) + addTempDecl(c, getIdentNode(c, n.sons[0]), skProc) openScope(c) n.sons[genericParamsPos] = semGenericStmt(c, n.sons[genericParamsPos], flags, ctx) if n.sons[paramsPos].kind != nkEmpty: if n.sons[paramsPos].sons[0].kind != nkEmpty: - addPrelimDecl(c, newSym(skUnknown, getIdent("result"), nil, n.info)) + addPrelimDecl(c, newSym(skUnknown, getIdent(c.cache, "result"), nil, n.info)) n.sons[paramsPos] = semGenericStmt(c, n.sons[paramsPos], flags, ctx) n.sons[pragmasPos] = semGenericStmt(c, n.sons[pragmasPos], flags, ctx) var body: PNode @@ -463,7 +473,7 @@ proc semGenericStmt(c: PContext, n: PNode, closeScope(c) of nkPragma, nkPragmaExpr: discard of nkExprColonExpr, nkExprEqExpr: - checkMinSonsLen(n, 2) + checkMinSonsLen(n, 2, c.config) result.sons[1] = semGenericStmt(c, n.sons[1], flags, ctx) else: for i in countup(0, sonsLen(n) - 1): @@ -472,7 +482,6 @@ proc semGenericStmt(c: PContext, n: PNode, when defined(nimsuggest): if withinTypeDesc in flags: dec c.inTypeContext - proc semGenericStmt(c: PContext, n: PNode): PNode = var ctx: GenericCtx ctx.toMixin = initIntset() @@ -484,3 +493,4 @@ proc semConceptBody(c: PContext, n: PNode): PNode = ctx.toMixin = initIntset() result = semGenericStmt(c, n, {withinConcept}, ctx) semIdeForTemplateOrGeneric(c, result, ctx.cursorInBody) + diff --git a/compiler/seminst.nim b/compiler/seminst.nim index acea9330b..4bf1e6ef2 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -54,10 +54,13 @@ proc pushProcCon*(c: PContext; owner: PSym) = rawPushProcCon(c, owner) rawHandleSelf(c, owner) +const + errCannotInstantiateX = "cannot instantiate: '$1'" + iterator instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable): PSym = - internalAssert n.kind == nkGenericParams + internalAssert c.config, n.kind == nkGenericParams for i, a in n.pairs: - internalAssert a.kind == nkSym + internalAssert c.config, a.kind == nkSym var q = a.sym if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyStatic}+tyTypeClasses: continue @@ -71,10 +74,10 @@ iterator instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable): PSym # later by semAsgn in return type inference scenario t = q.typ else: - localError(a.info, errCannotInstantiateX, s.name.s) + localError(c.config, a.info, errCannotInstantiateX % s.name.s) t = errorType(c) elif t.kind == tyGenericParam: - localError(a.info, errCannotInstantiateX, q.name.s) + localError(c.config, a.info, errCannotInstantiateX % q.name.s) t = errorType(c) elif t.kind == tyGenericInvocation: #t = instGenericContainer(c, a, t) @@ -94,10 +97,9 @@ proc sameInstantiation(a, b: TInstantiation): bool = proc genericCacheGet(genericSym: PSym, entry: TInstantiation; id: CompilesId): PSym = - if genericSym.procInstCache != nil: - for inst in genericSym.procInstCache: - if inst.compilesId == id and sameInstantiation(entry, inst[]): - return inst.sym + for inst in genericSym.procInstCache: + if inst.compilesId == id and sameInstantiation(entry, inst[]): + return inst.sym when false: proc `$`(x: PSym): string = @@ -145,7 +147,7 @@ proc instantiateBody(c: PContext, n, params: PNode, result, orig: PSym) = freshGenSyms(b, result, orig, symMap) b = semProcBody(c, b) b = hloBody(c, b) - n.sons[bodyPos] = transformBody(c.module, b, result) + n.sons[bodyPos] = transformBody(c.graph, c.module, b, result) #echo "code instantiated ", result.name.s excl(result.flags, sfForward) dec c.inGenericInst @@ -156,13 +158,13 @@ proc fixupInstantiatedSymbols(c: PContext, s: PSym) = var oldPrc = c.generics[i].inst.sym pushProcCon(c, oldPrc) pushOwner(c, oldPrc) - pushInfoContext(oldPrc.info) + pushInfoContext(c.config, oldPrc.info) openScope(c) var n = oldPrc.ast n.sons[bodyPos] = copyTree(s.getBody) instantiateBody(c, n, oldPrc.typ.n, oldPrc, s) closeScope(c) - popInfoContext() + popInfoContext(c.config) popOwner(c) popProcCon(c) @@ -174,6 +176,8 @@ proc sideEffectsCheck(c: PContext, s: PSym) = proc instGenericContainer(c: PContext, info: TLineInfo, header: PType, allowMetaTypes = false): PType = + internalAssert c.config, header.kind == tyGenericInvocation + var typeMap: LayeredIdTable cl: TReplTypeVars @@ -185,7 +189,43 @@ proc instGenericContainer(c: PContext, info: TLineInfo, header: PType, cl.info = info cl.c = c cl.allowMetaTypes = allowMetaTypes + + # We must add all generic params in scope, because the generic body + # may include tyFromExpr nodes depending on these generic params. + # XXX: This looks quite similar to the code in matchUserTypeClass, + # perhaps the code can be extracted in a shared function. + openScope(c) + let genericTyp = header.base + for i in 0 .. (genericTyp.len - 2): + let genParam = genericTyp[i] + var param: PSym + + template paramSym(kind): untyped = + newSym(kind, genParam.sym.name, genericTyp.sym, genParam.sym.info) + + if genParam.kind == tyStatic: + param = paramSym skConst + param.ast = header[i+1].n + param.typ = header[i+1] + else: + param = paramSym skType + param.typ = makeTypeDesc(c, header[i+1]) + + # this scope was not created by the user, + # unused params shoudn't be reported. + param.flags.incl sfUsed + addDecl(c, param) + result = replaceTypeVarsT(cl, header) + closeScope(c) + +proc referencesAnotherParam(n: PNode, p: PSym): bool = + if n.kind == nkSym: + return n.sym.kind == skParam and n.sym.owner == p + else: + for i in 0..<n.safeLen: + if referencesAnotherParam(n[i], p): return true + return false proc instantiateProcType(c: PContext, pt: TIdTable, prc: PSym, info: TLineInfo) = @@ -203,38 +243,67 @@ proc instantiateProcType(c: PContext, pt: TIdTable, # at this point semtypinst have to become part of sem, because it # will need to use openScope, addDecl, etc. #addDecl(c, prc) - - pushInfoContext(info) + pushInfoContext(c.config, info) var typeMap = initLayeredTypeMap(pt) var cl = initTypeVars(c, addr(typeMap), info, nil) var result = instCopyType(cl, prc.typ) let originalParams = result.n result.n = originalParams.shallowCopy - for i in 1 ..< result.len: # twrong_field_caching requires these 'resetIdTable' calls: if i > 1: resetIdTable(cl.symMap) resetIdTable(cl.localCache) - result.sons[i] = replaceTypeVarsT(cl, result.sons[i]) - propagateToOwner(result, result.sons[i]) - internalAssert originalParams[i].kind == nkSym - when true: - let oldParam = originalParams[i].sym - let param = copySym(oldParam) - param.owner = prc - param.typ = result.sons[i] - if oldParam.ast != nil: - param.ast = fitNode(c, param.typ, oldParam.ast, oldParam.ast.info) - - # don't be lazy here and call replaceTypeVarsN(cl, originalParams[i])! - result.n.sons[i] = newSymNode(param) - addDecl(c, param) - else: - let param = replaceTypeVarsN(cl, originalParams[i]) - result.n.sons[i] = param - param.sym.owner = prc - addDecl(c, result.n.sons[i].sym) + + # take a note of the original type. If't a free type or static parameter + # we'll need to keep it unbound for the `fitNode` operation below... + var typeToFit = result[i] + + let needsStaticSkipping = result[i].kind == tyFromExpr + result[i] = replaceTypeVarsT(cl, result[i]) + if needsStaticSkipping: + result[i] = result[i].skipTypes({tyStatic}) + + # ...otherwise, we use the instantiated type in `fitNode` + if (typeToFit.kind != tyTypeDesc or typeToFit.base.kind != tyNone) and + (typeToFit.kind != tyStatic): + typeToFit = result[i] + + internalAssert c.config, originalParams[i].kind == nkSym + let oldParam = originalParams[i].sym + let param = copySym(oldParam) + param.owner = prc + param.typ = result[i] + + # The default value is instantiated and fitted against the final + # concrete param type. We avoid calling `replaceTypeVarsN` on the + # call head symbol, because this leads to infinite recursion. + if oldParam.ast != nil: + var def = oldParam.ast.copyTree + if def.kind == nkCall: + for i in 1 ..< def.len: + def[i] = replaceTypeVarsN(cl, def[i]) + + def = semExprWithType(c, def) + if def.referencesAnotherParam(getCurrOwner(c)): + def.flags.incl nfDefaultRefsParam + + var converted = indexTypesMatch(c, typeToFit, def.typ, def) + if converted == nil: + # The default value doesn't match the final instantiated type. + # As an example of this, see: + # https://github.com/nim-lang/Nim/issues/1201 + # We are replacing the default value with an error node in case + # the user calls an explicit instantiation of the proc (this is + # the only way the default value might be inserted). + param.ast = errorNode(c, def) + else: + param.ast = fitNodePostMatch(c, typeToFit, converted) + param.typ = result[i] + + result.n[i] = newSymNode(param) + propagateToOwner(result, result[i]) + addDecl(c, param) resetIdTable(cl.symMap) resetIdTable(cl.localCache) @@ -247,7 +316,7 @@ proc instantiateProcType(c: PContext, pt: TIdTable, skipIntLiteralParams(result) prc.typ = result - popInfoContext() + popInfoContext(c.config) proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, info: TLineInfo): PSym = @@ -255,9 +324,10 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, ## The `pt` parameter is a type-unsafe mapping table used to link generic ## parameters to their concrete types within the generic instance. # no need to instantiate generic templates/macros: - internalAssert fn.kind notin {skMacro, skTemplate} + internalAssert c.config, fn.kind notin {skMacro, skTemplate} # generates an instantiated proc - if c.instCounter > 1000: internalError(fn.ast.info, "nesting too deep") + if c.instCounter > 50: + globalError(c.config, info, "generic instantiation too nested") inc(c.instCounter) # careful! we copy the whole AST including the possibly nil body! var n = copyTree(fn.ast) @@ -276,9 +346,9 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, openScope(c) let gp = n.sons[genericParamsPos] - internalAssert gp.kind != nkEmpty + internalAssert c.config, gp.kind != nkEmpty n.sons[namePos] = newSymNode(result) - pushInfoContext(info) + pushInfoContext(c.config, info) var entry = TInstantiation.new entry.sym = result # we need to compare both the generic types and the concrete types: @@ -297,7 +367,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, inc i if tfTriggersCompileTime in result.typ.flags: incl(result.flags, sfCompileTime) - n.sons[genericParamsPos] = ast.emptyNode + n.sons[genericParamsPos] = c.graph.emptyNode var oldPrc = genericCacheGet(fn, entry[], c.compilesContextId) if oldPrc == nil: # we MUST not add potentially wrong instantiations to the caching mechanism. @@ -316,11 +386,13 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, if c.inGenericContext == 0: instantiateBody(c, n, fn.typ.n, result, fn) sideEffectsCheck(c, result) - paramsTypeCheck(c, result.typ) + if result.magic != mSlice: + # 'toOpenArray' is special and it is allowed to return 'openArray': + paramsTypeCheck(c, result.typ) else: result = oldPrc popProcCon(c) - popInfoContext() + popInfoContext(c.config) closeScope(c) # close scope for parameters popOwner(c) c.currentScope = oldScope diff --git a/compiler/semmacrosanity.nim b/compiler/semmacrosanity.nim index fe9bb6c8d..3056f5d72 100644 --- a/compiler/semmacrosanity.nim +++ b/compiler/semmacrosanity.nim @@ -10,7 +10,7 @@ ## Implements type sanity checking for ASTs resulting from macros. Lots of ## room for improvement here. -import ast, astalgo, msgs, types +import ast, astalgo, msgs, types, options proc ithField(n: PNode, field: var int): PSym = result = nil @@ -20,7 +20,7 @@ proc ithField(n: PNode, field: var int): PSym = result = ithField(n.sons[i], field) if result != nil: return of nkRecCase: - if n.sons[0].kind != nkSym: internalError(n.info, "ithField") + if n.sons[0].kind != nkSym: return result = ithField(n.sons[0], field) if result != nil: return for i in countup(1, sonsLen(n) - 1): @@ -28,13 +28,13 @@ proc ithField(n: PNode, field: var int): PSym = of nkOfBranch, nkElse: result = ithField(lastSon(n.sons[i]), field) if result != nil: return - else: internalError(n.info, "ithField(record case branch)") + else: discard of nkSym: if field == 0: result = n.sym else: dec(field) else: discard -proc annotateType*(n: PNode, t: PType) = +proc annotateType*(n: PNode, t: PType; conf: ConfigRef) = let x = t.skipTypes(abstractInst+{tyRange}) # Note: x can be unequal to t and we need to be careful to use 't' # to not to skip tyGenericInst @@ -46,50 +46,50 @@ proc annotateType*(n: PNode, t: PType) = var j = i-1 let field = x.n.ithField(j) if field.isNil: - globalError n.info, "invalid field at index " & $i + globalError conf, n.info, "invalid field at index " & $i else: - internalAssert(n.sons[i].kind == nkExprColonExpr) - annotateType(n.sons[i].sons[1], field.typ) - of nkPar: + internalAssert(conf, n.sons[i].kind == nkExprColonExpr) + annotateType(n.sons[i].sons[1], field.typ, conf) + of nkPar, nkTupleConstr: if x.kind == tyTuple: n.typ = t for i in 0 ..< n.len: - if i >= x.len: globalError n.info, "invalid field at index " & $i - else: annotateType(n.sons[i], x.sons[i]) + if i >= x.len: globalError conf, n.info, "invalid field at index " & $i + else: annotateType(n.sons[i], x.sons[i], conf) elif x.kind == tyProc and x.callConv == ccClosure: n.typ = t else: - globalError(n.info, "() must have a tuple type") + globalError(conf, n.info, "() must have a tuple type") of nkBracket: if x.kind in {tyArray, tySequence, tyOpenArray}: n.typ = t - for m in n: annotateType(m, x.elemType) + for m in n: annotateType(m, x.elemType, conf) else: - globalError(n.info, "[] must have some form of array type") + globalError(conf, n.info, "[] must have some form of array type") of nkCurly: if x.kind in {tySet}: n.typ = t - for m in n: annotateType(m, x.elemType) + for m in n: annotateType(m, x.elemType, conf) else: - globalError(n.info, "{} must have the set type") + globalError(conf, n.info, "{} must have the set type") of nkFloatLit..nkFloat128Lit: if x.kind in {tyFloat..tyFloat128}: n.typ = t else: - globalError(n.info, "float literal must have some float type") + globalError(conf, n.info, "float literal must have some float type") of nkCharLit..nkUInt64Lit: if x.kind in {tyInt..tyUInt64, tyBool, tyChar, tyEnum}: n.typ = t else: - globalError(n.info, "integer literal must have some int type") + globalError(conf, n.info, "integer literal must have some int type") of nkStrLit..nkTripleStrLit: if x.kind in {tyString, tyCString}: n.typ = t else: - globalError(n.info, "string literal must be of some string type") + globalError(conf, n.info, "string literal must be of some string type") of nkNilLit: - if x.kind in NilableTypes: + if x.kind in NilableTypes+{tyString, tySequence}: n.typ = t else: - globalError(n.info, "nil literal must be of some pointer type") + globalError(conf, n.info, "nil literal must be of some pointer type") else: discard diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 189babdec..8bfa5545e 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -16,7 +16,7 @@ proc semAddr(c: PContext; n: PNode; isUnsafeAddr=false): PNode = if x.kind == nkSym: x.sym.flags.incl(sfAddrTaken) if isAssignable(c, x, isUnsafeAddr) notin {arLValue, arLocalLValue}: - localError(n.info, errExprHasNoAddress) + localError(c.config, n.info, errExprHasNoAddress) result.add x result.typ = makePtrType(c, x.typ) @@ -41,9 +41,9 @@ proc semArrGet(c: PContext; n: PNode; flags: TExprFlags): PNode = result = semSubscript(c, result, flags) if result.isNil: let x = copyTree(n) - x.sons[0] = newIdentNode(getIdent"[]", n.info) + x.sons[0] = newIdentNode(getIdent(c.cache, "[]"), n.info) bracketNotFoundError(c, x) - #localError(n.info, "could not resolve: " & $n) + #localError(c.config, n.info, "could not resolve: " & $n) result = n proc semArrPut(c: PContext; n: PNode; flags: TExprFlags): PNode = @@ -64,25 +64,28 @@ proc semAsgnOpr(c: PContext; n: PNode): PNode = proc semIsPartOf(c: PContext, n: PNode, flags: TExprFlags): PNode = var r = isPartOf(n[1], n[2]) - result = newIntNodeT(ord(r), n) + result = newIntNodeT(ord(r), n, c.graph) proc expectIntLit(c: PContext, n: PNode): int = let x = c.semConstExpr(c, n) case x.kind of nkIntLit..nkInt64Lit: result = int(x.intVal) - else: localError(n.info, errIntLiteralExpected) + else: localError(c.config, n.info, errIntLiteralExpected) proc semInstantiationInfo(c: PContext, n: PNode): PNode = - result = newNodeIT(nkPar, n.info, n.typ) + result = newNodeIT(nkTupleConstr, n.info, n.typ) let idx = expectIntLit(c, n.sons[1]) let useFullPaths = expectIntLit(c, n.sons[2]) - let info = getInfoContext(idx) - var filename = newNodeIT(nkStrLit, n.info, getSysType(tyString)) - filename.strVal = if useFullPaths != 0: info.toFullPath else: info.toFilename - var line = newNodeIT(nkIntLit, n.info, getSysType(tyInt)) + let info = getInfoContext(c.config, idx) + var filename = newNodeIT(nkStrLit, n.info, getSysType(c.graph, n.info, tyString)) + filename.strVal = if useFullPaths != 0: toFullPath(c.config, info) else: toFilename(c.config, info) + var line = newNodeIT(nkIntLit, n.info, getSysType(c.graph, n.info, tyInt)) line.intVal = toLinenumber(info) + var column = newNodeIT(nkIntLit, n.info, getSysType(c.graph, n.info, tyInt)) + column.intVal = toColumn(info) result.add(filename) result.add(line) + result.add(column) proc toNode(t: PType, i: TLineInfo): PNode = result = newNodeIT(nkType, i, t) @@ -107,10 +110,10 @@ proc uninstantiate(t: PType): PType = of tyCompositeTypeClass: uninstantiate t.sons[1] else: t -proc evalTypeTrait(traitCall: PNode, operand: PType, context: PSym): PNode = +proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym): PNode = const skippedTypes = {tyTypeDesc, tyAlias, tySink} let trait = traitCall[0] - internalAssert trait.kind == nkSym + internalAssert c.config, trait.kind == nkSym var operand = operand.skipTypes(skippedTypes) template operand2: PType = @@ -137,7 +140,7 @@ proc evalTypeTrait(traitCall: PNode, operand: PType, context: PSym): PNode = of "genericHead": var res = uninstantiate(operand) if res == operand and res.kind notin tyMagicGenerics: - localError(traitCall.info, + localError(c.config, traitCall.info, "genericHead expects a generic type. The given type was " & typeToString(operand)) return newType(tyError, context).toNode(traitCall.info) @@ -148,19 +151,19 @@ proc evalTypeTrait(traitCall: PNode, operand: PType, context: PSym): PNode = let t = operand.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred}) let complexObj = containsGarbageCollectedRef(t) or hasDestructor(t) - result = newIntNodeT(ord(not complexObj), traitCall) + result = newIntNodeT(ord(not complexObj), traitCall, c.graph) else: - localError(traitCall.info, "unknown trait") - result = emptyNode + localError(c.config, traitCall.info, "unknown trait") + result = newNodeI(nkEmpty, traitCall.info) proc semTypeTraits(c: PContext, n: PNode): PNode = - checkMinSonsLen(n, 2) + checkMinSonsLen(n, 2, c.config) let t = n.sons[1].typ - internalAssert t != nil and t.kind == tyTypeDesc + internalAssert c.config, t != nil and t.kind == tyTypeDesc if t.sonsLen > 0: # This is either a type known to sem or a typedesc # param to a regular proc (again, known at instantiation) - result = evalTypeTrait(n, t, getCurrOwner(c)) + result = evalTypeTrait(c, n, t, getCurrOwner(c)) else: # a typedesc variable, pass unmodified to evals result = n @@ -168,12 +171,12 @@ proc semTypeTraits(c: PContext, n: PNode): PNode = proc semOrd(c: PContext, n: PNode): PNode = result = n let parType = n.sons[1].typ - if isOrdinalType(parType): + if isOrdinalType(parType, allowEnumWithHoles=true): discard elif parType.kind == tySet: - result.typ = makeRangeType(c, firstOrd(parType), lastOrd(parType), n.info) + result.typ = makeRangeType(c, firstOrd(c.config, parType), lastOrd(c.config, parType), n.info) else: - localError(n.info, errOrdinalTypeExpected) + localError(c.config, n.info, errOrdinalTypeExpected) result.typ = errorType(c) proc semBindSym(c: PContext, n: PNode): PNode = @@ -182,29 +185,90 @@ proc semBindSym(c: PContext, n: PNode): PNode = let sl = semConstExpr(c, n.sons[1]) if sl.kind notin {nkStrLit, nkRStrLit, nkTripleStrLit}: - localError(n.sons[1].info, errStringLiteralExpected) + localError(c.config, n.sons[1].info, errStringLiteralExpected) return errorNode(c, n) let isMixin = semConstExpr(c, n.sons[2]) if isMixin.kind != nkIntLit or isMixin.intVal < 0 or isMixin.intVal > high(TSymChoiceRule).int: - localError(n.sons[2].info, errConstExprExpected) + localError(c.config, n.sons[2].info, errConstExprExpected) return errorNode(c, n) - let id = newIdentNode(getIdent(sl.strVal), n.info) + let id = newIdentNode(getIdent(c.cache, sl.strVal), n.info) let s = qualifiedLookUp(c, id, {checkUndeclared}) if s != nil: # we need to mark all symbols: var sc = symChoice(c, id, s, TSymChoiceRule(isMixin.intVal)) + if not (c.inStaticContext > 0 or getCurrOwner(c).isCompileTimeProc): + # inside regular code, bindSym resolves to the sym-choice + # nodes (see tinspectsymbol) + return sc result.add(sc) else: errorUndeclaredIdentifier(c, n.sons[1].info, sl.strVal) -proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode +proc opBindSym(c: PContext, scope: PScope, n: PNode, isMixin: int, info: PNode): PNode = + if n.kind notin {nkStrLit, nkRStrLit, nkTripleStrLit, nkIdent}: + localError(c.config, info.info, errStringOrIdentNodeExpected) + return errorNode(c, n) + + if isMixin < 0 or isMixin > high(TSymChoiceRule).int: + localError(c.config, info.info, errConstExprExpected) + return errorNode(c, n) + + let id = if n.kind == nkIdent: n + else: newIdentNode(getIdent(c.cache, n.strVal), info.info) + + let tmpScope = c.currentScope + c.currentScope = scope + let s = qualifiedLookUp(c, id, {checkUndeclared}) + if s != nil: + # we need to mark all symbols: + result = symChoice(c, id, s, TSymChoiceRule(isMixin)) + else: + errorUndeclaredIdentifier(c, info.info, if n.kind == nkIdent: n.ident.s + else: n.strVal) + c.currentScope = tmpScope + +proc semDynamicBindSym(c: PContext, n: PNode): PNode = + # inside regular code, bindSym resolves to the sym-choice + # nodes (see tinspectsymbol) + if not (c.inStaticContext > 0 or getCurrOwner(c).isCompileTimeProc): + return semBindSym(c, n) + + if c.graph.vm.isNil: + setupGlobalCtx(c.module, c.graph) + + let + vm = PCtx c.graph.vm + # cache the current scope to + # prevent it lost into oblivion + scope = c.currentScope + + # cannot use this + # vm.config.features.incl dynamicBindSym + + proc bindSymWrapper(a: VmArgs) = + # capture PContext and currentScope + # param description: + # 0. ident, a string literal / computed string / or ident node + # 1. bindSym rule + # 2. info node + a.setResult opBindSym(c, scope, a.getNode(0), a.getInt(1).int, a.getNode(2)) + + let + # altough we use VM callback here, it is not + # executed like 'normal' VM callback + idx = vm.registerCallback("bindSymImpl", bindSymWrapper) + # dummy node to carry idx information to VM + idxNode = newIntTypeNode(nkIntLit, idx, c.graph.getSysType(TLineInfo(), tyInt)) -proc isStrangeArray(t: PType): bool = - let t = t.skipTypes(abstractInst) - result = t.kind == tyArray and t.firstOrd != 0 + result = copyNode(n) + for x in n: result.add x + result.add n # info node + result.add idxNode + +proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode proc semOf(c: PContext, n: PNode): PNode = if sonsLen(n) == 3: @@ -218,9 +282,9 @@ proc semOf(c: PContext, n: PNode): PNode = let y = skipTypes(n.sons[2].typ, abstractPtrs-{tyTypeDesc}) if x.kind == tyTypeDesc or y.kind != tyTypeDesc: - localError(n.info, errXExpectsObjectTypes, "of") + localError(c.config, n.info, "'of' takes object types") elif b.kind != tyObject or a.kind != tyObject: - localError(n.info, errXExpectsObjectTypes, "of") + localError(c.config, n.info, "'of' takes object types") else: let diff = inheritanceDiff(a, b) # | returns: 0 iff `a` == `b` @@ -229,26 +293,26 @@ proc semOf(c: PContext, n: PNode): PNode = # | returns: `maxint` iff `a` and `b` are not compatible at all if diff <= 0: # optimize to true: - message(n.info, hintConditionAlwaysTrue, renderTree(n)) + message(c.config, n.info, hintConditionAlwaysTrue, renderTree(n)) result = newIntNode(nkIntLit, 1) result.info = n.info - result.typ = getSysType(tyBool) + result.typ = getSysType(c.graph, n.info, tyBool) return result elif diff == high(int): - localError(n.info, errXcanNeverBeOfThisSubtype, typeToString(a)) + localError(c.config, n.info, "'$1' cannot be of this subtype" % typeToString(a)) else: - localError(n.info, errXExpectsTwoArguments, "of") - n.typ = getSysType(tyBool) + localError(c.config, n.info, "'of' takes 2 arguments") + n.typ = getSysType(c.graph, n.info, tyBool) result = n proc magicsAfterOverloadResolution(c: PContext, n: PNode, flags: TExprFlags): PNode = case n[0].sym.magic of mAddr: - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) result = semAddr(c, n.sons[1], n[0].sym.name.s == "unsafeAddr") of mTypeOf: - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) result = semTypeOf(c, n.sons[1]) of mArrGet: result = semArrGet(c, n, flags) of mArrPut: result = semArrPut(c, n, flags) @@ -260,25 +324,29 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode, of mIsPartOf: result = semIsPartOf(c, n, flags) of mTypeTrait: result = semTypeTraits(c, n) of mAstToStr: - result = newStrNodeT(renderTree(n[1], {renderNoComments}), n) - result.typ = getSysType(tyString) + result = newStrNodeT(renderTree(n[1], {renderNoComments}), n, c.graph) + result.typ = getSysType(c.graph, n.info, tyString) of mInstantiationInfo: result = semInstantiationInfo(c, n) of mOrd: result = semOrd(c, n) of mOf: result = semOf(c, n) of mHigh, mLow: result = semLowHigh(c, n, n[0].sym.magic) of mShallowCopy: result = semShallowCopy(c, n, flags) - of mNBindSym: result = semBindSym(c, n) + of mNBindSym: + if dynamicBindSym notin c.features: + result = semBindSym(c, n) + else: + result = semDynamicBindSym(c, n) of mProcCall: result = n result.typ = n[1].typ of mDotDot: result = n of mRoof: - localError(n.info, "builtin roof operator is not supported anymore") + localError(c.config, n.info, "builtin roof operator is not supported anymore") of mPlugin: - let plugin = getPlugin(n[0].sym) + let plugin = getPlugin(c.cache, n[0].sym) if plugin.isNil: - localError(n.info, "cannot find plugin " & n[0].sym.name.s) + localError(c.config, n.info, "cannot find plugin " & n[0].sym.name.s) result = n else: result = plugin(c, n) diff --git a/compiler/semobjconstr.nim b/compiler/semobjconstr.nim index 63a4eb99a..90ab2c57a 100644 --- a/compiler/semobjconstr.nim +++ b/compiler/semobjconstr.nim @@ -39,32 +39,32 @@ proc mergeInitStatus(existing: var InitStatus, newStatus: InitStatus) = of initUnknown: discard -proc invalidObjConstr(n: PNode) = +proc invalidObjConstr(c: PContext, n: PNode) = if n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s[0] == ':': - localError(n.info, "incorrect object construction syntax; use a space after the colon") + localError(c.config, n.info, "incorrect object construction syntax; use a space after the colon") else: - localError(n.info, "incorrect object construction syntax") + localError(c.config, n.info, "incorrect object construction syntax") -proc locateFieldInInitExpr(field: PSym, initExpr: PNode): PNode = +proc locateFieldInInitExpr(c: PContext, field: PSym, initExpr: PNode): PNode = # Returns the assignment nkExprColonExpr node or nil let fieldId = field.name.id for i in 1 ..< initExpr.len: let assignment = initExpr[i] if assignment.kind != nkExprColonExpr: - invalidObjConstr(assignment) + invalidObjConstr(c, assignment) continue - if fieldId == considerQuotedIdent(assignment[0]).id: + if fieldId == considerQuotedIdent(c, assignment[0]).id: return assignment proc semConstrField(c: PContext, flags: TExprFlags, field: PSym, initExpr: PNode): PNode = - let assignment = locateFieldInInitExpr(field, initExpr) + let assignment = locateFieldInInitExpr(c, field, initExpr) if assignment != nil: if nfSem in assignment.flags: return assignment[1] if not fieldVisible(c, field): - localError(initExpr.info, - "the field '$1' is not accessible.", [field.name.s]) + localError(c.config, initExpr.info, + "the field '$1' is not accessible." % [field.name.s]) return var initValue = semExprFlagDispatched(c, assignment[1], flags) @@ -76,8 +76,10 @@ proc semConstrField(c: PContext, flags: TExprFlags, return initValue proc caseBranchMatchesExpr(branch, matched: PNode): bool = - for i in 0 .. (branch.len - 2): - if exprStructuralEquivalent(branch[i], matched): + for i in 0 .. branch.len-2: + if branch[i].kind == nkRange: + if overlap(branch[i], matched): return true + elif exprStructuralEquivalent(branch[i], matched): return true return false @@ -99,36 +101,36 @@ iterator directFieldsInRecList(recList: PNode): PNode = if recList.kind == nkSym: yield recList else: - internalAssert recList.kind == nkRecList + doAssert recList.kind == nkRecList for field in recList: if field.kind != nkSym: continue yield field template quoteStr(s: string): string = "'" & s & "'" -proc fieldsPresentInInitExpr(fieldsRecList, initExpr: PNode): string = +proc fieldsPresentInInitExpr(c: PContext, fieldsRecList, initExpr: PNode): string = result = "" for field in directFieldsInRecList(fieldsRecList): - let assignment = locateFieldInInitExpr(field.sym, initExpr) + let assignment = locateFieldInInitExpr(c, field.sym, initExpr) if assignment != nil: if result.len != 0: result.add ", " result.add field.sym.name.s.quoteStr -proc missingMandatoryFields(fieldsRecList, initExpr: PNode): string = +proc missingMandatoryFields(c: PContext, fieldsRecList, initExpr: PNode): string = for r in directFieldsInRecList(fieldsRecList): if {tfNotNil, tfNeedsInit} * r.sym.typ.flags != {}: - let assignment = locateFieldInInitExpr(r.sym, initExpr) + let assignment = locateFieldInInitExpr(c, r.sym, initExpr) if assignment == nil: - if result == nil: + if result.len == 0: result = r.sym.name.s else: result.add ", " result.add r.sym.name.s -proc checkForMissingFields(recList, initExpr: PNode) = - let missing = missingMandatoryFields(recList, initExpr) - if missing != nil: - localError(initExpr.info, "fields not initialized: $1.", [missing]) +proc checkForMissingFields(c: PContext, recList, initExpr: PNode) = + let missing = missingMandatoryFields(c, recList, initExpr) + if missing.len > 0: + localError(c.config, initExpr.info, "fields not initialized: $1.", [missing]) proc semConstructFields(c: PContext, recNode: PNode, initExpr: PNode, flags: TExprFlags): InitStatus = @@ -144,14 +146,14 @@ proc semConstructFields(c: PContext, recNode: PNode, template fieldsPresentInBranch(branchIdx: int): string = let branch = recNode[branchIdx] let fields = branch[branch.len - 1] - fieldsPresentInInitExpr(fields, initExpr) + fieldsPresentInInitExpr(c, fields, initExpr) template checkMissingFields(branchNode: PNode) = let fields = branchNode[branchNode.len - 1] - checkForMissingFields(fields, initExpr) + checkForMissingFields(c, fields, initExpr) - let discriminator = recNode.sons[0]; - internalAssert discriminator.kind == nkSym + let discriminator = recNode.sons[0] + internalAssert c.config, discriminator.kind == nkSym var selectedBranch = -1 for i in 1 ..< recNode.len: @@ -162,9 +164,9 @@ proc semConstructFields(c: PContext, recNode: PNode, if selectedBranch != -1: let prevFields = fieldsPresentInBranch(selectedBranch) let currentFields = fieldsPresentInBranch(i) - localError(initExpr.info, - "The fields ($1) and ($2) cannot be initialized together, " & - "because they are from conflicting branches in the case object.", + localError(c.config, initExpr.info, + ("The fields '$1' and '$2' cannot be initialized together, " & + "because they are from conflicting branches in the case object.") % [prevFields, currentFields]) result = initConflict else: @@ -177,9 +179,9 @@ proc semConstructFields(c: PContext, recNode: PNode, discriminator.sym, initExpr) if discriminatorVal == nil: let fields = fieldsPresentInBranch(selectedBranch) - localError(initExpr.info, - "you must provide a compile-time value for the discriminator '$1' " & - "in order to prove that it's safe to initialize $2.", + localError(c.config, initExpr.info, + ("you must provide a compile-time value for the discriminator '$1' " & + "in order to prove that it's safe to initialize $2.") % [discriminator.sym.name.s, fields]) mergeInitStatus(result, initNone) else: @@ -187,7 +189,7 @@ proc semConstructFields(c: PContext, recNode: PNode, template wrongBranchError(i) = let fields = fieldsPresentInBranch(i) - localError(initExpr.info, + localError(c.config, initExpr.info, "a case selecting discriminator '$1' with value '$2' " & "appears in the object construction, but the field(s) $3 " & "are in conflict with this value.", @@ -217,7 +219,7 @@ proc semConstructFields(c: PContext, recNode: PNode, # value was given to the discrimator. We can assume that it will be # initialized to zero and this will select a particular branch as # a result: - let matchedBranch = recNode.pickCaseBranch newIntLit(0) + let matchedBranch = recNode.pickCaseBranch newIntLit(c.graph, initExpr.info, 0) checkMissingFields matchedBranch else: result = initPartial @@ -237,20 +239,17 @@ proc semConstructFields(c: PContext, recNode: PNode, result = if e != nil: initFull else: initNone else: - internalAssert false + internalAssert c.config, false proc semConstructType(c: PContext, initExpr: PNode, t: PType, flags: TExprFlags): InitStatus = var t = t result = initUnknown - while true: let status = semConstructFields(c, t.n, initExpr, flags) mergeInitStatus(result, status) - if status in {initPartial, initNone, initUnknown}: - checkForMissingFields t.n, initExpr - + checkForMissingFields c, t.n, initExpr let base = t.sons[0] if base == nil: break t = skipTypes(base, skipPtrs) @@ -261,13 +260,13 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = for child in n: result.add child if t == nil: - localError(n.info, errGenerated, "object constructor needs an object type") + localError(c.config, n.info, errGenerated, "object constructor needs an object type") return t = skipTypes(t, {tyGenericInst, tyAlias, tySink}) if t.kind == tyRef: t = skipTypes(t.sons[0], {tyGenericInst, tyAlias, tySink}) if t.kind != tyObject: - localError(n.info, errGenerated, "object constructor needs an object type") + localError(c.config, n.info, errGenerated, "object constructor needs an object type") return # Check if the object is fully initialized by recursively testing each @@ -294,17 +293,16 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = let field = result[i] if nfSem notin field.flags: if field.kind != nkExprColonExpr: - invalidObjConstr(field) + invalidObjConstr(c, field) continue - let id = considerQuotedIdent(field[0]) + let id = considerQuotedIdent(c, field[0]) # This node was not processed. There are two possible reasons: # 1) It was shadowed by a field with the same name on the left for j in 1 ..< i: - let prevId = considerQuotedIdent(result[j][0]) + let prevId = considerQuotedIdent(c, result[j][0]) if prevId.id == id.id: - localError(field.info, errFieldInitTwice, id.s) + localError(c.config, field.info, errFieldInitTwice % id.s) return # 2) No such field exists in the constructed type - localError(field.info, errUndeclaredFieldX, id.s) + localError(c.config, field.info, errUndeclaredFieldX % id.s) return - diff --git a/compiler/semparallel.nim b/compiler/semparallel.nim index 057ade01d..0d780bdee 100644 --- a/compiler/semparallel.nim +++ b/compiler/semparallel.nim @@ -23,7 +23,8 @@ import ast, astalgo, idents, lowerings, magicsys, guards, sempass2, msgs, - renderer, types + renderer, types, modulegraphs, options + from trees import getMagic from strutils import `%` @@ -73,12 +74,15 @@ type # the 'parallel' section currentSpawnId: int inLoop: int + graph: ModuleGraph -proc initAnalysisCtx(): AnalysisCtx = +proc initAnalysisCtx(g: ModuleGraph): AnalysisCtx = result.locals = @[] result.slices = @[] result.args = @[] - result.guards = @[] + result.guards.s = @[] + result.guards.o = initOperators(g) + result.graph = g proc lookupSlot(c: AnalysisCtx; s: PSym): int = for i in 0..<c.locals.len: @@ -117,7 +121,7 @@ proc checkLocal(c: AnalysisCtx; n: PNode) = if isLocal(n): let s = c.lookupSlot(n.sym) if s >= 0 and c.locals[s].stride != nil: - localError(n.info, "invalid usage of counter after increment") + localError(c.graph.config, n.info, "invalid usage of counter after increment") else: for i in 0 ..< n.safeLen: checkLocal(c, n.sons[i]) @@ -126,14 +130,14 @@ template `?`(x): untyped = x.renderTree proc checkLe(c: AnalysisCtx; a, b: PNode) = case proveLe(c.guards, a, b) of impUnknown: - localError(a.info, "cannot prove: " & ?a & " <= " & ?b & " (bounds check)") + localError(c.graph.config, a.info, "cannot prove: " & ?a & " <= " & ?b & " (bounds check)") of impYes: discard of impNo: - localError(a.info, "can prove: " & ?a & " > " & ?b & " (bounds check)") + localError(c.graph.config, a.info, "can prove: " & ?a & " > " & ?b & " (bounds check)") proc checkBounds(c: AnalysisCtx; arr, idx: PNode) = - checkLe(c, arr.lowBound, idx) - checkLe(c, idx, arr.highBound) + checkLe(c, lowBound(c.graph.config, arr), idx) + checkLe(c, idx, highBound(c.graph.config, arr, c.guards.o)) proc addLowerBoundAsFacts(c: var AnalysisCtx) = for v in c.locals: @@ -142,34 +146,34 @@ proc addLowerBoundAsFacts(c: var AnalysisCtx) = proc addSlice(c: var AnalysisCtx; n: PNode; x, le, ri: PNode) = checkLocal(c, n) - let le = le.canon - let ri = ri.canon + let le = le.canon(c.guards.o) + let ri = ri.canon(c.guards.o) # perform static bounds checking here; and not later! - let oldState = c.guards.len + let oldState = c.guards.s.len addLowerBoundAsFacts(c) c.checkBounds(x, le) c.checkBounds(x, ri) - c.guards.setLen(oldState) + c.guards.s.setLen(oldState) c.slices.add((x, le, ri, c.currentSpawnId, c.inLoop > 0)) -proc overlap(m: TModel; x,y,c,d: PNode) = +proc overlap(m: TModel; conf: ConfigRef; x,y,c,d: PNode) = # X..Y and C..D overlap iff (X <= D and C <= Y) case proveLe(m, c, y) of impUnknown: case proveLe(m, x, d) of impNo: discard of impUnknown, impYes: - localError(x.info, + localError(conf, x.info, "cannot prove: $# > $#; required for ($#)..($#) disjoint from ($#)..($#)" % [?c, ?y, ?x, ?y, ?c, ?d]) of impYes: case proveLe(m, x, d) of impUnknown: - localError(x.info, + localError(conf, x.info, "cannot prove: $# > $#; required for ($#)..($#) disjoint from ($#)..($#)" % [?x, ?d, ?x, ?y, ?c, ?d]) of impYes: - localError(x.info, "($#)..($#) not disjoint from ($#)..($#)" % + localError(conf, x.info, "($#)..($#) not disjoint from ($#)..($#)" % [?c, ?y, ?x, ?y, ?c, ?d]) of impNo: discard of impNo: discard @@ -187,7 +191,7 @@ proc subStride(c: AnalysisCtx; n: PNode): PNode = if isLocal(n): let s = c.lookupSlot(n.sym) if s >= 0 and c.locals[s].stride != nil: - result = n +@ c.locals[s].stride.intVal + result = buildAdd(n, c.locals[s].stride.intVal, c.guards.o) else: result = n elif n.safeLen > 0: @@ -203,7 +207,7 @@ proc checkSlicesAreDisjoint(c: var AnalysisCtx) = addLowerBoundAsFacts(c) # Every slice used in a loop needs to be disjoint with itself: for x,a,b,id,inLoop in items(c.slices): - if inLoop: overlap(c.guards, a,b, c.subStride(a), c.subStride(b)) + if inLoop: overlap(c.guards, c.graph.config, a,b, c.subStride(a), c.subStride(b)) # Another tricky example is: # while true: # spawn f(a[i]) @@ -231,21 +235,21 @@ proc checkSlicesAreDisjoint(c: var AnalysisCtx) = # XXX strictly speaking, 'or' is not correct here and it needs to # be 'and'. However this prevents too many obviously correct programs # like f(a[0..x]); for i in x+1 .. a.high: f(a[i]) - overlap(c.guards, x.a, x.b, y.a, y.b) + overlap(c.guards, c.graph.config, x.a, x.b, y.a, y.b) elif (let k = simpleSlice(x.a, x.b); let m = simpleSlice(y.a, y.b); k >= 0 and m >= 0): # ah I cannot resist the temptation and add another sweet heuristic: # if both slices have the form (i+k)..(i+k) and (i+m)..(i+m) we # check they are disjoint and k < stride and m < stride: - overlap(c.guards, x.a, x.b, y.a, y.b) + overlap(c.guards, c.graph.config, x.a, x.b, y.a, y.b) let stride = min(c.stride(x.a), c.stride(y.a)) if k < stride and m < stride: discard else: - localError(x.x.info, "cannot prove ($#)..($#) disjoint from ($#)..($#)" % + localError(c.graph.config, x.x.info, "cannot prove ($#)..($#) disjoint from ($#)..($#)" % [?x.a, ?x.b, ?y.a, ?y.b]) else: - localError(x.x.info, "cannot prove ($#)..($#) disjoint from ($#)..($#)" % + localError(c.graph.config, x.x.info, "cannot prove ($#)..($#) disjoint from ($#)..($#)" % [?x.a, ?x.b, ?y.a, ?y.b]) proc analyse(c: var AnalysisCtx; n: PNode) @@ -292,31 +296,31 @@ proc analyseCall(c: var AnalysisCtx; n: PNode; op: PSym) = proc analyseCase(c: var AnalysisCtx; n: PNode) = analyse(c, n.sons[0]) - let oldFacts = c.guards.len + let oldFacts = c.guards.s.len for i in 1..<n.len: let branch = n.sons[i] - setLen(c.guards, oldFacts) + setLen(c.guards.s, oldFacts) addCaseBranchFacts(c.guards, n, i) for i in 0 ..< branch.len: analyse(c, branch.sons[i]) - setLen(c.guards, oldFacts) + setLen(c.guards.s, oldFacts) proc analyseIf(c: var AnalysisCtx; n: PNode) = analyse(c, n.sons[0].sons[0]) - let oldFacts = c.guards.len - addFact(c.guards, canon(n.sons[0].sons[0])) + let oldFacts = c.guards.s.len + addFact(c.guards, canon(n.sons[0].sons[0], c.guards.o)) analyse(c, n.sons[0].sons[1]) for i in 1..<n.len: let branch = n.sons[i] - setLen(c.guards, oldFacts) + setLen(c.guards.s, oldFacts) for j in 0..i-1: - addFactNeg(c.guards, canon(n.sons[j].sons[0])) + addFactNeg(c.guards, canon(n.sons[j].sons[0], c.guards.o)) if branch.len > 1: - addFact(c.guards, canon(branch.sons[0])) + addFact(c.guards, canon(branch.sons[0], c.guards.o)) for i in 0 ..< branch.len: analyse(c, branch.sons[i]) - setLen(c.guards, oldFacts) + setLen(c.guards.s, oldFacts) proc analyse(c: var AnalysisCtx; n: PNode) = case n.kind @@ -345,10 +349,11 @@ proc analyse(c: var AnalysisCtx; n: PNode) = if n[0].kind == nkSym: analyseCall(c, n, n[0].sym) else: analyseSons(c, n) of nkBracketExpr: - c.addSlice(n, n[0], n[1], n[1]) + if n[0].typ != nil and skipTypes(n[0].typ, abstractVar).kind != tyTuple: + c.addSlice(n, n[0], n[1], n[1]) analyseSons(c, n) of nkReturnStmt, nkRaiseStmt, nkTryStmt: - localError(n.info, "invalid control flow for 'parallel'") + localError(c.graph.config, n.info, "invalid control flow for 'parallel'") # 'break' that leaves the 'parallel' section is not valid either # or maybe we should generate a 'try' XXX of nkVarSection, nkLetSection: @@ -364,7 +369,7 @@ proc analyse(c: var AnalysisCtx; n: PNode) = if it[j].isLocal: let slot = c.getSlot(it[j].sym) if slot.lower.isNil: slot.lower = value - else: internalError(it.info, "slot already has a lower bound") + else: internalError(c.graph.config, it.info, "slot already has a lower bound") if not isSpawned: analyse(c, value) of nkCaseStmt: analyseCase(c, n) of nkWhen, nkIfStmt, nkIfExpr: analyseIf(c, n) @@ -377,14 +382,14 @@ proc analyse(c: var AnalysisCtx; n: PNode) = else: # loop may never execute: let oldState = c.locals.len - let oldFacts = c.guards.len - addFact(c.guards, canon(n.sons[0])) + let oldFacts = c.guards.s.len + addFact(c.guards, canon(n.sons[0], c.guards.o)) analyse(c, n.sons[1]) setLen(c.locals, oldState) - setLen(c.guards, oldFacts) + setLen(c.guards.s, oldFacts) # we know after the loop the negation holds: if not hasSubnodeWith(n.sons[1], nkBreakStmt): - addFactNeg(c.guards, canon(n.sons[0])) + addFactNeg(c.guards, canon(n.sons[0], c.guards.o)) dec c.inLoop of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkConstSection, nkPragma, nkFuncDef: @@ -392,13 +397,13 @@ proc analyse(c: var AnalysisCtx; n: PNode) = else: analyseSons(c, n) -proc transformSlices(n: PNode): PNode = +proc transformSlices(g: ModuleGraph; n: PNode): PNode = if n.kind in nkCallKinds and n[0].kind == nkSym: let op = n[0].sym if op.name.s == "[]" and op.fromSystem: result = copyNode(n) - let opSlice = newSymNode(createMagic("slice", mSlice)) - opSlice.typ = getSysType(tyInt) + let opSlice = newSymNode(createMagic(g, "slice", mSlice)) + opSlice.typ = getSysType(g, n.info, tyInt) result.add opSlice result.add n[1] let slice = n[2].skipStmtList @@ -408,49 +413,49 @@ proc transformSlices(n: PNode): PNode = if n.safeLen > 0: result = shallowCopy(n) for i in 0 ..< n.len: - result.sons[i] = transformSlices(n.sons[i]) + result.sons[i] = transformSlices(g, n.sons[i]) else: result = n -proc transformSpawn(owner: PSym; n, barrier: PNode): PNode -proc transformSpawnSons(owner: PSym; n, barrier: PNode): PNode = +proc transformSpawn(g: ModuleGraph; owner: PSym; n, barrier: PNode): PNode +proc transformSpawnSons(g: ModuleGraph; owner: PSym; n, barrier: PNode): PNode = result = shallowCopy(n) for i in 0 ..< n.len: - result.sons[i] = transformSpawn(owner, n.sons[i], barrier) + result.sons[i] = transformSpawn(g, owner, n.sons[i], barrier) -proc transformSpawn(owner: PSym; n, barrier: PNode): PNode = +proc transformSpawn(g: ModuleGraph; owner: PSym; n, barrier: PNode): PNode = case n.kind of nkVarSection, nkLetSection: result = nil for it in n: let b = it.lastSon if getMagic(b) == mSpawn: - if it.len != 3: localError(it.info, "invalid context for 'spawn'") - let m = transformSlices(b) + if it.len != 3: localError(g.config, it.info, "invalid context for 'spawn'") + let m = transformSlices(g, b) if result.isNil: result = newNodeI(nkStmtList, n.info) result.add n let t = b[1][0].typ.sons[0] if spawnResult(t, true) == srByVar: - result.add wrapProcForSpawn(owner, m, b.typ, barrier, it[0]) - it.sons[it.len-1] = emptyNode + result.add wrapProcForSpawn(g, owner, m, b.typ, barrier, it[0]) + it.sons[it.len-1] = newNodeI(nkEmpty, it.info) else: - it.sons[it.len-1] = wrapProcForSpawn(owner, m, b.typ, barrier, nil) + it.sons[it.len-1] = wrapProcForSpawn(g, owner, m, b.typ, barrier, nil) if result.isNil: result = n of nkAsgn, nkFastAsgn: let b = n[1] if getMagic(b) == mSpawn and (let t = b[1][0].typ.sons[0]; spawnResult(t, true) == srByVar): - let m = transformSlices(b) - return wrapProcForSpawn(owner, m, b.typ, barrier, n[0]) - result = transformSpawnSons(owner, n, barrier) + let m = transformSlices(g, b) + return wrapProcForSpawn(g, owner, m, b.typ, barrier, n[0]) + result = transformSpawnSons(g, owner, n, barrier) of nkCallKinds: if getMagic(n) == mSpawn: - result = transformSlices(n) - return wrapProcForSpawn(owner, result, n.typ, barrier, nil) - result = transformSpawnSons(owner, n, barrier) + result = transformSlices(g, n) + return wrapProcForSpawn(g, owner, result, n.typ, barrier, nil) + result = transformSpawnSons(g, owner, n, barrier) elif n.safeLen > 0: - result = transformSpawnSons(owner, n, barrier) + result = transformSpawnSons(g, owner, n, barrier) else: result = n @@ -460,7 +465,7 @@ proc checkArgs(a: var AnalysisCtx; n: PNode) = proc generateAliasChecks(a: AnalysisCtx; result: PNode) = discard "too implement" -proc liftParallel*(owner: PSym; n: PNode): PNode = +proc liftParallel*(g: ModuleGraph; owner: PSym; n: PNode): PNode = # this needs to be called after the 'for' loop elimination # first pass: @@ -469,17 +474,17 @@ proc liftParallel*(owner: PSym; n: PNode): PNode = # - detect used arguments #echo "PAR ", renderTree(n) - var a = initAnalysisCtx() + var a = initAnalysisCtx(g) let body = n.lastSon analyse(a, body) if a.spawns == 0: - localError(n.info, "'parallel' section without 'spawn'") + localError(g.config, n.info, "'parallel' section without 'spawn'") checkSlicesAreDisjoint(a) checkArgs(a, body) var varSection = newNodeI(nkVarSection, n.info) - var temp = newSym(skTemp, getIdent"barrier", owner, n.info) - temp.typ = magicsys.getCompilerProc("Barrier").typ + var temp = newSym(skTemp, getIdent(g.cache, "barrier"), owner, n.info) + temp.typ = magicsys.getCompilerProc(g, "Barrier").typ incl(temp.flags, sfFromGeneric) let tempNode = newSymNode(temp) varSection.addVar tempNode @@ -488,6 +493,6 @@ proc liftParallel*(owner: PSym; n: PNode): PNode = result = newNodeI(nkStmtList, n.info) generateAliasChecks(a, result) result.add varSection - result.add callCodegenProc("openBarrier", barrier) - result.add transformSpawn(owner, body, barrier) - result.add callCodegenProc("closeBarrier", barrier) + result.add callCodegenProc(g, "openBarrier", barrier) + result.add transformSpawn(g, owner, body, barrier) + result.add callCodegenProc(g, "closeBarrier", barrier) diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 25525d412..0a9de674b 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -9,7 +9,8 @@ import intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees, - wordrecg, strutils, options, guards, writetracking + wordrecg, strutils, options, guards, writetracking, lineinfos, + modulegraphs when defined(useDfa): import dfa @@ -52,6 +53,8 @@ type locked: seq[PNode] # locked locations gcUnsafe, isRecursive, isToplevel, hasSideEffect, inEnforcedGcSafe: bool maxLockLevel, currLockLevel: TLockLevel + config: ConfigRef + graph: ModuleGraph PEffects = var TEffects proc `<`(a, b: TLockLevel): bool {.borrow.} @@ -72,24 +75,23 @@ proc getLockLevel(t: PType): TLockLevel = proc lockLocations(a: PEffects; pragma: PNode) = if pragma.kind != nkExprColonExpr: - localError(pragma.info, errGenerated, "locks pragma without argument") + localError(a.config, pragma.info, "locks pragma without argument") return var firstLL = TLockLevel(-1'i16) for x in pragma[1]: let thisLL = getLockLevel(x.typ) if thisLL != 0.TLockLevel: if thisLL < 0.TLockLevel or thisLL > MaxLockLevel.TLockLevel: - localError(x.info, "invalid lock level: " & $thisLL) + localError(a.config, x.info, "invalid lock level: " & $thisLL) elif firstLL < 0.TLockLevel: firstLL = thisLL elif firstLL != thisLL: - localError(x.info, errGenerated, + localError(a.config, x.info, "multi-lock requires the same static lock level for every operand") a.maxLockLevel = max(a.maxLockLevel, firstLL) a.locked.add x if firstLL >= 0.TLockLevel and firstLL != a.currLockLevel: if a.currLockLevel > 0.TLockLevel and a.currLockLevel <= firstLL: - localError(pragma.info, errGenerated, - "invalid nested locking") + localError(a.config, pragma.info, "invalid nested locking") a.currLockLevel = firstLL proc guardGlobal(a: PEffects; n: PNode; guard: PSym) = @@ -102,7 +104,7 @@ proc guardGlobal(a: PEffects; n: PNode; guard: PSym) = # message(n.info, warnUnguardedAccess, renderTree(n)) #else: if not a.isTopLevel: - localError(n.info, errGenerated, "unguarded access: " & renderTree(n)) + localError(a.config, n.info, "unguarded access: " & renderTree(n)) # 'guard*' are checks which are concerned with 'guard' annotations # (var x{.guard: y.}: int) @@ -125,7 +127,7 @@ proc guardDotAccess(a: PEffects; n: PNode) = if ty == nil: break ty = ty.skipTypes(skipPtrs) if field == nil: - localError(n.info, errGenerated, "invalid guard field: " & g.name.s) + localError(a.config, n.info, "invalid guard field: " & g.name.s) return g = field #ri.sym.guard = field @@ -138,13 +140,13 @@ proc guardDotAccess(a: PEffects; n: PNode) = for L in a.locked: #if a.guards.sameSubexprs(dot, L): return if guards.sameTree(dot, L): return - localError(n.info, errGenerated, "unguarded access: " & renderTree(n)) + localError(a.config, n.info, "unguarded access: " & renderTree(n)) else: guardGlobal(a, n, g) proc makeVolatile(a: PEffects; s: PSym) {.inline.} = template compileToCpp(a): untyped = - gCmd == cmdCompileToCpp or sfCompileToCpp in getModule(a.owner).flags + a.config.cmd == cmdCompileToCpp or sfCompileToCpp in getModule(a.owner).flags if a.inTryStmt > 0 and not compileToCpp(a): incl(s.flags, sfVolatile) @@ -167,9 +169,9 @@ proc initVarViaNew(a: PEffects, n: PNode) = elif isLocalVar(a, s): makeVolatile(a, s) -proc warnAboutGcUnsafe(n: PNode) = +proc warnAboutGcUnsafe(n: PNode; conf: ConfigRef) = #assert false - message(n.info, warnGcUnsafe, renderTree(n)) + message(conf, n.info, warnGcUnsafe, renderTree(n)) proc markGcUnsafe(a: PEffects; reason: PSym) = if not a.inEnforcedGcSafe: @@ -183,8 +185,8 @@ proc markGcUnsafe(a: PEffects; reason: PNode) = if reason.kind == nkSym: a.owner.gcUnsafetyReason = reason.sym else: - a.owner.gcUnsafetyReason = newSym(skUnknown, getIdent("<unknown>"), - a.owner, reason.info) + a.owner.gcUnsafetyReason = newSym(skUnknown, a.owner.name, + a.owner, reason.info, {}) when true: template markSideEffect(a: PEffects; reason: typed) = @@ -194,42 +196,42 @@ else: a.hasSideEffect = true markGcUnsafe(a, reason) -proc listGcUnsafety(s: PSym; onlyWarning: bool; cycleCheck: var IntSet) = +proc listGcUnsafety(s: PSym; onlyWarning: bool; cycleCheck: var IntSet; conf: ConfigRef) = let u = s.gcUnsafetyReason if u != nil and not cycleCheck.containsOrIncl(u.id): let msgKind = if onlyWarning: warnGcUnsafe2 else: errGenerated case u.kind of skLet, skVar: - message(s.info, msgKind, + message(conf, s.info, msgKind, ("'$#' is not GC-safe as it accesses '$#'" & " which is a global using GC'ed memory") % [s.name.s, u.name.s]) of routineKinds: # recursive call *always* produces only a warning so the full error # message is printed: - listGcUnsafety(u, true, cycleCheck) - message(s.info, msgKind, + listGcUnsafety(u, true, cycleCheck, conf) + message(conf, s.info, msgKind, "'$#' is not GC-safe as it calls '$#'" % [s.name.s, u.name.s]) of skParam, skForVar: - message(s.info, msgKind, + message(conf, s.info, msgKind, "'$#' is not GC-safe as it performs an indirect call via '$#'" % [s.name.s, u.name.s]) else: - message(u.info, msgKind, + message(conf, u.info, msgKind, "'$#' is not GC-safe as it performs an indirect call here" % s.name.s) -proc listGcUnsafety(s: PSym; onlyWarning: bool) = +proc listGcUnsafety(s: PSym; onlyWarning: bool; conf: ConfigRef) = var cycleCheck = initIntSet() - listGcUnsafety(s, onlyWarning, cycleCheck) + listGcUnsafety(s, onlyWarning, cycleCheck, conf) proc useVar(a: PEffects, n: PNode) = let s = n.sym if isLocalVar(a, s): if s.id notin a.init: if {tfNeedsInit, tfNotNil} * s.typ.flags != {}: - message(n.info, warnProveInit, s.name.s) + message(a.config, n.info, warnProveInit, s.name.s) else: - message(n.info, warnUninit, s.name.s) + message(a.config, n.info, warnUninit, s.name.s) # prevent superfluous warnings about the same variable: a.init.add s.id if {sfGlobal, sfThread} * s.flags != {} and s.kind in {skVar, skLet} and @@ -257,34 +259,30 @@ proc addToIntersection(inter: var TIntersection, s: int) = proc throws(tracked, n: PNode) = if n.typ == nil or n.typ.kind != tyError: tracked.add n -proc getEbase(): PType = - result = if getCompilerProc("Exception") != nil: sysTypeFromName"Exception" - else: sysTypeFromName"E_Base" +proc getEbase(g: ModuleGraph; info: TLineInfo): PType = + result = g.sysTypeFromName(info, "Exception") -proc excType(n: PNode): PType = +proc excType(g: ModuleGraph; n: PNode): PType = # reraise is like raising E_Base: - let t = if n.kind == nkEmpty or n.typ.isNil: getEbase() else: n.typ + let t = if n.kind == nkEmpty or n.typ.isNil: getEbase(g, n.info) else: n.typ result = skipTypes(t, skipPtrs) -proc createRaise(n: PNode): PNode = +proc createRaise(g: ModuleGraph; n: PNode): PNode = result = newNode(nkType) - result.typ = getEbase() + result.typ = getEbase(g, n.info) if not n.isNil: result.info = n.info -proc createTag(n: PNode): PNode = +proc createTag(g: ModuleGraph; n: PNode): PNode = result = newNode(nkType) - if getCompilerProc("RootEffect") != nil: - result.typ = sysTypeFromName"RootEffect" - else: - result.typ = sysTypeFromName"TEffect" + result.typ = g.sysTypeFromName(n.info, "RootEffect") if not n.isNil: result.info = n.info proc addEffect(a: PEffects, e: PNode, useLineInfo=true) = assert e.kind != nkRaiseStmt var aa = a.exc for i in a.bottom ..< aa.len: - if sameType(aa[i].excType, e.excType): - if not useLineInfo or gCmd == cmdDoc: return + if sameType(a.graph.excType(aa[i]), a.graph.excType(e)): + if not useLineInfo or a.config.cmd == cmdDoc: return elif aa[i].info == e.info: return throws(a.exc, e) @@ -292,25 +290,25 @@ proc addTag(a: PEffects, e: PNode, useLineInfo=true) = var aa = a.tags for i in 0 ..< aa.len: if sameType(aa[i].typ.skipTypes(skipPtrs), e.typ.skipTypes(skipPtrs)): - if not useLineInfo or gCmd == cmdDoc: return + if not useLineInfo or a.config.cmd == cmdDoc: return elif aa[i].info == e.info: return throws(a.tags, e) proc mergeEffects(a: PEffects, b, comesFrom: PNode) = if b.isNil: - addEffect(a, createRaise(comesFrom)) + addEffect(a, createRaise(a.graph, comesFrom)) else: for effect in items(b): addEffect(a, effect, useLineInfo=comesFrom != nil) proc mergeTags(a: PEffects, b, comesFrom: PNode) = if b.isNil: - addTag(a, createTag(comesFrom)) + addTag(a, createTag(a.graph, comesFrom)) else: for effect in items(b): addTag(a, effect, useLineInfo=comesFrom != nil) proc listEffects(a: PEffects) = - for e in items(a.exc): message(e.info, hintUser, typeToString(e.typ)) - for e in items(a.tags): message(e.info, hintUser, typeToString(e.typ)) + for e in items(a.exc): message(a.config, e.info, hintUser, typeToString(e.typ)) + for e in items(a.tags): message(a.config, e.info, hintUser, typeToString(e.typ)) #if a.maxLockLevel != 0: # message(e.info, hintUser, "lockLevel: " & a.maxLockLevel) @@ -320,18 +318,18 @@ proc catches(tracked: PEffects, e: PType) = var i = tracked.bottom while i < L: # r supertype of e? - if safeInheritanceDiff(tracked.exc[i].excType, e) <= 0: + if safeInheritanceDiff(tracked.graph.excType(tracked.exc[i]), e) <= 0: tracked.exc.sons[i] = tracked.exc.sons[L-1] dec L else: inc i - if not isNil(tracked.exc.sons): + if tracked.exc.len > 0: setLen(tracked.exc.sons, L) else: assert L == 0 proc catchesAll(tracked: PEffects) = - if not isNil(tracked.exc.sons): + if tracked.exc.len > 0: setLen(tracked.exc.sons, tracked.bottom) proc track(tracked: PEffects, n: PNode) @@ -359,9 +357,12 @@ proc trackTryStmt(tracked: PEffects, n: PNode) = catchesAll(tracked) else: for j in countup(0, blen - 2): - assert(b.sons[j].kind == nkType) - catches(tracked, b.sons[j].typ) - + if b.sons[j].isInfixAs(): + assert(b.sons[j][1].kind == nkType) + catches(tracked, b.sons[j][1].typ) + else: + assert(b.sons[j].kind == nkType) + catches(tracked, b.sons[j].typ) setLen(tracked.init, oldState) track(tracked, b.sons[blen-1]) for i in oldState..<tracked.init.len: @@ -409,7 +410,7 @@ proc effectSpec(n: PNode, effectType: TSpecialWord): PNode = result.add(it.sons[1]) return -proc documentEffect(n, x: PNode, effectType: TSpecialWord, idx: int): PNode = +proc documentEffect(cache: IdentCache; n, x: PNode, effectType: TSpecialWord, idx: int): PNode = let spec = effectSpec(x, effectType) if isNil(spec): let s = n.sons[namePos].sym @@ -423,14 +424,14 @@ proc documentEffect(n, x: PNode, effectType: TSpecialWord, idx: int): PNode = for i in 0 ..< real.len: var t = typeToString(real[i].typ) if t.startsWith("ref "): t = substr(t, 4) - effects.sons[i] = newIdentNode(getIdent(t), n.info) + effects.sons[i] = newIdentNode(getIdent(cache, t), n.info) # set the type so that the following analysis doesn't screw up: effects.sons[i].typ = real[i].typ result = newNode(nkExprColonExpr, n.info, @[ - newIdentNode(getIdent(specialWords[effectType]), n.info), effects]) + newIdentNode(getIdent(cache, specialWords[effectType]), n.info), effects]) -proc documentWriteEffect(n: PNode; flag: TSymFlag; pragmaName: string): PNode = +proc documentWriteEffect(cache: IdentCache; n: PNode; flag: TSymFlag; pragmaName: string): PNode = let s = n.sons[namePos].sym let params = s.typ.n @@ -441,21 +442,21 @@ proc documentWriteEffect(n: PNode; flag: TSymFlag; pragmaName: string): PNode = if effects.len > 0: result = newNode(nkExprColonExpr, n.info, @[ - newIdentNode(getIdent(pragmaName), n.info), effects]) + newIdentNode(getIdent(cache, pragmaName), n.info), effects]) -proc documentNewEffect(n: PNode): PNode = +proc documentNewEffect(cache: IdentCache; n: PNode): PNode = let s = n.sons[namePos].sym if tfReturnsNew in s.typ.flags: - result = newIdentNode(getIdent("new"), n.info) + result = newIdentNode(getIdent(cache, "new"), n.info) -proc documentRaises*(n: PNode) = +proc documentRaises*(cache: IdentCache; n: PNode) = if n.sons[namePos].kind != nkSym: return let pragmas = n.sons[pragmasPos] - let p1 = documentEffect(n, pragmas, wRaises, exceptionEffects) - let p2 = documentEffect(n, pragmas, wTags, tagEffects) - let p3 = documentWriteEffect(n, sfWrittenTo, "writes") - let p4 = documentNewEffect(n) - let p5 = documentWriteEffect(n, sfEscapes, "escapes") + let p1 = documentEffect(cache, n, pragmas, wRaises, exceptionEffects) + let p2 = documentEffect(cache, n, pragmas, wTags, tagEffects) + let p3 = documentWriteEffect(cache, n, sfWrittenTo, "writes") + let p4 = documentNewEffect(cache, n) + let p5 = documentWriteEffect(cache, n, sfEscapes, "escapes") if p1 != nil or p2 != nil or p3 != nil or p4 != nil or p5 != nil: if pragmas.kind == nkEmpty: @@ -486,7 +487,7 @@ proc mergeLockLevels(tracked: PEffects, n: PNode, lockLevel: TLockLevel) = if lockLevel >= tracked.currLockLevel: # if in lock section: if tracked.currLockLevel > 0.TLockLevel: - localError n.info, errGenerated, + localError tracked.config, n.info, errGenerated, "expected lock level < " & $tracked.currLockLevel & " but got lock level " & $lockLevel tracked.maxLockLevel = max(tracked.maxLockLevel, lockLevel) @@ -500,22 +501,22 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) = mergeTags(tracked, tagSpec, n) if notGcSafe(s.typ) and sfImportc notin s.flags: - if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n) + if warnGcUnsafe in tracked.config.notes: warnAboutGcUnsafe(n, tracked.config) markGcUnsafe(tracked, s) if tfNoSideEffect notin s.typ.flags: markSideEffect(tracked, s) mergeLockLevels(tracked, n, s.getLockLevel) -proc procVarcheck(n: PNode) = +proc procVarcheck(n: PNode; conf: ConfigRef) = if n.kind in nkSymChoices: - for x in n: procVarCheck(x) + for x in n: procVarCheck(x, conf) elif n.kind == nkSym and n.sym.magic != mNone and n.sym.kind in routineKinds: - localError(n.info, errXCannotBePassedToProcVar, n.sym.name.s) + localError(conf, n.info, "'$1' cannot be passed to a procvar" % n.sym.name.s) proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) = let n = n.skipConv if paramType.isNil or paramType.kind != tyTypeDesc: - procVarcheck skipConvAndClosure(n) + procVarcheck skipConvAndClosure(n), tracked.config #elif n.kind in nkSymChoices: # echo "came here" let paramType = paramType.skipTypesOrNil(abstractInst) @@ -531,15 +532,16 @@ proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) = return case impliesNotNil(tracked.guards, n) of impUnknown: - message(n.info, errGenerated, + message(tracked.config, n.info, errGenerated, "cannot prove '$1' is not nil" % n.renderTree) of impNo: - message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree) + message(tracked.config, n.info, errGenerated, + "'$1' is provably nil" % n.renderTree) of impYes: discard proc assumeTheWorst(tracked: PEffects; n: PNode; op: PType) = - addEffect(tracked, createRaise(n)) - addTag(tracked, createTag(n)) + addEffect(tracked, createRaise(tracked.graph, n)) + addTag(tracked, createTag(tracked.graph, n)) let lockLevel = if op.lockLevel == UnspecifiedLockLevel: UnknownLockLevel else: op.lockLevel #if lockLevel == UnknownLockLevel: @@ -550,16 +552,20 @@ proc isOwnedProcVar(n: PNode; owner: PSym): bool = # XXX prove the soundness of this effect system rule result = n.kind == nkSym and n.sym.kind == skParam and owner == n.sym.owner +proc isNoEffectList(n: PNode): bool {.inline.} = + assert n.kind == nkEffectList + n.len == 0 or (n[tagEffects] == nil and n[exceptionEffects] == nil) + proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = let a = skipConvAndClosure(n) let op = a.typ if op != nil and op.kind == tyProc and n.skipConv.kind != nkNilLit: - internalAssert op.n.sons[0].kind == nkEffectList + internalAssert tracked.config, op.n.sons[0].kind == nkEffectList var effectList = op.n.sons[0] let s = n.skipConv if s.kind == nkSym and s.sym.kind in routineKinds: propagateEffects(tracked, n, s.sym) - elif effectList.len == 0: + elif isNoEffectList(effectList): if isForwardedProc(n): # we have no explicit effects but it's a forward declaration and so it's # stated there are no additional effects, so simply propagate them: @@ -569,7 +575,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = assumeTheWorst(tracked, n, op) # assume GcUnsafe unless in its type; 'forward' does not matter: if notGcSafe(op) and not isOwnedProcVar(a, tracked.owner): - if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n) + if warnGcUnsafe in tracked.config.notes: warnAboutGcUnsafe(n, tracked.config) markGcUnsafe(tracked, a) elif tfNoSideEffect notin op.flags and not isOwnedProcVar(a, tracked.owner): markSideEffect(tracked, a) @@ -577,7 +583,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeTags(tracked, effectList.sons[tagEffects], n) if notGcSafe(op): - if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n) + if warnGcUnsafe in tracked.config.notes: warnAboutGcUnsafe(n, tracked.config) markGcUnsafe(tracked, a) elif tfNoSideEffect notin op.flags: markSideEffect(tracked, a) @@ -589,7 +595,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = # XXX figure out why this can be a non tyProc here. See httpclient.nim for an # example that triggers it. if argtype.kind == tyProc and notGcSafe(argtype) and not tracked.inEnforcedGcSafe: - localError(n.info, $n & " is not GC safe") + localError(tracked.config, n.info, $n & " is not GC safe") notNilCheck(tracked, n, paramType) proc breaksBlock(n: PNode): bool = @@ -605,18 +611,18 @@ proc breaksBlock(n: PNode): bool = proc trackCase(tracked: PEffects, n: PNode) = track(tracked, n.sons[0]) let oldState = tracked.init.len - let oldFacts = tracked.guards.len + let oldFacts = tracked.guards.s.len let stringCase = skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString} let interesting = not stringCase and interestingCaseExpr(n.sons[0]) and - warnProveField in gNotes + warnProveField in tracked.config.notes var inter: TIntersection = @[] var toCover = 0 for i in 1..<n.len: let branch = n.sons[i] setLen(tracked.init, oldState) if interesting: - setLen(tracked.guards, oldFacts) + setLen(tracked.guards.s, oldFacts) addCaseBranchFacts(tracked.guards, n, i) for i in 0 ..< branch.len: track(tracked, branch.sons[i]) @@ -629,11 +635,11 @@ proc trackCase(tracked: PEffects, n: PNode) = for id, count in items(inter): if count >= toCover: tracked.init.add id # else we can't merge - setLen(tracked.guards, oldFacts) + setLen(tracked.guards.s, oldFacts) proc trackIf(tracked: PEffects, n: PNode) = track(tracked, n.sons[0].sons[0]) - let oldFacts = tracked.guards.len + let oldFacts = tracked.guards.s.len addFact(tracked.guards, n.sons[0].sons[0]) let oldState = tracked.init.len @@ -646,7 +652,7 @@ proc trackIf(tracked: PEffects, n: PNode) = for i in 1..<n.len: let branch = n.sons[i] - setLen(tracked.guards, oldFacts) + setLen(tracked.guards.s, oldFacts) for j in 0..i-1: addFactNeg(tracked.guards, n.sons[j].sons[0]) if branch.len > 1: @@ -662,7 +668,7 @@ proc trackIf(tracked: PEffects, n: PNode) = for id, count in items(inter): if count >= toCover: tracked.init.add id # else we can't merge as it is not exhaustive - setLen(tracked.guards, oldFacts) + setLen(tracked.guards.s, oldFacts) proc trackBlock(tracked: PEffects, n: PNode) = if n.kind in {nkStmtList, nkStmtListExpr}: @@ -690,7 +696,7 @@ proc paramType(op: PType, i: int): PType = proc cstringCheck(tracked: PEffects; n: PNode) = if n.sons[0].typ.kind == tyCString and (let a = skipConv(n[1]); a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}): - message(n.info, warnUnsafeCode, renderTree(n)) + message(tracked.config, n.info, warnUnsafeCode, renderTree(n)) proc track(tracked: PEffects, n: PNode) = case n.kind @@ -721,7 +727,7 @@ proc track(tracked: PEffects, n: PNode) = var effectList = op.n.sons[0] if a.kind == nkSym and a.sym.kind == skMethod: propagateEffects(tracked, n, a.sym) - elif effectList.len == 0: + elif isNoEffectList(effectList): if isForwardedProc(a): propagateEffects(tracked, n, a.sym) elif isIndirectCall(a, tracked.owner): @@ -732,7 +738,7 @@ proc track(tracked: PEffects, n: PNode) = if notGcSafe(op) and not importedFromC(a): # and it's not a recursive call: if not (a.kind == nkSym and a.sym == tracked.owner): - if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n) + if warnGcUnsafe in tracked.config.notes: warnAboutGcUnsafe(n, tracked.config) markGcUnsafe(tracked, a) if tfNoSideEffect notin op.flags and not importedFromC(a): # and it's not a recursive call: @@ -750,7 +756,7 @@ proc track(tracked: PEffects, n: PNode) = # var s: seq[notnil]; newSeq(s, 0) is a special case! discard else: - message(arg.info, warnProveInit, $arg) + message(tracked.config, arg.info, warnProveInit, $arg) for i in 0 ..< safeLen(n): track(tracked, n.sons[i]) of nkDotExpr: @@ -758,7 +764,8 @@ proc track(tracked: PEffects, n: PNode) = for i in 0 ..< len(n): track(tracked, n.sons[i]) of nkCheckedFieldExpr: track(tracked, n.sons[0]) - if warnProveField in gNotes: checkFieldAccess(tracked.guards, n) + if warnProveField in tracked.config.notes: + checkFieldAccess(tracked.guards, n, tracked.config) of nkTryStmt: trackTryStmt(tracked, n) of nkPragma: trackPragmaStmt(tracked, n) of nkAsgn, nkFastAsgn: @@ -778,6 +785,15 @@ proc track(tracked: PEffects, n: PNode) = initVar(tracked, child.sons[i], volatileCheck=false) addAsgnFact(tracked.guards, child.sons[i], last) notNilCheck(tracked, last, child.sons[i].typ) + elif child.kind == nkVarTuple and last.kind != nkEmpty: + for i in 0 .. child.len-2: + if child[i].kind == nkEmpty or + child[i].kind == nkSym and child[i].sym.name.s == "_": + continue + initVar(tracked, child[i], volatileCheck=false) + if last.kind in {nkPar, nkTupleConstr}: + addAsgnFact(tracked.guards, child[i], last[i]) + notNilCheck(tracked, last[i], child[i].typ) # since 'var (a, b): T = ()' is not even allowed, there is always type # inference for (a, b) and thus no nil checking is necessary. of nkConstSection: @@ -795,11 +811,11 @@ proc track(tracked: PEffects, n: PNode) = else: # loop may never execute: let oldState = tracked.init.len - let oldFacts = tracked.guards.len + let oldFacts = tracked.guards.s.len addFact(tracked.guards, n.sons[0]) track(tracked, n.sons[1]) setLen(tracked.init, oldState) - setLen(tracked.guards, oldFacts) + setLen(tracked.guards.s, oldFacts) of nkForStmt, nkParForStmt: # we are very conservative here and assume the loop is never executed: let oldState = tracked.init.len @@ -808,13 +824,13 @@ proc track(tracked: PEffects, n: PNode) = setLen(tracked.init, oldState) of nkObjConstr: when false: track(tracked, n.sons[0]) - let oldFacts = tracked.guards.len + let oldFacts = tracked.guards.s.len for i in 1 ..< len(n): let x = n.sons[i] track(tracked, x) if x.sons[0].kind == nkSym and sfDiscriminant in x.sons[0].sym.flags: addDiscriminantFact(tracked.guards, x) - setLen(tracked.guards, oldFacts) + setLen(tracked.guards.s, oldFacts) of nkPragmaBlock: let pragmaList = n.sons[0] let oldLocked = tracked.locked.len @@ -841,31 +857,31 @@ proc track(tracked: PEffects, n: PNode) = else: for i in 0 ..< safeLen(n): track(tracked, n.sons[i]) -proc subtypeRelation(spec, real: PNode): bool = - result = safeInheritanceDiff(real.excType, spec.typ) <= 0 +proc subtypeRelation(g: ModuleGraph; spec, real: PNode): bool = + result = safeInheritanceDiff(g.excType(real), spec.typ) <= 0 -proc checkRaisesSpec(spec, real: PNode, msg: string, hints: bool; - effectPredicate: proc (a, b: PNode): bool {.nimcall.}) = +proc checkRaisesSpec(g: ModuleGraph; spec, real: PNode, msg: string, hints: bool; + effectPredicate: proc (g: ModuleGraph; a, b: PNode): bool {.nimcall.}) = # check that any real exception is listed in 'spec'; mark those as used; # report any unused exception var used = initIntSet() for r in items(real): block search: for s in 0 ..< spec.len: - if effectPredicate(spec[s], r): + if effectPredicate(g, spec[s], r): used.incl(s) break search # XXX call graph analysis would be nice here! - pushInfoContext(spec.info) - localError(r.info, errGenerated, msg & typeToString(r.typ)) - popInfoContext() + pushInfoContext(g.config, spec.info) + localError(g.config, r.info, errGenerated, msg & typeToString(r.typ)) + popInfoContext(g.config) # hint about unnecessarily listed exception types: if hints: for s in 0 ..< spec.len: if not used.contains(s): - message(spec[s].info, hintXDeclaredButNotUsed, renderTree(spec[s])) + message(g.config, spec[s].info, hintXDeclaredButNotUsed, renderTree(spec[s])) -proc checkMethodEffects*(disp, branch: PSym) = +proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) = ## checks for consistent effects for multi methods. let actual = branch.typ.n.sons[0] if actual.len != effectListLen: return @@ -873,98 +889,101 @@ proc checkMethodEffects*(disp, branch: PSym) = let p = disp.ast.sons[pragmasPos] let raisesSpec = effectSpec(p, wRaises) if not isNil(raisesSpec): - checkRaisesSpec(raisesSpec, actual.sons[exceptionEffects], + checkRaisesSpec(g, raisesSpec, actual.sons[exceptionEffects], "can raise an unlisted exception: ", hints=off, subtypeRelation) let tagsSpec = effectSpec(p, wTags) if not isNil(tagsSpec): - checkRaisesSpec(tagsSpec, actual.sons[tagEffects], + checkRaisesSpec(g, tagsSpec, actual.sons[tagEffects], "can have an unlisted effect: ", hints=off, subtypeRelation) if sfThread in disp.flags and notGcSafe(branch.typ): - localError(branch.info, "base method is GC-safe, but '$1' is not" % + localError(g.config, branch.info, "base method is GC-safe, but '$1' is not" % branch.name.s) if branch.typ.lockLevel > disp.typ.lockLevel: when true: - message(branch.info, warnLockLevel, + message(g.config, branch.info, warnLockLevel, "base method has lock level $1, but dispatcher has $2" % [$branch.typ.lockLevel, $disp.typ.lockLevel]) else: # XXX make this an error after bigbreak has been released: - localError(branch.info, + localError(g.config, branch.info, "base method has lock level $1, but dispatcher has $2" % [$branch.typ.lockLevel, $disp.typ.lockLevel]) -proc setEffectsForProcType*(t: PType, n: PNode) = - var effects = t.n.sons[0] - internalAssert t.kind == tyProc and effects.kind == nkEffectList - - let - raisesSpec = effectSpec(n, wRaises) - tagsSpec = effectSpec(n, wTags) - if not isNil(raisesSpec) or not isNil(tagsSpec): - internalAssert effects.len == 0 +proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) = + var effects = t.n[0] + if t.kind != tyProc or effects.kind != nkEffectList: return + if n.kind != nkEmpty: + internalAssert g.config, effects.len == 0 newSeq(effects.sons, effectListLen) + let raisesSpec = effectSpec(n, wRaises) if not isNil(raisesSpec): - effects.sons[exceptionEffects] = raisesSpec + effects[exceptionEffects] = raisesSpec + let tagsSpec = effectSpec(n, wTags) if not isNil(tagsSpec): - effects.sons[tagEffects] = tagsSpec + effects[tagEffects] = tagsSpec + effects[pragmasEffects] = n -proc initEffects(effects: PNode; s: PSym; t: var TEffects) = +proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects) = newSeq(effects.sons, effectListLen) effects.sons[exceptionEffects] = newNodeI(nkArgList, s.info) effects.sons[tagEffects] = newNodeI(nkArgList, s.info) - effects.sons[usesEffects] = ast.emptyNode - effects.sons[writeEffects] = ast.emptyNode + effects.sons[usesEffects] = g.emptyNode + effects.sons[writeEffects] = g.emptyNode + effects.sons[pragmasEffects] = g.emptyNode t.exc = effects.sons[exceptionEffects] t.tags = effects.sons[tagEffects] t.owner = s t.init = @[] - t.guards = @[] + t.guards.s = @[] + t.guards.o = initOperators(g) t.locked = @[] + t.graph = g + t.config = g.config -proc trackProc*(s: PSym, body: PNode) = +proc trackProc*(g: ModuleGraph; s: PSym, body: PNode) = var effects = s.typ.n.sons[0] - internalAssert effects.kind == nkEffectList + if effects.kind != nkEffectList: return # effects already computed? if sfForward in s.flags: return if effects.len == effectListLen: return var t: TEffects - initEffects(effects, s, t) + initEffects(g, effects, s, t) track(t, body) if not isEmptyType(s.typ.sons[0]) and {tfNeedsInit, tfNotNil} * s.typ.sons[0].flags != {} and s.kind in {skProc, skFunc, skConverter, skMethod}: var res = s.ast.sons[resultPos].sym # get result symbol if res.id notin t.init: - message(body.info, warnProveInit, "result") + message(g.config, body.info, warnProveInit, "result") let p = s.ast.sons[pragmasPos] let raisesSpec = effectSpec(p, wRaises) if not isNil(raisesSpec): - checkRaisesSpec(raisesSpec, t.exc, "can raise an unlisted exception: ", + checkRaisesSpec(g, raisesSpec, t.exc, "can raise an unlisted exception: ", hints=on, subtypeRelation) # after the check, use the formal spec: effects.sons[exceptionEffects] = raisesSpec let tagsSpec = effectSpec(p, wTags) if not isNil(tagsSpec): - checkRaisesSpec(tagsSpec, t.tags, "can have an unlisted effect: ", + checkRaisesSpec(g, tagsSpec, t.tags, "can have an unlisted effect: ", hints=off, subtypeRelation) # after the check, use the formal spec: effects.sons[tagEffects] = tagsSpec if sfThread in s.flags and t.gcUnsafe: - if optThreads in gGlobalOptions and optThreadAnalysis in gGlobalOptions: + if optThreads in g.config.globalOptions and optThreadAnalysis in g.config.globalOptions: #localError(s.info, "'$1' is not GC-safe" % s.name.s) - listGcUnsafety(s, onlyWarning=false) + listGcUnsafety(s, onlyWarning=false, g.config) else: - listGcUnsafety(s, onlyWarning=true) + listGcUnsafety(s, onlyWarning=true, g.config) #localError(s.info, warnGcUnsafe2, s.name.s) if sfNoSideEffect in s.flags and t.hasSideEffect: when false: - listGcUnsafety(s, onlyWarning=false) + listGcUnsafety(s, onlyWarning=false, g.config) else: - localError(s.info, errXhasSideEffects, s.name.s) + localError(g.config, s.info, "'$1' can have side effects" % s.name.s) if not t.gcUnsafe: s.typ.flags.incl tfGcSafe if not t.hasSideEffect and sfSideEffect notin s.flags: @@ -973,7 +992,7 @@ proc trackProc*(s: PSym, body: PNode) = s.typ.lockLevel = t.maxLockLevel elif t.maxLockLevel > s.typ.lockLevel: #localError(s.info, - message(s.info, warnLockLevel, + message(g.config, s.info, warnLockLevel, "declared lock level is $1, but real lock level is $2" % [$s.typ.lockLevel, $t.maxLockLevel]) when defined(useDfa): @@ -981,12 +1000,12 @@ proc trackProc*(s: PSym, body: PNode) = dataflowAnalysis(s, body) when false: trackWrites(s, body) -proc trackTopLevelStmt*(module: PSym; n: PNode) = +proc trackTopLevelStmt*(g: ModuleGraph; module: PSym; n: PNode) = if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkFuncDef, nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}: return var effects = newNode(nkEffectList, n.info) var t: TEffects - initEffects(effects, module, t) + initEffects(g, effects, module, t) t.isToplevel = true track(t, n) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index a86787a8e..9a218f225 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -10,90 +10,90 @@ ## this module does the semantic checking of statements # included from sem.nim -var enforceVoidContext = PType(kind: tyStmt) +const + errNoSymbolToBorrowFromFound = "no symbol to borrow from found" + errDiscardValueX = "value of type '$1' has to be discarded" + errInvalidDiscard = "statement returns no value that can be discarded" + errInvalidControlFlowX = "invalid control flow: $1" + errSelectorMustBeOfCertainTypes = "selector must be of an ordinal type, float or string" + errExprCannotBeRaised = "only a 'ref object' can be raised" + errBreakOnlyInLoop = "'break' only allowed in loop construct" + errExceptionAlreadyHandled = "exception already handled" + errYieldNotAllowedHere = "'yield' only allowed in an iterator" + errYieldNotAllowedInTryStmt = "'yield' cannot be used within 'try' in a non-inlined iterator" + errInvalidNumberOfYieldExpr = "invalid number of 'yield' expressions" + errCannotReturnExpr = "current routine cannot return an expression" + errGenericLambdaNotAllowed = "A nested proc can have generic parameters only when " & + "it is used as an operand to another routine and the types " & + "of the generic paramers can be inferred from the expected signature." + errCannotInferTypeOfTheLiteral = "cannot infer the type of the $1" + errCannotInferReturnType = "cannot infer the return type of the proc" + errCannotInferStaticParam = "cannot infer the value of the static param '$1'" + errProcHasNoConcreteType = "'$1' doesn't have a concrete type, due to unspecified generic parameters." + errLetNeedsInit = "'let' symbol requires an initialization" + errThreadvarCannotInit = "a thread var cannot be initialized explicitly; this would only run for the main thread" + errImplOfXexpected = "implementation of '$1' expected" + errRecursiveDependencyX = "recursive dependency: '$1'" + errPragmaOnlyInHeaderOfProcX = "pragmas are only allowed in the header of a proc; redefinition of $1" proc semDiscard(c: PContext, n: PNode): PNode = result = n - checkSonsLen(n, 1) + checkSonsLen(n, 1, c.config) if n.sons[0].kind != nkEmpty: n.sons[0] = semExprWithType(c, n.sons[0]) if isEmptyType(n.sons[0].typ) or n.sons[0].typ.kind == tyNone or n.sons[0].kind == nkTypeOfExpr: - localError(n.info, errInvalidDiscard) + localError(c.config, n.info, errInvalidDiscard) proc semBreakOrContinue(c: PContext, n: PNode): PNode = result = n - checkSonsLen(n, 1) + checkSonsLen(n, 1, c.config) if n.sons[0].kind != nkEmpty: if n.kind != nkContinueStmt: var s: PSym case n.sons[0].kind of nkIdent: s = lookUp(c, n.sons[0]) of nkSym: s = n.sons[0].sym - else: illFormedAst(n) + else: illFormedAst(n, c.config) s = getGenSym(c, s) if s.kind == skLabel and s.owner.id == c.p.owner.id: var x = newSymNode(s) x.info = n.info incl(s.flags, sfUsed) n.sons[0] = x - suggestSym(x.info, s, c.graph.usageSym) + suggestSym(c.config, x.info, s, c.graph.usageSym) styleCheckUse(x.info, s) else: - localError(n.info, errInvalidControlFlowX, s.name.s) + localError(c.config, n.info, errInvalidControlFlowX % s.name.s) else: - localError(n.info, errGenerated, "'continue' cannot have a label") + localError(c.config, n.info, errGenerated, "'continue' cannot have a label") elif (c.p.nestedLoopCounter <= 0) and (c.p.nestedBlockCounter <= 0): - localError(n.info, errInvalidControlFlowX, + localError(c.config, n.info, errInvalidControlFlowX % renderTree(n, {renderNoComments})) -proc semAsm(con: PContext, n: PNode): PNode = - checkSonsLen(n, 2) - var marker = pragmaAsm(con, n.sons[0]) +proc semAsm(c: PContext, n: PNode): PNode = + checkSonsLen(n, 2, c.config) + var marker = pragmaAsm(c, n.sons[0]) if marker == '\0': marker = '`' # default marker - result = semAsmOrEmit(con, n, marker) + result = semAsmOrEmit(c, n, marker) proc semWhile(c: PContext, n: PNode): PNode = result = n - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) openScope(c) n.sons[0] = forceBool(c, semExprWithType(c, n.sons[0])) inc(c.p.nestedLoopCounter) n.sons[1] = semStmt(c, n.sons[1]) dec(c.p.nestedLoopCounter) closeScope(c) - if n.sons[1].typ == enforceVoidContext: - result.typ = enforceVoidContext + if n.sons[1].typ == c.enforceVoidContext: + result.typ = c.enforceVoidContext -proc toCover(t: PType): BiggestInt = - var t2 = skipTypes(t, abstractVarRange-{tyTypeDesc}) +proc toCover(c: PContext, t: PType): BiggestInt = + let t2 = skipTypes(t, abstractVarRange-{tyTypeDesc}) if t2.kind == tyEnum and enumHasHoles(t2): result = sonsLen(t2.n) else: - result = lengthOrd(skipTypes(t, abstractVar-{tyTypeDesc})) - -when false: - proc performProcvarCheck(c: PContext, info: TLineInfo, s: PSym) = - ## Checks that the given symbol is a proper procedure variable, meaning - ## that it - var smoduleId = getModule(s).id - if sfProcvar notin s.flags and s.typ.callConv == ccDefault and - smoduleId != c.module.id: - block outer: - for module in c.friendModules: - if smoduleId == module.id: - break outer - localError(info, errXCannotBePassedToProcVar, s.name.s) - -template semProcvarCheck(c: PContext, n: PNode) = - when false: - var n = n.skipConv - if n.kind in nkSymChoices: - for x in n: - if x.sym.kind in {skProc, skMethod, skConverter, skIterator}: - performProcvarCheck(c, n.info, x.sym) - elif n.kind == nkSym and n.sym.kind in {skProc, skMethod, skConverter, - skIterator}: - performProcvarCheck(c, n.info, n.sym) + result = lengthOrd(c.config, skipTypes(t, abstractVar-{tyTypeDesc})) proc semProc(c: PContext, n: PNode): PNode @@ -101,7 +101,6 @@ proc semExprBranch(c: PContext, n: PNode): PNode = result = semExpr(c, n) if result.typ != nil: # XXX tyGenericInst here? - semProcvarCheck(c, result) if result.typ.kind in {tyVar, tyLent}: result = newDeref(result) proc semExprBranchScope(c: PContext, n: PNode): PNode = @@ -112,50 +111,42 @@ proc semExprBranchScope(c: PContext, n: PNode): PNode = const skipForDiscardable = {nkIfStmt, nkIfExpr, nkCaseStmt, nkOfBranch, nkElse, nkStmtListExpr, nkTryStmt, nkFinally, nkExceptBranch, - nkElifBranch, nkElifExpr, nkElseExpr, nkBlockStmt, nkBlockExpr} + nkElifBranch, nkElifExpr, nkElseExpr, nkBlockStmt, nkBlockExpr, + nkHiddenStdConv} proc implicitlyDiscardable(n: PNode): bool = var n = n while n.kind in skipForDiscardable: n = n.lastSon - result = isCallExpr(n) and n.sons[0].kind == nkSym and - sfDiscardable in n.sons[0].sym.flags + result = n.kind == nkRaiseStmt or + (isCallExpr(n) and n.sons[0].kind == nkSym and + sfDiscardable in n.sons[0].sym.flags) -proc fixNilType(n: PNode) = +proc fixNilType(c: PContext; n: PNode) = if isAtom(n): if n.kind != nkNilLit and n.typ != nil: - localError(n.info, errDiscardValueX, n.typ.typeToString) + localError(c.config, n.info, errDiscardValueX % n.typ.typeToString) elif n.kind in {nkStmtList, nkStmtListExpr}: n.kind = nkStmtList - for it in n: fixNilType(it) + for it in n: fixNilType(c, it) n.typ = nil proc discardCheck(c: PContext, result: PNode) = if c.matchedConcept != nil: return if result.typ != nil and result.typ.kind notin {tyStmt, tyVoid}: - if result.kind == nkNilLit: - result.typ = nil - message(result.info, warnNilStatement) - elif implicitlyDiscardable(result): + if implicitlyDiscardable(result): + var n = newNodeI(nkDiscardStmt, result.info, 1) + n[0] = result + elif result.typ.kind != tyError and c.config.cmd != cmdInteractive: var n = result - result.typ = nil - while n.kind in skipForDiscardable: - n = n.lastSon - n.typ = nil - elif result.typ.kind != tyError and gCmd != cmdInteractive: - if result.typ.kind == tyNil: - fixNilType(result) - message(result.info, warnNilStatement) - else: - var n = result - while n.kind in skipForDiscardable: n = n.lastSon - var s = "expression '" & $n & "' is of type '" & - result.typ.typeToString & "' and has to be discarded" - if result.info.line != n.info.line or - result.info.fileIndex != n.info.fileIndex: - s.add "; start of expression here: " & $result.info - if result.typ.kind == tyProc: - s.add "; for a function call use ()" - localError(n.info, s) + while n.kind in skipForDiscardable: n = n.lastSon + var s = "expression '" & $n & "' is of type '" & + result.typ.typeToString & "' and has to be discarded" + if result.info.line != n.info.line or + result.info.fileIndex != n.info.fileIndex: + s.add "; start of expression here: " & c.config$result.info + if result.typ.kind == tyProc: + s.add "; for a function call use ()" + localError(c.config, n.info, s) proc semIf(c: PContext, n: PNode): PNode = result = n @@ -164,9 +155,8 @@ proc semIf(c: PContext, n: PNode): PNode = for i in countup(0, sonsLen(n) - 1): var it = n.sons[i] if it.len == 2: - when newScopeForIf: openScope(c) + openScope(c) it.sons[0] = forceBool(c, semExprWithType(c, it.sons[0])) - when not newScopeForIf: openScope(c) it.sons[1] = semExprBranch(c, it.sons[1]) typ = commonType(typ, it.sons[1]) closeScope(c) @@ -174,12 +164,12 @@ proc semIf(c: PContext, n: PNode): PNode = hasElse = true it.sons[0] = semExprBranchScope(c, it.sons[0]) typ = commonType(typ, it.sons[0]) - else: illFormedAst(it) + else: illFormedAst(it, c.config) if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse: for it in n: discardCheck(c, it.lastSon) result.kind = nkIfStmt # propagate any enforced VoidContext: - if typ == enforceVoidContext: result.typ = enforceVoidContext + if typ == c.enforceVoidContext: result.typ = c.enforceVoidContext else: for it in n: let j = it.len-1 @@ -188,140 +178,78 @@ proc semIf(c: PContext, n: PNode): PNode = result.kind = nkIfExpr result.typ = typ -proc semCase(c: PContext, n: PNode): PNode = - result = n - checkMinSonsLen(n, 2) - openScope(c) - n.sons[0] = semExprWithType(c, n.sons[0]) - var chckCovered = false - var covered: BiggestInt = 0 - var typ = commonTypeBegin - var hasElse = false - let caseTyp = skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc}) - case caseTyp.kind - of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32, tyBool: - chckCovered = true - of tyFloat..tyFloat128, tyString, tyError: - discard - else: - localError(n.info, errSelectorMustBeOfCertainTypes) - return - for i in countup(1, sonsLen(n) - 1): - var x = n.sons[i] - when defined(nimsuggest): - if gIdeCmd == ideSug and exactEquals(gTrackPos, x.info) and caseTyp.kind == tyEnum: - suggestEnum(c, x, caseTyp) - case x.kind - of nkOfBranch: - checkMinSonsLen(x, 2) - semCaseBranch(c, n, x, i, covered) - var last = sonsLen(x)-1 - x.sons[last] = semExprBranchScope(c, x.sons[last]) - typ = commonType(typ, x.sons[last]) - of nkElifBranch: - chckCovered = false - checkSonsLen(x, 2) - when newScopeForIf: openScope(c) - x.sons[0] = forceBool(c, semExprWithType(c, x.sons[0])) - when not newScopeForIf: openScope(c) - x.sons[1] = semExprBranch(c, x.sons[1]) - typ = commonType(typ, x.sons[1]) - closeScope(c) - of nkElse: - chckCovered = false - checkSonsLen(x, 1) - x.sons[0] = semExprBranchScope(c, x.sons[0]) - typ = commonType(typ, x.sons[0]) - hasElse = true - else: - illFormedAst(x) - if chckCovered: - if covered == toCover(n.sons[0].typ): - hasElse = true - else: - localError(n.info, errNotAllCasesCovered) - closeScope(c) - if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse: - for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon) - # propagate any enforced VoidContext: - if typ == enforceVoidContext: - result.typ = enforceVoidContext - else: - for i in 1..n.len-1: - var it = n.sons[i] - let j = it.len-1 - if not endsInNoReturn(it.sons[j]): - it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) - result.typ = typ - proc semTry(c: PContext, n: PNode): PNode = + + var check = initIntSet() + template semExceptBranchType(typeNode: PNode): bool = + # returns true if exception type is imported type + let typ = semTypeNode(c, typeNode, nil).toObject() + var is_imported = false + if isImportedException(typ, c.config): + is_imported = true + elif not isException(typ): + localError(c.config, typeNode.info, errExprCannotBeRaised) + + if containsOrIncl(check, typ.id): + localError(c.config, typeNode.info, errExceptionAlreadyHandled) + typeNode = newNodeIT(nkType, typeNode.info, typ) + is_imported + result = n inc c.p.inTryStmt - checkMinSonsLen(n, 2) + checkMinSonsLen(n, 2, c.config) var typ = commonTypeBegin - n.sons[0] = semExprBranchScope(c, n.sons[0]) - typ = commonType(typ, n.sons[0].typ) + n[0] = semExprBranchScope(c, n[0]) + typ = commonType(typ, n[0].typ) - var check = initIntSet() var last = sonsLen(n) - 1 for i in countup(1, last): - var a = n.sons[i] - checkMinSonsLen(a, 1) - var length = sonsLen(a) + let a = n.sons[i] + checkMinSonsLen(a, 1, c.config) openScope(c) if a.kind == nkExceptBranch: - # so that ``except [a, b, c]`` is supported: - if length == 2 and a.sons[0].kind == nkBracket: - a.sons[0..0] = a.sons[0].sons - length = a.sonsLen - - # Iterate through each exception type in the except branch. - for j in countup(0, length-2): - var typeNode = a.sons[j] # e.g. `Exception` - var symbolNode: PNode = nil # e.g. `foobar` - # Handle the case where the `Exception as foobar` syntax is used. - if typeNode.isInfixAs(): - typeNode = a.sons[j].sons[1] - symbolNode = a.sons[j].sons[2] - - # Resolve the type ident into a PType. - var typ = semTypeNode(c, typeNode, nil).toObject() - if typ.kind != tyObject: - localError(a.sons[j].info, errExprCannotBeRaised) - - let newTypeNode = newNodeI(nkType, typeNode.info) - newTypeNode.typ = typ - if symbolNode.isNil: - a.sons[j] = newTypeNode - else: - a.sons[j].sons[1] = newTypeNode - # Add the exception ident to the symbol table. - let symbol = newSymG(skLet, symbolNode, c) - symbol.typ = typ.toRef() - addDecl(c, symbol) - # Overwrite symbol in AST with the symbol in the symbol table. - let symNode = newNodeI(nkSym, typeNode.info) - symNode.sym = symbol - a.sons[j].sons[2] = symNode - - if containsOrIncl(check, typ.id): - localError(a.sons[j].info, errExceptionAlreadyHandled) + + if a.len == 2 and a[0].kind == nkBracket: + # rewrite ``except [a, b, c]: body`` -> ```except a, b, c: body``` + a.sons[0..0] = a[0].sons + + if a.len == 2 and a[0].isInfixAs(): + # support ``except Exception as ex: body`` + let is_imported = semExceptBranchType(a[0][1]) + let symbol = newSymG(skLet, a[0][2], c) + symbol.typ = if is_imported: a[0][1].typ + else: a[0][1].typ.toRef() + addDecl(c, symbol) + # Overwrite symbol in AST with the symbol in the symbol table. + a[0][2] = newSymNode(symbol, a[0][2].info) + + else: + # support ``except KeyError, ValueError, ... : body`` + var is_native, is_imported: bool + for j in 0..a.len-2: + let tmp = semExceptBranchType(a[j]) + if tmp: is_imported = true + else: is_native = true + + if is_native and is_imported: + localError(c.config, a[0].info, "Mix of imported and native exception types is not allowed in one except branch") + elif a.kind != nkFinally: - illFormedAst(n) + illFormedAst(n, c.config) # last child of an nkExcept/nkFinally branch is a statement: - a.sons[length-1] = semExprBranchScope(c, a.sons[length-1]) - if a.kind != nkFinally: typ = commonType(typ, a.sons[length-1].typ) + a[^1] = semExprBranchScope(c, a[^1]) + if a.kind != nkFinally: typ = commonType(typ, a[^1]) else: dec last closeScope(c) dec c.p.inTryStmt - if isEmptyType(typ) or typ.kind == tyNil: + if isEmptyType(typ) or typ.kind in {tyNil, tyExpr}: discardCheck(c, n.sons[0]) for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon) - if typ == enforceVoidContext: - result.typ = enforceVoidContext + if typ == c.enforceVoidContext: + result.typ = c.enforceVoidContext else: if n.lastSon.kind == nkFinally: discardCheck(c, n.lastSon.lastSon) n.sons[0] = fitNode(c, typ, n.sons[0], n.sons[0].info) @@ -340,10 +268,10 @@ proc fitRemoveHiddenConv(c: PContext, typ: PType, n: PNode): PNode = result.info = n.info result.typ = typ else: - changeType(r1, typ, check=true) + changeType(c, r1, typ, check=true) result = r1 elif not sameType(result.typ, typ): - changeType(result, typ, check=false) + changeType(c, result, typ, check=false) proc findShadowedVar(c: PContext, v: PSym): PSym = for scope in walkScopes(c.currentScope.parent): @@ -367,44 +295,23 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym = result = semIdentWithPragma(c, kind, n, {}) if result.owner.kind == skModule: incl(result.flags, sfGlobal) - suggestSym(n.info, result, c.graph.usageSym) - styleCheckDef(result) + suggestSym(c.config, n.info, result, c.graph.usageSym) + styleCheckDef(c.config, result) -proc checkNilable(v: PSym) = +proc checkNilable(c: PContext; v: PSym) = if {sfGlobal, sfImportC} * v.flags == {sfGlobal} and {tfNotNil, tfNeedsInit} * v.typ.flags != {}: if v.ast.isNil: - message(v.info, warnProveInit, v.name.s) + message(c.config, v.info, warnProveInit, v.name.s) elif tfNotNil in v.typ.flags and tfNotNil notin v.ast.typ.flags: - message(v.info, warnProveInit, v.name.s) + message(c.config, v.info, warnProveInit, v.name.s) include semasgn -proc addToVarSection(c: PContext; result: var PNode; orig, identDefs: PNode) = - # consider this: - # var - # x = 0 - # withOverloadedAssignment = foo() - # y = use(withOverloadedAssignment) - # We need to split this into a statement list with multiple 'var' sections - # in order for this transformation to be correct. +proc addToVarSection(c: PContext; result: PNode; orig, identDefs: PNode) = let L = identDefs.len let value = identDefs[L-1] - if value.typ != nil and tfHasAsgn in value.typ.flags and not newDestructors: - # the spec says we need to rewrite 'var x = T()' to 'var x: T; x = T()': - identDefs.sons[L-1] = emptyNode - if result.kind != nkStmtList: - let oldResult = result - oldResult.add identDefs - result = newNodeI(nkStmtList, result.info) - result.add oldResult - else: - let o = copyNode(orig) - o.add identDefs - result.add o - for i in 0 .. L-3: - result.add overloadedAsgn(c, identDefs[i], value) - elif result.kind == nkStmtList: + if result.kind == nkStmtList: let o = copyNode(orig) o.add identDefs result.add o @@ -417,14 +324,14 @@ proc isDiscardUnderscore(v: PSym): bool = result = true proc semUsing(c: PContext; n: PNode): PNode = - result = ast.emptyNode - if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "using") + result = c.graph.emptyNode + if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "using") for i in countup(0, sonsLen(n)-1): var a = n.sons[i] - if gCmd == cmdIdeTools: suggestStmt(c, a) + if c.config.cmd == cmdIdeTools: suggestStmt(c, a) if a.kind == nkCommentStmt: continue - if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a) - checkMinSonsLen(a, 3) + if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config) + checkMinSonsLen(a, 3, c.config) var length = sonsLen(a) if a.sons[length-2].kind != nkEmpty: let typ = semTypeNode(c, a.sons[length-2], nil) @@ -433,10 +340,10 @@ proc semUsing(c: PContext; n: PNode): PNode = v.typ = typ strTableIncl(c.signatures, v) else: - localError(a.info, "'using' section must have a type") + localError(c.config, a.info, "'using' section must have a type") var def: PNode if a.sons[length-1].kind != nkEmpty: - localError(a.info, "'using' sections cannot contain assignments") + localError(c.config, a.info, "'using' sections cannot contain assignments") proc hasEmpty(typ: PType): bool = if typ.kind in {tySequence, tyArray, tySet}: @@ -465,10 +372,10 @@ proc makeDeref(n: PNode): PNode = proc fillPartialObject(c: PContext; n: PNode; typ: PType) = if n.len == 2: let x = semExprWithType(c, n[0]) - let y = considerQuotedIdent(n[1]) + let y = considerQuotedIdent(c, n[1]) let obj = x.typ.skipTypes(abstractPtrs) if obj.kind == tyObject and tfPartial in obj.flags: - let field = newSym(skField, getIdent(y.s), obj.sym, n[1].info) + let field = newSym(skField, getIdent(c.cache, y.s), obj.sym, n[1].info) field.typ = skipIntLit(typ) field.position = sonsLen(obj.n) addSon(obj.n, newSymNode(field)) @@ -476,14 +383,14 @@ proc fillPartialObject(c: PContext; n: PNode; typ: PType) = n.sons[1] = newSymNode(field) n.typ = field.typ else: - localError(n.info, "implicit object field construction " & + localError(c.config, n.info, "implicit object field construction " & "requires a .partial object, but got " & typeToString(obj)) else: - localError(n.info, "nkDotNode requires 2 children") + localError(c.config, n.info, "nkDotNode requires 2 children") -proc setVarType(v: PSym, typ: PType) = +proc setVarType(c: PContext; v: PSym, typ: PType) = if v.typ != nil and not sameTypeOrNil(v.typ, typ): - localError(v.info, "inconsistent typing for reintroduced symbol '" & + localError(c.config, v.info, "inconsistent typing for reintroduced symbol '" & v.name.s & "': previous type was: " & typeToString(v.typ) & "; new type is: " & typeToString(typ)) v.typ = typ @@ -494,22 +401,22 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = var hasCompileTime = false for i in countup(0, sonsLen(n)-1): var a = n.sons[i] - if gCmd == cmdIdeTools: suggestStmt(c, a) + if c.config.cmd == cmdIdeTools: suggestStmt(c, a) if a.kind == nkCommentStmt: continue - if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a) - checkMinSonsLen(a, 3) + if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config) + checkMinSonsLen(a, 3, c.config) var length = sonsLen(a) var typ: PType if a.sons[length-2].kind != nkEmpty: typ = semTypeNode(c, a.sons[length-2], nil) else: typ = nil - var def: PNode = ast.emptyNode + var def: PNode = c.graph.emptyNode if a.sons[length-1].kind != nkEmpty: def = semExprWithType(c, a.sons[length-1], {efAllowDestructor}) if def.typ.kind == tyTypeDesc and c.p.owner.kind != skMacro: # prevent the all too common 'var x = int' bug: - localError(def.info, "'typedesc' metatype is not valid here; typed '=' instead of ':'?") + localError(c.config, def.info, "'typedesc' metatype is not valid here; typed '=' instead of ':'?") def.typ = errorType(c) if typ != nil: if typ.isMetaType: @@ -521,36 +428,37 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = def = fitNode(c, typ, def, def.info) #changeType(def.skipConv, typ, check=true) else: - typ = skipIntLit(def.typ) + typ = def.typ.skipTypes({tyStatic}).skipIntLit if typ.kind in tyUserTypeClasses and typ.isResolvedUserTypeClass: typ = typ.lastSon if hasEmpty(typ): - localError(def.info, errCannotInferTypeOfTheLiteral, + localError(c.config, def.info, errCannotInferTypeOfTheLiteral % ($typ.kind).substr(2).toLowerAscii) elif typ.kind == tyProc and tfUnresolved in typ.flags: - localError(def.info, errProcHasNoConcreteType, def.renderTree) + localError(c.config, def.info, errProcHasNoConcreteType % def.renderTree) else: - if symkind == skLet: localError(a.info, errLetNeedsInit) + if symkind == skLet: localError(c.config, a.info, errLetNeedsInit) # this can only happen for errornous var statements: if typ == nil: continue - typeAllowedCheck(a.info, typ, symkind, if c.matchedConcept != nil: {taConcept} else: {}) + typeAllowedCheck(c.config, a.info, typ, symkind, if c.matchedConcept != nil: {taConcept} else: {}) liftTypeBoundOps(c, typ, a.info) var tup = skipTypes(typ, {tyGenericInst, tyAlias, tySink}) if a.kind == nkVarTuple: if tup.kind != tyTuple: - localError(a.info, errXExpected, "tuple") + localError(c.config, a.info, errXExpected, "tuple") elif length-2 != sonsLen(tup): - localError(a.info, errWrongNumberOfVariables) - else: - b = newNodeI(nkVarTuple, a.info) - newSons(b, length) - b.sons[length-2] = a.sons[length-2] # keep type desc for doc generator - b.sons[length-1] = def - addToVarSection(c, result, n, b) - elif tup.kind == tyTuple and def.kind == nkPar and + localError(c.config, a.info, errWrongNumberOfVariables) + b = newNodeI(nkVarTuple, a.info) + newSons(b, length) + # keep type desc for doc generator + # NOTE: at the moment this is always ast.emptyNode, see parser.nim + b.sons[length-2] = a.sons[length-2] + b.sons[length-1] = def + addToVarSection(c, result, n, b) + elif tup.kind == tyTuple and def.kind in {nkPar, nkTupleConstr} and a.kind == nkIdentDefs and a.len > 3: - message(a.info, warnEachIdentIsTuple) + message(c.config, a.info, warnEachIdentIsTuple) for j in countup(0, length-3): if a[j].kind == nkDotExpr: @@ -568,48 +476,60 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = if shadowed != nil: shadowed.flags.incl(sfShadowed) if shadowed.kind == skResult and sfGenSym notin v.flags: - message(a.info, warnResultShadowed) + message(c.config, a.info, warnResultShadowed) # a shadowed variable is an error unless it appears on the right # side of the '=': - if warnShadowIdent in gNotes and not identWithin(def, v.name): - message(a.info, warnShadowIdent, v.name.s) + if warnShadowIdent in c.config.notes and not identWithin(def, v.name): + message(c.config, a.info, warnShadowIdent, v.name.s) if a.kind != nkVarTuple: if def.kind != nkEmpty: # this is needed for the evaluation pass and for the guard checking: v.ast = def - if sfThread in v.flags: localError(def.info, errThreadvarCannotInit) - setVarType(v, typ) + if sfThread in v.flags: localError(c.config, def.info, errThreadvarCannotInit) + setVarType(c, v, typ) b = newNodeI(nkIdentDefs, a.info) - if importantComments(): + if importantComments(c.config): # keep documentation information: b.comment = a.comment addSon(b, newSymNode(v)) - addSon(b, a.sons[length-2]) # keep type desc for doc generator + # keep type desc for doc generator, but only if the user explicitly + # added it + if a.sons[length-2].kind != nkEmpty: + addSon(b, newNodeIT(nkType, a.info, typ)) + else: + addSon(b, a.sons[length-2]) addSon(b, copyTree(def)) addToVarSection(c, result, n, b) else: - if def.kind == nkPar: v.ast = def[j] - setVarType(v, tup.sons[j]) + if def.kind in {nkPar, nkTupleConstr}: v.ast = def[j] + # bug #7663, for 'nim check' this can be a non-tuple: + if tup.kind == tyTuple: setVarType(c, v, tup.sons[j]) + else: v.typ = tup b.sons[j] = newSymNode(v) - checkNilable(v) + checkNilable(c, v) if sfCompileTime in v.flags: hasCompileTime = true - if hasCompileTime: vm.setupCompileTimeVar(c.module, c.cache, result) + if v.flags * {sfGlobal, sfThread} == {sfGlobal}: + message(c.config, v.info, hintGlobalVar) + if hasCompileTime: + vm.setupCompileTimeVar(c.module, c.graph, result) + # handled by the VM codegen: + #c.graph.recordStmt(c.graph, c.module, result) proc semConst(c: PContext, n: PNode): PNode = result = copyNode(n) for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] - if gCmd == cmdIdeTools: suggestStmt(c, a) + if c.config.cmd == cmdIdeTools: suggestStmt(c, a) if a.kind == nkCommentStmt: continue - if (a.kind != nkConstDef): illFormedAst(a) - checkSonsLen(a, 3) + if a.kind != nkConstDef: illFormedAst(a, c.config) + checkSonsLen(a, 3, c.config) var v = semIdentDef(c, a.sons[0], skConst) var typ: PType = nil if a.sons[1].kind != nkEmpty: typ = semTypeNode(c, a.sons[1], nil) var def = semConstExpr(c, a.sons[2]) if def == nil: - localError(a.sons[2].info, errConstExprExpected) + localError(c.config, a.sons[2].info, errConstExprExpected) continue # check type compatibility between def.typ and typ: if typ != nil: @@ -617,16 +537,16 @@ proc semConst(c: PContext, n: PNode): PNode = else: typ = def.typ if typ == nil: - localError(a.sons[2].info, errConstExprExpected) + localError(c.config, a.sons[2].info, errConstExprExpected) continue if typeAllowed(typ, skConst) != nil and def.kind != nkNilLit: - localError(a.info, "invalid type for const: " & typeToString(typ)) + localError(c.config, a.info, "invalid type for const: " & typeToString(typ)) continue - setVarType(v, typ) + setVarType(c, v, typ) v.ast = def # no need to copy if sfGenSym notin v.flags: addInterfaceDecl(c, v) var b = newNodeI(nkConstDef, a.info) - if importantComments(): b.comment = a.comment + if importantComments(c.config): b.comment = a.comment addSon(b, newSymNode(v)) addSon(b, a.sons[1]) addSon(b, copyTree(def)) @@ -635,18 +555,20 @@ proc semConst(c: PContext, n: PNode): PNode = include semfields proc addForVarDecl(c: PContext, v: PSym) = - if warnShadowIdent in gNotes: + if warnShadowIdent in c.config.notes: let shadowed = findShadowedVar(c, v) if shadowed != nil: # XXX should we do this here? #shadowed.flags.incl(sfShadowed) - message(v.info, warnShadowIdent, v.name.s) + message(c.config, v.info, warnShadowIdent, v.name.s) addDecl(c, v) proc symForVar(c: PContext, n: PNode): PSym = let m = if n.kind == nkPragmaExpr: n.sons[0] else: n result = newSymG(skForVar, m, c) - styleCheckDef(result) + styleCheckDef(c.config, result) + if n.kind == nkPragmaExpr: + pragma(c, result, n.sons[1], forVarPragmas) proc semForVars(c: PContext, n: PNode): PNode = result = n @@ -666,9 +588,9 @@ proc semForVars(c: PContext, n: PNode): PNode = n.sons[0] = newSymNode(v) if sfGenSym notin v.flags: addForVarDecl(c, v) else: - localError(n.info, errWrongNumberOfVariables) + localError(c.config, n.info, errWrongNumberOfVariables) elif length-2 != sonsLen(iter): - localError(n.info, errWrongNumberOfVariables) + localError(c.config, n.info, errWrongNumberOfVariables) else: for i in countup(0, length - 3): var v = symForVar(c, n.sons[i]) @@ -685,7 +607,7 @@ proc semForVars(c: PContext, n: PNode): PNode = proc implicitIterator(c: PContext, it: string, arg: PNode): PNode = result = newNodeI(nkCall, arg.info) - result.add(newIdentNode(it.getIdent, arg.info)) + result.add(newIdentNode(getIdent(c.cache, it), arg.info)) if arg.typ != nil and arg.typ.kind in {tyVar, tyLent}: result.add newDeref(arg) else: @@ -698,11 +620,79 @@ proc isTrivalStmtExpr(n: PNode): bool = return false result = true +proc handleStmtMacro(c: PContext; n, selector: PNode; magicType: string): PNode = + if selector.kind in nkCallKinds: + # we transform + # n := for a, b, c in m(x, y, z): Y + # to + # m(n) + let maType = magicsys.getCompilerProc(c.graph, magicType) + if maType == nil: return + + let headSymbol = selector[0] + var o: TOverloadIter + var match: PSym = nil + var symx = initOverloadIter(o, c, headSymbol) + while symx != nil: + if symx.kind in {skTemplate, skMacro}: + if symx.typ.len == 2 and symx.typ[1] == maType.typ: + if match == nil: + match = symx + else: + localError(c.config, n.info, errAmbiguousCallXYZ % [ + getProcHeader(c.config, match), + getProcHeader(c.config, symx), $selector]) + symx = nextOverloadIter(o, c, headSymbol) + + if match == nil: return + var callExpr = newNodeI(nkCall, n.info) + callExpr.add newSymNode(match) + callExpr.add n + case match.kind + of skMacro: result = semMacroExpr(c, callExpr, callExpr, match, {}) + of skTemplate: result = semTemplateExpr(c, callExpr, match, {}) + else: result = nil + +proc handleForLoopMacro(c: PContext; n: PNode): PNode = + result = handleStmtMacro(c, n, n[^2], "ForLoopStmt") + +proc handleCaseStmtMacro(c: PContext; n: PNode): PNode = + # n[0] has been sem'checked and has a type. We use this to resolve + # 'match(n[0])' but then we pass 'n' to the 'match' macro. This seems to + # be the best solution. + var toResolve = newNodeI(nkCall, n.info) + toResolve.add newIdentNode(getIdent(c.cache, "match"), n.info) + toResolve.add n[0] + + var errors: CandidateErrors + var r = resolveOverloads(c, toResolve, toResolve, {skTemplate, skMacro}, {}, + errors, false) + if r.state == csMatch: + var match = r.calleeSym + markUsed(c.config, n[0].info, match, c.graph.usageSym) + styleCheckUse(n[0].info, match) + + # but pass 'n' to the 'match' macro, not 'n[0]': + r.call.sons[1] = n + let toExpand = semResolvedCall(c, r, r.call, {}) + case match.kind + of skMacro: result = semMacroExpr(c, toExpand, toExpand, match, {}) + of skTemplate: result = semTemplateExpr(c, toExpand, match, {}) + else: result = nil + # this would be the perfectly consistent solution with 'for loop macros', + # but it kinda sucks for pattern matching as the matcher is not attached to + # a type then: + when false: + result = handleStmtMacro(c, n, n[0], "CaseStmt") + proc semFor(c: PContext, n: PNode): PNode = - result = n - checkMinSonsLen(n, 3) + checkMinSonsLen(n, 3, c.config) var length = sonsLen(n) + if forLoopMacros in c.features: + result = handleForLoopMacro(c, n) + if result != nil: return result openScope(c) + result = n n.sons[length-2] = semExprNoDeref(c, n.sons[length-2], {efWantIterator}) var call = n.sons[length-2] if call.kind == nkStmtListExpr and isTrivalStmtExpr(call): @@ -727,46 +717,112 @@ proc semFor(c: PContext, n: PNode): PNode = elif length == 4: n.sons[length-2] = implicitIterator(c, "pairs", n.sons[length-2]) else: - localError(n.sons[length-2].info, errIteratorExpected) + localError(c.config, n.sons[length-2].info, "iterator within for loop context expected") result = semForVars(c, n) else: result = semForVars(c, n) # propagate any enforced VoidContext: - let bodyType = n.sons[length-1].typ - if bodyType == enforceVoidContext or isEmptyType(bodyType): - result.typ = bodyType - else: - # if the body of a for loop is of type 'T', the - # loop's type is 'iterator (): T' - proc createForLoopExpr(c: PContext; t: PType; info: TLineInfo): PType {.nimcall.} = - result = newType(tyGenericInvocation, c.module) - addSonSkipIntLit(result, magicsys.getCompilerProc("ForLoopExpr").typ) - addSonSkipIntLit(result, t) - result = instGenericContainer(c, info, result, allowMetaTypes = false) - result.typ = createForLoopExpr(c, bodyType, result.info) + if n.sons[length-1].typ == c.enforceVoidContext: + result.typ = c.enforceVoidContext closeScope(c) -proc semRaise(c: PContext, n: PNode): PNode = +proc semCase(c: PContext, n: PNode): PNode = result = n - checkSonsLen(n, 1) - if n.sons[0].kind != nkEmpty: - n.sons[0] = semExprWithType(c, n.sons[0]) - var typ = n.sons[0].typ - if typ.kind != tyRef or typ.lastSon.kind != tyObject: - localError(n.info, errExprCannotBeRaised) + checkMinSonsLen(n, 2, c.config) + openScope(c) + n.sons[0] = semExprWithType(c, n.sons[0]) + var chckCovered = false + var covered: BiggestInt = 0 + var typ = commonTypeBegin + var hasElse = false + let caseTyp = skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc}) + case caseTyp.kind + of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32, tyBool: + chckCovered = true + of tyFloat..tyFloat128, tyString, tyError: + discard + else: + if caseStmtMacros in c.features: + result = handleCaseStmtMacro(c, n) + if result != nil: return result - # check if the given object inherits from Exception - if not typ.lastSon.isException(): - localError(n.info, "raised object of type $1 does not inherit from Exception", - [typeToString(typ)]) + localError(c.config, n.info, errSelectorMustBeOfCertainTypes) + return + for i in countup(1, sonsLen(n) - 1): + var x = n.sons[i] + when defined(nimsuggest): + if c.config.ideCmd == ideSug and exactEquals(c.config.m.trackPos, x.info) and caseTyp.kind == tyEnum: + suggestEnum(c, x, caseTyp) + case x.kind + of nkOfBranch: + checkMinSonsLen(x, 2, c.config) + semCaseBranch(c, n, x, i, covered) + var last = sonsLen(x)-1 + x.sons[last] = semExprBranchScope(c, x.sons[last]) + typ = commonType(typ, x.sons[last]) + of nkElifBranch: + chckCovered = false + checkSonsLen(x, 2, c.config) + openScope(c) + x.sons[0] = forceBool(c, semExprWithType(c, x.sons[0])) + x.sons[1] = semExprBranch(c, x.sons[1]) + typ = commonType(typ, x.sons[1]) + closeScope(c) + of nkElse: + chckCovered = false + checkSonsLen(x, 1, c.config) + x.sons[0] = semExprBranchScope(c, x.sons[0]) + typ = commonType(typ, x.sons[0]) + hasElse = true + else: + illFormedAst(x, c.config) + if chckCovered: + if covered == toCover(c, n.sons[0].typ): + hasElse = true + else: + localError(c.config, n.info, "not all cases are covered") + closeScope(c) + if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse: + for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon) + # propagate any enforced VoidContext: + if typ == c.enforceVoidContext: + result.typ = c.enforceVoidContext + else: + for i in 1..n.len-1: + var it = n.sons[i] + let j = it.len-1 + if not endsInNoReturn(it.sons[j]): + it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) + result.typ = typ +proc semRaise(c: PContext, n: PNode): PNode = + result = n + checkSonsLen(n, 1, c.config) + if n[0].kind != nkEmpty: + n[0] = semExprWithType(c, n[0]) + let typ = n[0].typ + if not isImportedException(typ, c.config): + if typ.kind != tyRef or typ.lastSon.kind != tyObject: + localError(c.config, n.info, errExprCannotBeRaised) + if typ.len > 0 and not isException(typ.lastSon): + localError(c.config, n.info, "raised object of type $1 does not inherit from Exception", + [typeToString(typ)]) proc addGenericParamListToScope(c: PContext, n: PNode) = - if n.kind != nkGenericParams: illFormedAst(n) + if n.kind != nkGenericParams: illFormedAst(n, c.config) for i in countup(0, sonsLen(n)-1): var a = n.sons[i] if a.kind == nkSym: addDecl(c, a.sym) - else: illFormedAst(a) + else: illFormedAst(a, c.config) + +proc typeSectionTypeName(c: PContext; n: PNode): PNode = + if n.kind == nkPragmaExpr: + if n.len == 0: illFormedAst(n, c.config) + result = n.sons[0] + else: + result = n + if result.kind != nkSym: illFormedAst(n, c.config) + proc typeSectionLeftSidePass(c: PContext, n: PNode) = # process the symbols on the left side for the whole type section, before @@ -774,21 +830,21 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) = for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] when defined(nimsuggest): - if gCmd == cmdIdeTools: + if c.config.cmd == cmdIdeTools: inc c.inTypeContext suggestStmt(c, a) dec c.inTypeContext if a.kind == nkCommentStmt: continue - if a.kind != nkTypeDef: illFormedAst(a) - checkSonsLen(a, 3) + if a.kind != nkTypeDef: illFormedAst(a, c.config) + checkSonsLen(a, 3, c.config) let name = a.sons[0] var s: PSym if name.kind == nkDotExpr and a[2].kind == nkObjectTy: - let pkgName = considerQuotedIdent(name[0]) - let typName = considerQuotedIdent(name[1]) + let pkgName = considerQuotedIdent(c, name[0]) + let typName = considerQuotedIdent(c, name[1]) let pkg = c.graph.packageSyms.strTableGet(pkgName) if pkg.isNil or pkg.kind != skPackage: - localError(name.info, "unknown package name: " & pkgName.s) + localError(c.config, name.info, "unknown package name: " & pkgName.s) else: let typsym = pkg.tab.strTableGet(typName) if typsym.isNil: @@ -802,7 +858,7 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) = s = typsym addInterfaceDecl(c, s) else: - localError(name.info, typsym.name.s & " is not a type that can be forwarded") + localError(c.config, name.info, typsym.name.s & " is not a type that can be forwarded") s = typsym else: s = semIdentDef(c, name, skType) @@ -814,7 +870,7 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) = # check if the symbol already exists: let pkg = c.module.owner if not isTopLevel(c) or pkg.isNil: - localError(name.info, "only top level types in a package can be 'package'") + localError(c.config, name.info, "only top level types in a package can be 'package'") else: let typsym = pkg.tab.strTableGet(s.name) if typsym != nil: @@ -822,22 +878,23 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) = typeCompleted(typsym) typsym.info = s.info else: - localError(name.info, "cannot complete type '" & s.name.s & "' twice; " & - "previous type completion was here: " & $typsym.info) + localError(c.config, name.info, "cannot complete type '" & s.name.s & "' twice; " & + "previous type completion was here: " & c.config$typsym.info) s = typsym # add it here, so that recursive types are possible: if sfGenSym notin s.flags: addInterfaceDecl(c, s) - a.sons[0] = newSymNode(s) + if name.kind == nkPragmaExpr: + a.sons[0].sons[0] = newSymNode(s) + else: + a.sons[0] = newSymNode(s) -proc checkCovariantParamsUsages(genericType: PType) = +proc checkCovariantParamsUsages(c: PContext; genericType: PType) = var body = genericType[^1] - proc traverseSubTypes(t: PType): bool = - template error(msg) = localError(genericType.sym.info, msg) - + proc traverseSubTypes(c: PContext; t: PType): bool = + template error(msg) = localError(c.config, genericType.sym.info, msg) result = false - template subresult(r) = let sub = r result = result or sub @@ -846,24 +903,19 @@ proc checkCovariantParamsUsages(genericType: PType) = of tyGenericParam: t.flags.incl tfWeakCovariant return true - of tyObject: for field in t.n: - subresult traverseSubTypes(field.typ) - + subresult traverseSubTypes(c, field.typ) of tyArray: - return traverseSubTypes(t[1]) - + return traverseSubTypes(c, t[1]) of tyProc: for subType in t.sons: if subType != nil: - subresult traverseSubTypes(subType) + subresult traverseSubTypes(c, subType) if result: - error("non-invariant type param used in a proc type: " & $t) - + error("non-invariant type param used in a proc type: " & $t) of tySequence: - return traverseSubTypes(t[0]) - + return traverseSubTypes(c, t[0]) of tyGenericInvocation: let targetBody = t[0] for i in 1 ..< t.len: @@ -884,44 +936,35 @@ proc checkCovariantParamsUsages(genericType: PType) = "' used in a non-contravariant position") result = true else: - subresult traverseSubTypes(param) - + subresult traverseSubTypes(c, param) of tyAnd, tyOr, tyNot, tyStatic, tyBuiltInTypeClass, tyCompositeTypeClass: error("non-invariant type parameters cannot be used with types such '" & $t & "'") - of tyUserTypeClass, tyUserTypeClassInst: error("non-invariant type parameters are not supported in concepts") - of tyTuple: for fieldType in t.sons: - subresult traverseSubTypes(fieldType) - + subresult traverseSubTypes(c, fieldType) of tyPtr, tyRef, tyVar, tyLent: if t.base.kind == tyGenericParam: return true - return traverseSubTypes(t.base) - + return traverseSubTypes(c, t.base) of tyDistinct, tyAlias, tySink: - return traverseSubTypes(t.lastSon) - + return traverseSubTypes(c, t.lastSon) of tyGenericInst: - internalAssert false - + internalAssert c.config, false else: discard - - discard traverseSubTypes(body) + discard traverseSubTypes(c, body) proc typeSectionRightSidePass(c: PContext, n: PNode) = for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] if a.kind == nkCommentStmt: continue - if (a.kind != nkTypeDef): illFormedAst(a) - checkSonsLen(a, 3) - let name = a.sons[0] - if (name.kind != nkSym): illFormedAst(a) + if a.kind != nkTypeDef: illFormedAst(a, c.config) + checkSonsLen(a, 3, c.config) + let name = typeSectionTypeName(c, a.sons[0]) var s = name.sym if s.magic == mNone and a.sons[2].kind == nkEmpty: - localError(a.info, errImplOfXexpected, s.name.s) + localError(c.config, a.info, errImplOfXexpected % s.name.s) if s.magic != mNone: processMagicType(c, s) if a.sons[1].kind != nkEmpty: # We have a generic type declaration here. In generic types, @@ -952,7 +995,7 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = body.size = -1 # could not be computed properly s.typ.sons[sonsLen(s.typ) - 1] = body if tfCovariant in s.typ.flags: - checkCovariantParamsUsages(s.typ) + checkCovariantParamsUsages(c, s.typ) # XXX: This is a temporary limitation: # The codegen currently produces various failures with # generic imported types that have fields, but we need @@ -965,7 +1008,10 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = var body = s.typ.lastSon if body.kind == tyObject: # erases all declared fields - body.n.sons = nil + when defined(nimNoNilSeqs): + body.n.sons = @[] + else: + body.n.sons = nil popOwner(c) closeScope(c) @@ -987,45 +1033,45 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = # give anonymous object a dummy symbol: var st = s.typ if st.kind == tyGenericBody: st = st.lastSon - internalAssert st.kind in {tyPtr, tyRef} - internalAssert st.lastSon.sym == nil + internalAssert c.config, st.kind in {tyPtr, tyRef} + internalAssert c.config, st.lastSon.sym == nil incl st.flags, tfRefsAnonObj - let obj = newSym(skType, getIdent(s.name.s & ":ObjectType"), + let obj = newSym(skType, getIdent(c.cache, s.name.s & ":ObjectType"), getCurrOwner(c), s.info) obj.typ = st.lastSon st.lastSon.sym = obj -proc checkForMetaFields(n: PNode) = +proc checkForMetaFields(c: PContext; n: PNode) = template checkMeta(t) = if t != nil and t.isMetaType and tfGenericTypeParam notin t.flags: - localError(n.info, errTIsNotAConcreteType, t.typeToString) + localError(c.config, n.info, errTIsNotAConcreteType % t.typeToString) if n.isNil: return case n.kind of nkRecList, nkRecCase: - for s in n: checkForMetaFields(s) + for s in n: checkForMetaFields(c, s) of nkOfBranch, nkElse: - checkForMetaFields(n.lastSon) + checkForMetaFields(c, n.lastSon) of nkSym: let t = n.sym.typ case t.kind of tySequence, tySet, tyArray, tyOpenArray, tyVar, tyLent, tyPtr, tyRef, tyProc, tyGenericInvocation, tyGenericInst, tyAlias, tySink: - let start = int ord(t.kind in {tyGenericInvocation, tyGenericInst}) - for i in start ..< t.sons.len: + let start = ord(t.kind in {tyGenericInvocation, tyGenericInst}) + for i in start ..< t.len: checkMeta(t.sons[i]) else: checkMeta(t) else: - internalAssert false + internalAssert c.config, false proc typeSectionFinalPass(c: PContext, n: PNode) = for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] if a.kind == nkCommentStmt: continue - if a.sons[0].kind != nkSym: illFormedAst(a) - var s = a.sons[0].sym + let name = typeSectionTypeName(c, a.sons[0]) + var s = name.sym # compute the type's size and check for illegal recursions: if a.sons[1].kind == nkEmpty: var x = a[2] @@ -1044,9 +1090,9 @@ proc typeSectionFinalPass(c: PContext, n: PNode) = assert s.typ != nil assignType(s.typ, t) s.typ.id = t.id # same id - checkConstructedType(s.info, s.typ) + checkConstructedType(c.config, s.info, s.typ) if s.typ.kind in {tyObject, tyTuple} and not s.typ.n.isNil: - checkForMetaFields(s.typ.n) + checkForMetaFields(c, s.typ.n) instAllTypeBoundOp(c, n.info) @@ -1055,14 +1101,14 @@ proc semAllTypeSections(c: PContext; n: PNode): PNode = case n.kind of nkIncludeStmt: for i in 0..<n.len: - var f = checkModuleName(n.sons[i]) + var f = checkModuleName(c.config, n.sons[i]) if f != InvalidFileIDX: - if containsOrIncl(c.includedFiles, f): - localError(n.info, errRecursiveDependencyX, f.toFilename) + if containsOrIncl(c.includedFiles, f.int): + localError(c.config, n.info, errRecursiveDependencyX % toFilename(c.config, f)) else: - let code = gIncludeFile(c.graph, c.module, f, c.cache) + let code = c.graph.includeFileCallback(c.graph, c.module, f) gatherStmts c, code, result - excl(c.includedFiles, f) + excl(c.includedFiles, f.int) of nkStmtList: for i in 0 ..< n.len: gatherStmts(c, n.sons[i], result) @@ -1114,12 +1160,12 @@ proc semParamList(c: PContext, n, genericParams: PNode, s: PSym) = s.typ = semProcTypeNode(c, n, genericParams, nil, s.kind) if s.kind notin {skMacro, skTemplate}: if s.typ.sons[0] != nil and s.typ.sons[0].kind == tyStmt: - localError(n.info, errGenerated, "invalid return type: 'stmt'") + localError(c.config, n.info, "invalid return type: 'stmt'") proc addParams(c: PContext, n: PNode, kind: TSymKind) = for i in countup(1, sonsLen(n)-1): if n.sons[i].kind == nkSym: addParamOrResult(c, n.sons[i].sym, kind) - else: illFormedAst(n) + else: illFormedAst(n, c.config) proc semBorrow(c: PContext, n: PNode, s: PSym) = # search for the correct alias: @@ -1128,11 +1174,11 @@ proc semBorrow(c: PContext, n: PNode, s: PSym) = # store the alias: n.sons[bodyPos] = newSymNode(b) else: - localError(n.info, errNoSymbolToBorrowFromFound) + localError(c.config, n.info, errNoSymbolToBorrowFromFound) proc addResult(c: PContext, t: PType, info: TLineInfo, owner: TSymKind) = if t != nil: - var s = newSym(skResult, getIdent"result", getCurrOwner(c), info) + var s = newSym(skResult, getIdent(c.cache, "result"), getCurrOwner(c), info) s.typ = t incl(s.flags, sfUsed) addParamOrResult(c, s, owner) @@ -1151,7 +1197,7 @@ proc lookupMacro(c: PContext, n: PNode): PSym = result = n.sym if result.kind notin {skMacro, skTemplate}: result = nil else: - result = searchInScopes(c, considerQuotedIdent(n), {skMacro, skTemplate}) + result = searchInScopes(c, considerQuotedIdent(c, n), {skMacro, skTemplate}) proc semProcAnnotation(c: PContext, prc: PNode; validPragmas: TSpecialWords): PNode = @@ -1163,11 +1209,11 @@ proc semProcAnnotation(c: PContext, prc: PNode; let m = lookupMacro(c, key) if m == nil: if key.kind == nkIdent and key.ident.id == ord(wDelegator): - if considerQuotedIdent(prc.sons[namePos]).s == "()": + if considerQuotedIdent(c, prc.sons[namePos]).s == "()": prc.sons[namePos] = newIdentNode(c.cache.idDelegator, prc.info) prc.sons[pragmasPos] = copyExcept(n, i) else: - localError(prc.info, errOnlyACallOpCanBeDelegator) + localError(c.config, prc.info, "only a call operator can be a delegator") continue elif sfCustomPragma in m.flags: continue # semantic check for custom pragma happens later in semProcAux @@ -1178,7 +1224,7 @@ proc semProcAnnotation(c: PContext, prc: PNode; x.add(newSymNode(m)) prc.sons[pragmasPos] = copyExcept(n, i) if prc[pragmasPos].kind != nkEmpty and prc[pragmasPos].len == 0: - prc.sons[pragmasPos] = emptyNode + prc.sons[pragmasPos] = c.graph.emptyNode if it.kind in nkPragmaCallKinds and it.len > 1: # pass pragma arguments to the macro too: @@ -1201,7 +1247,7 @@ proc setGenericParamsMisc(c: PContext; n: PNode): PNode = # issue https://github.com/nim-lang/Nim/issues/1713 result = semGenericParamList(c, orig) if n.sons[miscPos].kind == nkEmpty: - n.sons[miscPos] = newTree(nkBracket, ast.emptyNode, orig) + n.sons[miscPos] = newTree(nkBracket, c.graph.emptyNode, orig) else: n.sons[miscPos].sons[1] = orig n.sons[genericParamsPos] = result @@ -1212,7 +1258,7 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode = result = semProcAnnotation(c, n, lambdaPragmas) if result != nil: return result result = n - checkSonsLen(n, bodyPos + 1) + checkSonsLen(n, bodyPos + 1, c.config) var s: PSym if n[namePos].kind != nkSym: s = newSym(skProc, c.cache.idAnon, getCurrOwner(c), n.info) @@ -1229,9 +1275,6 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode = gp = newNodeI(nkGenericParams, n.info) if n.sons[paramsPos].kind != nkEmpty: - #if n.kind == nkDo and not experimentalMode(c): - # localError(n.sons[paramsPos].info, - # "use the {.experimental.} pragma to enable 'do' with parameters") semParamList(c, n.sons[paramsPos], gp, s) # paramsTypeCheck(c, s.typ) if sonsLen(gp) > 0 and n.sons[genericParamsPos].kind == nkEmpty: @@ -1241,10 +1284,10 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode = s.typ = newProcType(c, n.info) if n.sons[pragmasPos].kind != nkEmpty: pragma(c, s, n.sons[pragmasPos], lambdaPragmas) - s.options = gOptions + s.options = c.config.options if n.sons[bodyPos].kind != nkEmpty: if sfImportc in s.flags: - localError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s) + localError(c.config, n.sons[bodyPos].info, errImplOfXNotAllowed % s.name.s) #if efDetermineType notin flags: # XXX not good enough; see tnamedparamanonproc.nim if gp.len == 0 or (gp.len == 1 and tfRetType in gp[0].typ.flags): @@ -1252,13 +1295,13 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode = addResult(c, s.typ.sons[0], n.info, skProc) addResultNode(c, n) let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos])) - n.sons[bodyPos] = transformBody(c.module, semBody, s) + n.sons[bodyPos] = transformBody(c.graph, c.module, semBody, s) popProcCon(c) elif efOperand notin flags: - localError(n.info, errGenericLambdaNotAllowed) + localError(c.config, n.info, errGenericLambdaNotAllowed) sideEffectsCheck(c, s) else: - localError(n.info, errImplOfXexpected, s.name.s) + localError(c.config, n.info, errImplOfXexpected % s.name.s) closeScope(c) # close scope for parameters popOwner(c) result.typ = s.typ @@ -1275,7 +1318,7 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode = result = n s.ast = result n.sons[namePos].sym = s - n.sons[genericParamsPos] = emptyNode + n.sons[genericParamsPos] = c.graph.emptyNode # for LL we need to avoid wrong aliasing let params = copyTree n.typ.n n.sons[paramsPos] = params @@ -1283,7 +1326,7 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode = for i in 1..<params.len: if params[i].typ.kind in {tyTypeDesc, tyGenericParam, tyFromExpr}+tyTypeClasses: - localError(params[i].info, "cannot infer type of parameter: " & + localError(c.config, params[i].info, "cannot infer type of parameter: " & params[i].sym.name.s) #params[i].sym.owner = s openScope(c) @@ -1293,7 +1336,7 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode = addResult(c, n.typ.sons[0], n.info, skProc) addResultNode(c, n) let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos])) - n.sons[bodyPos] = transformBody(c.module, semBody, s) + n.sons[bodyPos] = transformBody(c.graph, c.module, semBody, s) popProcCon(c) popOwner(c) closeScope(c) @@ -1325,27 +1368,26 @@ proc maybeAddResult(c: PContext, s: PSym, n: PNode) = proc semOverride(c: PContext, s: PSym, n: PNode) = case s.name.s.normalize - of "destroy", "=destroy": - if newDestructors: - let t = s.typ - var noError = false - if t.len == 2 and t.sons[0] == nil and t.sons[1].kind == tyVar: - var obj = t.sons[1].sons[0] - while true: - incl(obj.flags, tfHasAsgn) - if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.lastSon - elif obj.kind == tyGenericInvocation: obj = obj.sons[0] - else: break - if obj.kind in {tyObject, tyDistinct}: - if obj.destructor.isNil: - obj.destructor = s - else: - localError(n.info, errGenerated, - "cannot bind another '" & s.name.s & "' to: " & typeToString(obj)) - noError = true - if not noError and sfSystemModule notin s.owner.flags: - localError(n.info, errGenerated, - "signature for '" & s.name.s & "' must be proc[T: object](x: var T)") + of "=destroy": + let t = s.typ + var noError = false + if t.len == 2 and t.sons[0] == nil and t.sons[1].kind == tyVar: + var obj = t.sons[1].sons[0] + while true: + incl(obj.flags, tfHasAsgn) + if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.lastSon + elif obj.kind == tyGenericInvocation: obj = obj.sons[0] + else: break + if obj.kind in {tyObject, tyDistinct, tySequence, tyString}: + if obj.destructor.isNil: + obj.destructor = s + else: + localError(c.config, n.info, errGenerated, + "cannot bind another '" & s.name.s & "' to: " & typeToString(obj)) + noError = true + if not noError and sfSystemModule notin s.owner.flags: + localError(c.config, n.info, errGenerated, + "signature for '" & s.name.s & "' must be proc[T: object](x: var T)") incl(s.flags, sfUsed) of "deepcopy", "=deepcopy": if s.typ.len == 2 and @@ -1358,16 +1400,16 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = if t.kind == tyGenericBody: t = t.lastSon elif t.kind == tyGenericInvocation: t = t.sons[0] else: break - if t.kind in {tyObject, tyDistinct, tyEnum}: + if t.kind in {tyObject, tyDistinct, tyEnum, tySequence, tyString}: if t.deepCopy.isNil: t.deepCopy = s else: - localError(n.info, errGenerated, + localError(c.config, n.info, errGenerated, "cannot bind another 'deepCopy' to: " & typeToString(t)) else: - localError(n.info, errGenerated, + localError(c.config, n.info, errGenerated, "cannot bind 'deepCopy' to: " & typeToString(t)) else: - localError(n.info, errGenerated, + localError(c.config, n.info, errGenerated, "signature for 'deepCopy' must be proc[T: ptr|ref](x: T): T") incl(s.flags, sfUsed) of "=", "=sink": @@ -1387,30 +1429,30 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = elif objB.kind in {tyGenericInvocation, tyGenericInst}: objB = objB.sons[0] else: break - if obj.kind in {tyObject, tyDistinct} and sameType(obj, objB): + if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, objB): let opr = if s.name.s == "=": addr(obj.assignment) else: addr(obj.sink) if opr[].isNil: opr[] = s else: - localError(n.info, errGenerated, + localError(c.config, n.info, errGenerated, "cannot bind another '" & s.name.s & "' to: " & typeToString(obj)) return if sfSystemModule notin s.owner.flags: - localError(n.info, errGenerated, + localError(c.config, n.info, errGenerated, "signature for '" & s.name.s & "' must be proc[T: object](x: var T; y: T)") else: if sfOverriden in s.flags: - localError(n.info, errGenerated, + localError(c.config, n.info, errGenerated, "'destroy' or 'deepCopy' expected for 'override'") -proc cursorInProcAux(n: PNode): bool = - if inCheckpoint(n.info) != cpNone: return true +proc cursorInProcAux(conf: ConfigRef; n: PNode): bool = + if inCheckpoint(n.info, conf.m.trackPos) != cpNone: return true for i in 0..<n.safeLen: - if cursorInProcAux(n[i]): return true + if cursorInProcAux(conf, n[i]): return true -proc cursorInProc(n: PNode): bool = - if n.info.fileIndex == gTrackPos.fileIndex: - result = cursorInProcAux(n) +proc cursorInProc(conf: ConfigRef; n: PNode): bool = + if n.info.fileIndex == conf.m.trackPos.fileIndex: + result = cursorInProcAux(conf, n) type TProcCompilationSteps = enum @@ -1443,7 +1485,7 @@ proc semMethodPrototype(c: PContext; s: PSym; n: PNode) = foundObj = true x.methods.safeAdd((col,s)) if not foundObj: - message(n.info, warnDeprecated, "generic method not attachable to object type") + message(c.config, n.info, warnDeprecated, "generic method not attachable to object type") else: # why check for the body? bug #2400 has none. Checking for sfForward makes # no sense either. @@ -1451,7 +1493,7 @@ proc semMethodPrototype(c: PContext; s: PSym; n: PNode) = if hasObjParam(s): methodDef(c.graph, s, fromCache=false) else: - localError(n.info, errXNeedsParamObjectType, "method") + localError(c.config, n.info, "'method' needs a parameter that has an object type") proc semProcAux(c: PContext, n: PNode, kind: TSymKind, validPragmas: TSpecialWords, @@ -1459,7 +1501,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, result = semProcAnnotation(c, n, validPragmas) if result != nil: return result result = n - checkSonsLen(n, bodyPos + 1) + checkSonsLen(n, bodyPos + 1, c.config) var s: PSym var typeIsDetermined = false var isAnon = false @@ -1489,6 +1531,8 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, s.ast = n #s.scope = c.currentScope + s.options = c.config.options + # before compiling the proc body, set as current the scope # where the proc was declared let oldScope = c.currentScope @@ -1516,7 +1560,9 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, n.sons[patternPos] = semPattern(c, n.sons[patternPos]) if s.kind == skIterator: s.typ.flags.incl(tfIterator) - + elif s.kind == skFunc: + incl(s.flags, sfNoSideEffect) + incl(s.typ.flags, tfNoSideEffect) var proto = searchForProc(c, oldScope, s) if proto == nil or isAnon: if s.kind == skIterator: @@ -1546,10 +1592,10 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, # XXX This needs more checks eventually, for example that external # linking names do agree: if proto.typ.callConv != s.typ.callConv or proto.typ.flags < s.typ.flags: - localError(n.sons[pragmasPos].info, errPragmaOnlyInHeaderOfProcX, - "'" & proto.name.s & "' from " & $proto.info) - if sfForward notin proto.flags: - wrongRedefinition(n.info, proto.name.s) + localError(c.config, n.sons[pragmasPos].info, errPragmaOnlyInHeaderOfProcX % + ("'" & proto.name.s & "' from " & c.config$proto.info)) + if sfForward notin proto.flags and proto.magic == mNone: + wrongRedefinition(c, n.info, proto.name.s) excl(proto.flags, sfForward) closeScope(c) # close scope with wrong parameter symbols openScope(c) # open scope for old (correct) parameter symbols @@ -1558,35 +1604,38 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, addParams(c, proto.typ.n, proto.kind) proto.info = s.info # more accurate line information s.typ = proto.typ + proto.options = s.options s = proto n.sons[genericParamsPos] = proto.ast.sons[genericParamsPos] n.sons[paramsPos] = proto.ast.sons[paramsPos] n.sons[pragmasPos] = proto.ast.sons[pragmasPos] - if n.sons[namePos].kind != nkSym: internalError(n.info, "semProcAux") + if n.sons[namePos].kind != nkSym: internalError(c.config, n.info, "semProcAux") n.sons[namePos].sym = proto - if importantComments() and not isNil(proto.ast.comment): + if importantComments(c.config) and proto.ast.comment.len > 0: n.comment = proto.ast.comment proto.ast = n # needed for code generation popOwner(c) pushOwner(c, s) - s.options = gOptions + if sfOverriden in s.flags or s.name.s[0] == '=': semOverride(c, s, n) if s.name.s[0] in {'.', '('}: - if s.name.s in [".", ".()", ".="] and not experimentalMode(c) and not newDestructors: - message(n.info, warnDeprecated, "overloaded '.' and '()' operators are now .experimental; " & s.name.s) - elif s.name.s == "()" and not experimentalMode(c): - message(n.info, warnDeprecated, "overloaded '()' operators are now .experimental; " & s.name.s) + if s.name.s in [".", ".()", ".="] and {destructor, dotOperators} * c.features == {}: + localError(c.config, n.info, "the overloaded " & s.name.s & + " operator has to be enabled with {.experimental: \"dotOperators\".}") + elif s.name.s == "()" and callOperator notin c.features: + localError(c.config, n.info, "the overloaded " & s.name.s & + " operator has to be enabled with {.experimental: \"callOperator\".}") if n.sons[bodyPos].kind != nkEmpty: # for DLL generation it is annoying to check for sfImportc! if sfBorrow in s.flags: - localError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s) + localError(c.config, n.sons[bodyPos].info, errImplOfXNotAllowed % s.name.s) let usePseudoGenerics = kind in {skMacro, skTemplate} # Macros and Templates can have generic parameters, but they are # only used for overload resolution (there is no instantiation of # the symbol, so we must process the body now) - if not usePseudoGenerics and gIdeCmd in {ideSug, ideCon} and not - cursorInProc(n.sons[bodyPos]): + if not usePseudoGenerics and c.config.ideCmd in {ideSug, ideCon} and not + cursorInProc(c.config, n.sons[bodyPos]): discard "speed up nimsuggest" if s.kind == skMethod: semMethodPrototype(c, s, n) else: @@ -1603,23 +1652,24 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos])) # unfortunately we cannot skip this step when in 'system.compiles' # context as it may even be evaluated in 'system.compiles': - n.sons[bodyPos] = transformBody(c.module, semBody, s) + n.sons[bodyPos] = transformBody(c.graph, c.module, semBody, s) else: if s.typ.sons[0] != nil and kind != skIterator: - addDecl(c, newSym(skUnknown, getIdent"result", nil, n.info)) + addDecl(c, newSym(skUnknown, getIdent(c.cache, "result"), nil, n.info)) openScope(c) n.sons[bodyPos] = semGenericStmt(c, n.sons[bodyPos]) closeScope(c) - fixupInstantiatedSymbols(c, s) + if s.magic == mNone: + fixupInstantiatedSymbols(c, s) if s.kind == skMethod: semMethodPrototype(c, s, n) if sfImportc in s.flags: # so we just ignore the body after semantic checking for importc: - n.sons[bodyPos] = ast.emptyNode + n.sons[bodyPos] = c.graph.emptyNode popProcCon(c) else: if s.kind == skMethod: semMethodPrototype(c, s, n) - if proto != nil: localError(n.info, errImplOfXexpected, proto.name.s) + if proto != nil: localError(c.config, n.info, errImplOfXexpected % proto.name.s) if {sfImportc, sfBorrow} * s.flags == {} and s.magic == mNone: incl(s.flags, sfForward) elif sfBorrow in s.flags: semBorrow(c, n, s) @@ -1634,7 +1684,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, result.typ = s.typ if isTopLevel(c) and s.kind != skIterator and s.typ.callConv == ccClosure: - localError(s.info, "'.closure' calling convention for top level routines is invalid") + localError(c.config, s.info, "'.closure' calling convention for top level routines is invalid") proc determineType(c: PContext, s: PSym) = if s.typ != nil: return @@ -1657,9 +1707,9 @@ proc semIterator(c: PContext, n: PNode): PNode = var s = result.sons[namePos].sym var t = s.typ if t.sons[0] == nil and s.typ.callConv != ccClosure: - localError(n.info, errXNeedsReturnType, "iterator") + localError(c.config, n.info, "iterator needs a return type") if isAnon and s.typ.callConv == ccInline: - localError(n.info, "inline iterators are not first-class / cannot be assigned to variables") + localError(c.config, n.info, "inline iterators are not first-class / cannot be assigned to variables") # iterators are either 'inline' or 'closure'; for backwards compatibility, # we require first class iterators to be marked with 'closure' explicitly # -- at least for 0.9.2. @@ -1667,13 +1717,8 @@ proc semIterator(c: PContext, n: PNode): PNode = incl(s.typ.flags, tfCapturesEnv) else: s.typ.callConv = ccInline - when false: - if s.typ.callConv != ccInline: - s.typ.callConv = ccClosure - # and they always at least use the 'env' for the state field: - incl(s.typ.flags, tfCapturesEnv) if n.sons[bodyPos].kind == nkEmpty and s.magic == mNone: - localError(n.info, errImplOfXexpected, s.name.s) + localError(c.config, n.info, errImplOfXexpected % s.name.s) proc semProc(c: PContext, n: PNode): PNode = result = semProcAux(c, n, skProc, procPragmas) @@ -1682,7 +1727,7 @@ proc semFunc(c: PContext, n: PNode): PNode = result = semProcAux(c, n, skFunc, procPragmas) proc semMethod(c: PContext, n: PNode): PNode = - if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "method") + if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "method") result = semProcAux(c, n, skMethod, methodPragmas) # macros can transform converters to nothing: if namePos >= result.safeLen: return result @@ -1699,12 +1744,12 @@ proc semMethod(c: PContext, n: PNode): PNode = let ret = s.typ.sons[0] disp.typ.sons[0] = ret if disp.ast[resultPos].kind == nkSym: - if isEmptyType(ret): disp.ast.sons[resultPos] = emptyNode + if isEmptyType(ret): disp.ast.sons[resultPos] = c.graph.emptyNode else: disp.ast[resultPos].sym.typ = ret proc semConverterDef(c: PContext, n: PNode): PNode = - if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "converter") - checkSonsLen(n, bodyPos + 1) + if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "converter") + checkSonsLen(n, bodyPos + 1, c.config) result = semProcAux(c, n, skConverter, converterPragmas) # macros can transform converters to nothing: if namePos >= result.safeLen: return result @@ -1714,12 +1759,12 @@ proc semConverterDef(c: PContext, n: PNode): PNode = if result.kind != nkConverterDef: return var s = result.sons[namePos].sym var t = s.typ - if t.sons[0] == nil: localError(n.info, errXNeedsReturnType, "converter") - if sonsLen(t) != 2: localError(n.info, errXRequiresOneArgument, "converter") + if t.sons[0] == nil: localError(c.config, n.info, errXNeedsReturnType % "converter") + if sonsLen(t) != 2: localError(c.config, n.info, "a converter takes exactly one argument") addConverter(c, s) proc semMacroDef(c: PContext, n: PNode): PNode = - checkSonsLen(n, bodyPos + 1) + checkSonsLen(n, bodyPos + 1, c.config) result = semProcAux(c, n, skMacro, macroPragmas) # macros can transform macros to nothing: if namePos >= result.safeLen: return result @@ -1734,21 +1779,21 @@ proc semMacroDef(c: PContext, n: PNode): PNode = let param = t.n.sons[i].sym if param.typ.kind != tyExpr: allUntyped = false if allUntyped: incl(s.flags, sfAllUntyped) - if t.sons[0] == nil: localError(n.info, errXNeedsReturnType, "macro") + if t.sons[0] == nil: localError(c.config, n.info, "macro needs a return type") if n.sons[bodyPos].kind == nkEmpty: - localError(n.info, errImplOfXexpected, s.name.s) + localError(c.config, n.info, errImplOfXexpected % s.name.s) proc evalInclude(c: PContext, n: PNode): PNode = result = newNodeI(nkStmtList, n.info) addSon(result, n) for i in countup(0, sonsLen(n) - 1): - var f = checkModuleName(n.sons[i]) + var f = checkModuleName(c.config, n.sons[i]) if f != InvalidFileIDX: - if containsOrIncl(c.includedFiles, f): - localError(n.info, errRecursiveDependencyX, f.toFilename) + if containsOrIncl(c.includedFiles, f.int): + localError(c.config, n.info, errRecursiveDependencyX % toFilename(c.config, f)) else: - addSon(result, semStmt(c, gIncludeFile(c.graph, c.module, f, c.cache))) - excl(c.includedFiles, f) + addSon(result, semStmt(c, c.graph.includeFileCallback(c.graph, c.module, f))) + excl(c.includedFiles, f.int) proc setLine(n: PNode, info: TLineInfo) = for i in 0 ..< safeLen(n): setLine(n.sons[i], info) @@ -1772,19 +1817,19 @@ proc semPragmaBlock(c: PContext, n: PNode): PNode = proc semStaticStmt(c: PContext, n: PNode): PNode = #echo "semStaticStmt" #writeStackTrace() + inc c.inStaticContext + openScope(c) let a = semStmt(c, n.sons[0]) + closeScope(c) + dec c.inStaticContext n.sons[0] = a - evalStaticStmt(c.module, c.cache, a, c.p.owner) - result = newNodeI(nkDiscardStmt, n.info, 1) - result.sons[0] = emptyNode + evalStaticStmt(c.module, c.graph, a, c.p.owner) when false: - result = evalStaticStmt(c.module, a, c.p.owner) - if result.isNil: - LocalError(n.info, errCannotInterpretNodeX, renderTree(n)) - result = emptyNode - elif result.kind == nkEmpty: - result = newNodeI(nkDiscardStmt, n.info, 1) - result.sons[0] = emptyNode + # for incremental replays, keep the AST as required for replays: + result = n + else: + result = newNodeI(nkDiscardStmt, n.info, 1) + result.sons[0] = c.graph.emptyNode proc usesResult(n: PNode): bool = # nkStmtList(expr) properly propagates the void context, @@ -1803,7 +1848,7 @@ proc inferConceptStaticParam(c: PContext, inferred, n: PNode) = var typ = inferred.typ let res = semConstExpr(c, n) if not sameType(res.typ, typ.base): - localError(n.info, + localError(c.config, n.info, "cannot infer the concept parameter '%s', due to a type mismatch. " & "attempt to equate '%s' and '%s'.", [inferred.renderTree, $res.typ, $typ.base]) @@ -1826,80 +1871,47 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = # nkNilLit, nkEmpty}: # dec last for i in countup(0, length - 1): - let k = n.sons[i].kind - case k - of nkFinally, nkExceptBranch: - # stand-alone finally and except blocks are - # transformed into regular try blocks: - # - # var f = fopen("somefile") | var f = fopen("somefile") - # finally: fclose(f) | try: - # ... | ... - # | finally: - # | fclose(f) - var deferPart: PNode - if k == nkDefer: - deferPart = newNodeI(nkFinally, n.sons[i].info) - deferPart.add n.sons[i].sons[0] - elif k == nkFinally: - message(n.info, warnDeprecated, - "use 'defer'; standalone 'finally'") - deferPart = n.sons[i] - else: - message(n.info, warnDeprecated, - "use an explicit 'try'; standalone 'except'") - deferPart = n.sons[i] - var tryStmt = newNodeI(nkTryStmt, n.sons[i].info) - var body = newNodeI(nkStmtList, n.sons[i].info) - if i < n.sonsLen - 1: - body.sons = n.sons[(i+1)..n.len-1] - tryStmt.addSon(body) - tryStmt.addSon(deferPart) - n.sons[i] = semTry(c, tryStmt) - n.sons.setLen(i+1) + var expr = semExpr(c, n.sons[i], flags) + n.sons[i] = expr + if c.matchedConcept != nil and expr.typ != nil and + (nfFromTemplate notin n.flags or i != last): + case expr.typ.kind + of tyBool: + if expr.kind == nkInfix and + expr[0].kind == nkSym and + expr[0].sym.name.s == "==": + if expr[1].typ.isUnresolvedStatic: + inferConceptStaticParam(c, expr[1], expr[2]) + continue + elif expr[2].typ.isUnresolvedStatic: + inferConceptStaticParam(c, expr[2], expr[1]) + continue + + let verdict = semConstExpr(c, n[i]) + if verdict.intVal == 0: + localError(c.config, result.info, "concept predicate failed") + of tyUnknown: continue + else: discard + if n.sons[i].typ == c.enforceVoidContext: #or usesResult(n.sons[i]): + voidContext = true + n.typ = c.enforceVoidContext + if i == last and (length == 1 or efWantValue in flags): n.typ = n.sons[i].typ - return + if not isEmptyType(n.typ): n.kind = nkStmtListExpr + elif i != last or voidContext: + discardCheck(c, n.sons[i]) else: - var expr = semExpr(c, n.sons[i], flags) - n.sons[i] = expr - if c.matchedConcept != nil and expr.typ != nil and - (nfFromTemplate notin n.flags or i != last): - case expr.typ.kind - of tyBool: - if expr.kind == nkInfix and - expr[0].kind == nkSym and - expr[0].sym.name.s == "==": - if expr[1].typ.isUnresolvedStatic: - inferConceptStaticParam(c, expr[1], expr[2]) - continue - elif expr[2].typ.isUnresolvedStatic: - inferConceptStaticParam(c, expr[2], expr[1]) - continue - - let verdict = semConstExpr(c, n[i]) - if verdict.intVal == 0: - localError(result.info, "concept predicate failed") - of tyUnknown: continue - else: discard - if n.sons[i].typ == enforceVoidContext: #or usesResult(n.sons[i]): - voidContext = true - n.typ = enforceVoidContext - if i == last and (length == 1 or efWantValue in flags): - n.typ = n.sons[i].typ - if not isEmptyType(n.typ): n.kind = nkStmtListExpr - elif i != last or voidContext: - discardCheck(c, n.sons[i]) - else: - n.typ = n.sons[i].typ - if not isEmptyType(n.typ): n.kind = nkStmtListExpr - if n.sons[i].kind in LastBlockStmts or - n.sons[i].kind in nkCallKinds and n.sons[i][0].kind == nkSym and sfNoReturn in n.sons[i][0].sym.flags: - for j in countup(i + 1, length - 1): - case n.sons[j].kind - of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkBlockExpr, - nkBlockStmt, nkState: discard - else: localError(n.sons[j].info, errStmtInvalidAfterReturn) - else: discard + n.typ = n.sons[i].typ + if not isEmptyType(n.typ): n.kind = nkStmtListExpr + if n.sons[i].kind in LastBlockStmts or + n.sons[i].kind in nkCallKinds and n.sons[i][0].kind == nkSym and + sfNoReturn in n.sons[i][0].sym.flags: + for j in countup(i + 1, length - 1): + case n.sons[j].kind + of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkBlockExpr, + nkBlockStmt, nkState: discard + else: localError(c.config, n.sons[j].info, "unreachable statement after 'return'") + else: discard if result.len == 1 and # concept bodies should be preserved as a stmt list: @@ -1915,15 +1927,6 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = not (result.comment[0] == '#' and result.comment[1] == '#'): # it is an old-style comment statement: we replace it with 'discard ""': prettybase.replaceComment(result.info) - when false: - # a statement list (s; e) has the type 'e': - if result.kind == nkStmtList and result.len > 0: - var lastStmt = lastSon(result) - if lastStmt.kind != nkNilLit and not implicitlyDiscardable(lastStmt): - result.typ = lastStmt.typ - #localError(lastStmt.info, errGenerated, - # "Last expression must be explicitly returned if it " & - # "is discardable or discarded") proc semStmt(c: PContext, n: PNode): PNode = # now: simply an alias: diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 454dadec0..396696422 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -26,6 +26,9 @@ discard """ a way to achieve lexical scoping at compile time. """ +const + errImplOfXNotAllowed = "implementation of '$1' is not allowed" + type TSymBinding = enum spNone, spGenSym, spInject @@ -60,7 +63,7 @@ proc symChoice(c: PContext, n: PNode, s: PSym, r: TSymChoiceRule): PNode = # (s.kind notin routineKinds or s.magic != mNone): # for instance 'nextTry' is both in tables.nim and astalgo.nim ... result = newSymNode(s, n.info) - markUsed(n.info, s, c.graph.usageSym) + markUsed(c.config, n.info, s, c.graph.usageSym) else: # semantic checking requires a type; ``fitNode`` deals with it # appropriately @@ -91,20 +94,20 @@ proc semBindStmt(c: PContext, n: PNode, toBind: var IntSet): PNode = else: for x in items(sc): toBind.incl(x.sym.id) else: - illFormedAst(a) + illFormedAst(a, c.config) result = newNodeI(nkEmpty, n.info) proc semMixinStmt(c: PContext, n: PNode, toMixin: var IntSet): PNode = for i in 0 ..< n.len: - toMixin.incl(considerQuotedIdent(n.sons[i]).id) + toMixin.incl(considerQuotedIdent(c, n.sons[i]).id) result = newNodeI(nkEmpty, n.info) -proc replaceIdentBySym(n: var PNode, s: PNode) = +proc replaceIdentBySym(c: PContext; n: var PNode, s: PNode) = case n.kind - of nkPostfix: replaceIdentBySym(n.sons[1], s) - of nkPragmaExpr: replaceIdentBySym(n.sons[0], s) + of nkPostfix: replaceIdentBySym(c, n.sons[1], s) + of nkPragmaExpr: replaceIdentBySym(c, n.sons[0], s) of nkIdent, nkAccQuoted, nkSym: n = s - else: illFormedAst(n) + else: illFormedAst(n, c.config) type TemplCtx = object @@ -129,7 +132,7 @@ proc getIdentNode(c: var TemplCtx, n: PNode): PNode = result = newSymNode(s, n.info) of nkAccQuoted, nkSym: result = n else: - illFormedAst(n) + illFormedAst(n, c.c.config) result = n proc isTemplParam(c: TemplCtx, n: PNode): bool {.inline.} = @@ -163,7 +166,7 @@ proc onlyReplaceParams(c: var TemplCtx, n: PNode): PNode = result.sons[i] = onlyReplaceParams(c, n.sons[i]) proc newGenSym(kind: TSymKind, n: PNode, c: var TemplCtx): PSym = - result = newSym(kind, considerQuotedIdent(n), c.owner, n.info) + result = newSym(kind, considerQuotedIdent(c.c, n), c.owner, n.info) incl(result.flags, sfGenSym) incl(result.flags, sfShadowed) @@ -184,12 +187,12 @@ proc addLocalDecl(c: var TemplCtx, n: var PNode, k: TSymKind) = n = onlyReplaceParams(c, n) return else: - illFormedAst(x) + illFormedAst(x, c.c.config) let ident = getIdentNode(c, x) if not isTemplParam(c, ident): c.toInject.incl(x.ident.id) else: - replaceIdentBySym(n, ident) + replaceIdentBySym(c.c, n, ident) else: let ident = getIdentNode(c, n) if not isTemplParam(c, ident): @@ -203,17 +206,17 @@ proc addLocalDecl(c: var TemplCtx, n: var PNode, k: TSymKind) = # # We need to ensure that both 'a' produce the same gensym'ed symbol. # So we need only check the *current* scope. - let s = localSearchInScope(c.c, considerQuotedIdent ident) + let s = localSearchInScope(c.c, considerQuotedIdent(c.c, ident)) if s != nil and s.owner == c.owner and sfGenSym in s.flags: styleCheckUse(n.info, s) - replaceIdentBySym(n, newSymNode(s, n.info)) - else: + replaceIdentBySym(c.c, n, newSymNode(s, n.info)) + elif not (n.kind == nkSym and sfGenSym in n.sym.flags): let local = newGenSym(k, ident, c) addPrelimDecl(c.c, local) - styleCheckDef(n.info, local) - replaceIdentBySym(n, newSymNode(local, n.info)) + styleCheckDef(c.c.config, n.info, local) + replaceIdentBySym(c.c, n, newSymNode(local, n.info)) else: - replaceIdentBySym(n, ident) + replaceIdentBySym(c.c, n, ident) proc semTemplSymbol(c: PContext, n: PNode, s: PSym): PNode = incl(s.flags, sfUsed) @@ -249,7 +252,7 @@ proc semRoutineInTemplName(c: var TemplCtx, n: PNode): PNode = proc semRoutineInTemplBody(c: var TemplCtx, n: PNode, k: TSymKind): PNode = result = n - checkSonsLen(n, bodyPos + 1) + checkSonsLen(n, bodyPos + 1, c.c.config) # routines default to 'inject': if n.kind notin nkLambdaKinds and symBinding(n.sons[pragmasPos]) == spGenSym: let ident = getIdentNode(c, n.sons[namePos]) @@ -257,7 +260,7 @@ proc semRoutineInTemplBody(c: var TemplCtx, n: PNode, k: TSymKind): PNode = var s = newGenSym(k, ident, c) s.ast = n addPrelimDecl(c.c, s) - styleCheckDef(n.info, s) + styleCheckDef(c.c.config, n.info, s) n.sons[namePos] = newSymNode(s, n.sons[namePos].info) else: n.sons[namePos] = ident @@ -281,8 +284,8 @@ proc semTemplSomeDecl(c: var TemplCtx, n: PNode, symKind: TSymKind; start=0) = for i in countup(start, sonsLen(n) - 1): var a = n.sons[i] if a.kind == nkCommentStmt: continue - if (a.kind != nkIdentDefs) and (a.kind != nkVarTuple): illFormedAst(a) - checkMinSonsLen(a, 3) + if (a.kind != nkIdentDefs) and (a.kind != nkVarTuple): illFormedAst(a, c.c.config) + checkMinSonsLen(a, 3, c.c.config) var L = sonsLen(a) when defined(nimsuggest): inc c.c.inTypeContext @@ -302,7 +305,7 @@ proc semTemplBodySons(c: var TemplCtx, n: PNode): PNode = proc semTemplBody(c: var TemplCtx, n: PNode): PNode = result = n - semIdeForTemplateOrGenericCheck(n, c.cursorInBody) + semIdeForTemplateOrGenericCheck(c.c.config, n, c.cursorInBody) case n.kind of nkIdent: if n.ident.id in c.toInject: return n @@ -337,9 +340,8 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = for i in countup(0, sonsLen(n)-1): var it = n.sons[i] if it.len == 2: - when newScopeForIf: openScope(c) + openScope(c) it.sons[0] = semTemplBody(c, it.sons[0]) - when not newScopeForIf: openScope(c) it.sons[1] = semTemplBody(c, it.sons[1]) closeScope(c) else: @@ -354,7 +356,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = n.sons[0] = semTemplBody(c, n.sons[0]) for i in countup(1, sonsLen(n)-1): var a = n.sons[i] - checkMinSonsLen(a, 1) + checkMinSonsLen(a, 1, c.c.config) var L = sonsLen(a) for j in countup(0, L-2): a.sons[j] = semTemplBody(c, a.sons[j]) @@ -371,7 +373,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = closeScope(c) closeScope(c) of nkBlockStmt, nkBlockExpr, nkBlockType: - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.c.config) openScope(c) if n.sons[0].kind != nkEmpty: addLocalDecl(c, n.sons[0], skLabel) @@ -379,16 +381,16 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = # labels are always 'gensym'ed: let s = newGenSym(skLabel, n.sons[0], c) addPrelimDecl(c.c, s) - styleCheckDef(s) + styleCheckDef(c.c.config, s) n.sons[0] = newSymNode(s, n.sons[0].info) n.sons[1] = semTemplBody(c, n.sons[1]) closeScope(c) of nkTryStmt: - checkMinSonsLen(n, 2) + checkMinSonsLen(n, 2, c.c.config) n.sons[0] = semTemplBodyScope(c, n.sons[0]) for i in countup(1, sonsLen(n)-1): var a = n.sons[i] - checkMinSonsLen(a, 1) + checkMinSonsLen(a, 1, c.c.config) var L = sonsLen(a) openScope(c) for j in countup(0, L-2): @@ -402,15 +404,15 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = of nkVarSection: semTemplSomeDecl(c, n, skVar) of nkLetSection: semTemplSomeDecl(c, n, skLet) of nkFormalParams: - checkMinSonsLen(n, 1) + checkMinSonsLen(n, 1, c.c.config) n.sons[0] = semTemplBody(c, n.sons[0]) semTemplSomeDecl(c, n, skParam, 1) of nkConstSection: for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] if a.kind == nkCommentStmt: continue - if (a.kind != nkConstDef): illFormedAst(a) - checkSonsLen(a, 3) + if (a.kind != nkConstDef): illFormedAst(a, c.c.config) + checkSonsLen(a, 3, c.c.config) addLocalDecl(c, a.sons[0], skConst) a.sons[1] = semTemplBody(c, a.sons[1]) a.sons[2] = semTemplBody(c, a.sons[2]) @@ -418,14 +420,14 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] if a.kind == nkCommentStmt: continue - if (a.kind != nkTypeDef): illFormedAst(a) - checkSonsLen(a, 3) + if (a.kind != nkTypeDef): illFormedAst(a, c.c.config) + checkSonsLen(a, 3, c.c.config) addLocalDecl(c, a.sons[0], skType) for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] if a.kind == nkCommentStmt: continue - if (a.kind != nkTypeDef): illFormedAst(a) - checkSonsLen(a, 3) + if (a.kind != nkTypeDef): illFormedAst(a, c.c.config) + checkSonsLen(a, 3, c.c.config) if a.sons[1].kind != nkEmpty: openScope(c) a.sons[1] = semTemplBody(c, a.sons[1]) @@ -457,18 +459,18 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = x.sons[1] = semTemplBody(c, x.sons[1]) of nkBracketExpr: result = newNodeI(nkCall, n.info) - result.add newIdentNode(getIdent("[]"), n.info) + result.add newIdentNode(getIdent(c.c.cache, "[]"), n.info) for i in 0 ..< n.len: result.add(n[i]) let n0 = semTemplBody(c, n.sons[0]) withBracketExpr c, n0: result = semTemplBodySons(c, result) of nkCurlyExpr: result = newNodeI(nkCall, n.info) - result.add newIdentNode(getIdent("{}"), n.info) + result.add newIdentNode(getIdent(c.c.cache, "{}"), n.info) for i in 0 ..< n.len: result.add(n[i]) result = semTemplBodySons(c, result) of nkAsgn, nkFastAsgn: - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.c.config) let a = n.sons[0] let b = n.sons[1] @@ -476,7 +478,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = case k of nkBracketExpr: result = newNodeI(nkCall, n.info) - result.add newIdentNode(getIdent("[]="), n.info) + result.add newIdentNode(getIdent(c.c.cache, "[]="), n.info) for i in 0 ..< a.len: result.add(a[i]) result.add(b) let a0 = semTemplBody(c, a.sons[0]) @@ -484,7 +486,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = result = semTemplBodySons(c, result) of nkCurlyExpr: result = newNodeI(nkCall, n.info) - result.add newIdentNode(getIdent("{}="), n.info) + result.add newIdentNode(getIdent(c.c.cache, "{}="), n.info) for i in 0 ..< a.len: result.add(a[i]) result.add(b) result = semTemplBodySons(c, result) @@ -515,7 +517,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = proc semTemplBodyDirty(c: var TemplCtx, n: PNode): PNode = result = n - semIdeForTemplateOrGenericCheck(n, c.cursorInBody) + semIdeForTemplateOrGenericCheck(c.c.config, n, c.cursorInBody) case n.kind of nkIdent: let s = qualifiedLookUp(c.c, n, {}) @@ -548,7 +550,7 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = incl(s.flags, sfGlobal) else: s = semIdentVis(c, skTemplate, n.sons[0], {}) - styleCheckDef(s) + styleCheckDef(c.config, s) # check parameter list: #s.scope = c.currentScope pushOwner(c, s) @@ -610,9 +612,9 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = result = n if sfCustomPragma in s.flags: if n.sons[bodyPos].kind != nkEmpty: - localError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s) + localError(c.config, n.sons[bodyPos].info, errImplOfXNotAllowed % s.name.s) elif n.sons[bodyPos].kind == nkEmpty: - localError(n.info, errImplOfXexpected, s.name.s) + localError(c.config, n.info, "implementation of '$1' expected" % s.name.s) var proto = searchForProc(c, c.currentScope, s) if proto == nil: addInterfaceOverloadableSymAt(c, c.currentScope, s) @@ -655,7 +657,7 @@ proc semPatternBody(c: var TemplCtx, n: PNode): PNode = if s != nil and s.owner == c.owner and s.kind == skParam: result = newParam(c, n, s) else: - localError(n.info, errInvalidExpression) + localError(c.c.config, n.info, "invalid expression") result = n proc stupidStmtListExpr(n: PNode): bool = @@ -675,7 +677,7 @@ proc semPatternBody(c: var TemplCtx, n: PNode): PNode = # we support '(pattern){x}' to bind a subpattern to a parameter 'x'; # '(pattern){|x}' does the same but the matches will be gathered in 'x' if n.len != 2: - localError(n.info, errInvalidExpression) + localError(c.c.config, n.info, "invalid expression") elif n.sons[1].kind == nkIdent: n.sons[0] = semPatternBody(c, n.sons[0]) n.sons[1] = expectParam(c, n.sons[1]) @@ -685,9 +687,9 @@ proc semPatternBody(c: var TemplCtx, n: PNode): PNode = n.sons[0] = semPatternBody(c, n.sons[0]) n.sons[1].sons[1] = expectParam(c, n.sons[1].sons[1]) else: - localError(n.info, errInvalidExpression) + localError(c.c.config, n.info, "invalid expression") else: - localError(n.info, errInvalidExpression) + localError(c.c.config, n.info, "invalid expression") of nkStmtList, nkStmtListExpr: if stupidStmtListExpr(n): result = semPatternBody(c, n.lastSon) @@ -759,5 +761,5 @@ proc semPattern(c: PContext, n: PNode): PNode = if result.len == 1: result = result.sons[0] elif result.len == 0: - localError(n.info, errInvalidExpression) + localError(c.config, n.info, "a pattern cannot be empty") closeScope(c) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 50c2e287e..86f3a17ab 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -10,6 +10,40 @@ # this module does the semantic checking of type declarations # included from sem.nim +const + errStringOrIdentNodeExpected = "string or ident node expected" + errStringLiteralExpected = "string literal expected" + errIntLiteralExpected = "integer literal expected" + errWrongNumberOfVariables = "wrong number of variables" + errInvalidOrderInEnumX = "invalid order in enum '$1'" + errOrdinalTypeExpected = "ordinal type expected" + errSetTooBig = "set is too large" + errBaseTypeMustBeOrdinal = "base type of a set must be an ordinal" + errInheritanceOnlyWithNonFinalObjects = "inheritance only works with non-final objects" + errXExpectsOneTypeParam = "'$1' expects one type parameter" + errArrayExpectsTwoTypeParams = "array expects two type parameters" + errInvalidVisibilityX = "invalid visibility: '$1'" + errInitHereNotAllowed = "initialization not allowed here" + errXCannotBeAssignedTo = "'$1' cannot be assigned to" + errIteratorNotAllowed = "iterators can only be defined at the module's top level" + errXNeedsReturnType = "$1 needs a return type" + errNoReturnTypeDeclared = "no return type declared" + errTIsNotAConcreteType = "'$1' is not a concrete type" + errTypeExpected = "type expected" + errXOnlyAtModuleScope = "'$1' is only allowed at top level" + errDuplicateCaseLabel = "duplicate case label" + errMacroBodyDependsOnGenericTypes = "the macro body cannot be compiled, " & + "because the parameter '$1' has a generic type" + errIllegalRecursionInTypeX = "illegal recursion in type '$1'" + errNoGenericParamsAllowedForX = "no generic parameters allowed for $1" + errInOutFlagNotExtern = "the '$1' modifier can be used only with imported types" + +const + mStaticTy = {mStatic} + mTypeTy = {mType, mTypeOf} + # XXX: This should be needed only temporarily until the C + # sources are rebuilt + proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext): PType = if prev == nil: result = newTypeS(kind, c) @@ -34,12 +68,12 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType = base = nil result = newOrPrevType(tyEnum, prev, c) result.n = newNodeI(nkEnumTy, n.info) - checkMinSonsLen(n, 1) + checkMinSonsLen(n, 1, c.config) if n.sons[0].kind != nkEmpty: base = semTypeNode(c, n.sons[0].sons[0], nil) if base.kind != tyEnum: - localError(n.sons[0].info, errInheritanceOnlyWithEnums) - counter = lastOrd(base) + 1 + localError(c.config, n.sons[0].info, "inheritance only works with an enum") + counter = lastOrd(c.config, base) + 1 rawAddSon(result, base) let isPure = result.sym != nil and sfPure in result.sym.flags var symbols: TStrTable @@ -58,9 +92,9 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType = if skipTypes(strVal.typ, abstractInst).kind in {tyString, tyCString}: x = getOrdValue(v.sons[0]) # first tuple part is the ordinal else: - localError(strVal.info, errStringLiteralExpected) + localError(c.config, strVal.info, errStringLiteralExpected) else: - localError(v.info, errWrongNumberOfVariables) + localError(c.config, v.info, errWrongNumberOfVariables) of tyString, tyCString: strVal = v x = counter @@ -69,7 +103,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType = if i != 1: if x != counter: incl(result.flags, tfEnumHasHoles) if x < counter: - localError(n.sons[i].info, errInvalidOrderInEnumX, e.name.s) + localError(c.config, n.sons[i].info, errInvalidOrderInEnumX % e.name.s) x = counter e.ast = strVal # might be nil counter = x @@ -78,7 +112,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType = of nkIdent, nkAccQuoted: e = newSymS(skEnumField, n.sons[i], c) else: - illFormedAst(n[i]) + illFormedAst(n[i], c.config) e.typ = result e.position = int(counter) if e.position == 0: hasNull = true @@ -87,12 +121,12 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType = incl(e.flags, sfExported) if not isPure: strTableAdd(c.module.tab, e) addSon(result.n, newSymNode(e)) - styleCheckDef(e) + styleCheckDef(c.config, e) if sfGenSym notin e.flags: if not isPure: addDecl(c, e) else: importPureEnumField(c, e) if isPure and strTableIncl(symbols, e): - wrongRedefinition(e.info, e.name.s) + wrongRedefinition(c, e.info, e.name.s) inc(counter) if not hasNull: incl(result.flags, tfNeedsInit) @@ -103,35 +137,38 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType = addSonSkipIntLit(result, base) if base.kind in {tyGenericInst, tyAlias, tySink}: base = lastSon(base) if base.kind != tyGenericParam: - if not isOrdinalType(base): - localError(n.info, errOrdinalTypeExpected) - elif lengthOrd(base) > MaxSetElements: - localError(n.info, errSetTooBig) + if not isOrdinalType(base, allowEnumWithHoles = true): + localError(c.config, n.info, errOrdinalTypeExpected) + elif lengthOrd(c.config, base) > MaxSetElements: + localError(c.config, n.info, errSetTooBig) else: - localError(n.info, errXExpectsOneTypeParam, "set") + localError(c.config, n.info, errXExpectsOneTypeParam % "set") addSonSkipIntLit(result, errorType(c)) -proc semContainer(c: PContext, n: PNode, kind: TTypeKind, kindStr: string, - prev: PType): PType = - result = newOrPrevType(kind, prev, c) +proc semContainerArg(c: PContext; n: PNode, kindStr: string; result: PType) = if sonsLen(n) == 2: var base = semTypeNode(c, n.sons[1], nil) if base.kind == tyVoid: - localError(n.info, errTIsNotAConcreteType, typeToString(base)) + localError(c.config, n.info, errTIsNotAConcreteType % typeToString(base)) addSonSkipIntLit(result, base) else: - localError(n.info, errXExpectsOneTypeParam, kindStr) + localError(c.config, n.info, errXExpectsOneTypeParam % kindStr) addSonSkipIntLit(result, errorType(c)) +proc semContainer(c: PContext, n: PNode, kind: TTypeKind, kindStr: string, + prev: PType): PType = + result = newOrPrevType(kind, prev, c) + semContainerArg(c, n, kindStr, result) + proc semVarargs(c: PContext, n: PNode, prev: PType): PType = result = newOrPrevType(tyVarargs, prev, c) if sonsLen(n) == 2 or sonsLen(n) == 3: var base = semTypeNode(c, n.sons[1], nil) addSonSkipIntLit(result, base) if sonsLen(n) == 3: - result.n = newIdentNode(considerQuotedIdent(n.sons[2]), n.sons[2].info) + result.n = newIdentNode(considerQuotedIdent(c, n.sons[2]), n.sons[2].info) else: - localError(n.info, errXExpectsOneTypeParam, "varargs") + localError(c.config, n.info, errXExpectsOneTypeParam % "varargs") addSonSkipIntLit(result, errorType(c)) proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType = @@ -140,10 +177,13 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType = else: let isCall = int ord(n.kind in nkCallKinds+{nkBracketExpr}) let n = if n[0].kind == nkBracket: n[0] else: n - checkMinSonsLen(n, 1) + checkMinSonsLen(n, 1, c.config) var t = semTypeNode(c, n.lastSon, nil) if t.kind == tyTypeDesc and tfUnresolved notin t.flags: t = t.base + if t.kind == tyVoid: + const kindToStr: array[tyPtr..tyRef, string] = ["ptr", "ref"] + localError(c.config, n.info, "type '$1 void' is not allowed" % kindToStr[kind]) result = newOrPrevType(kind, prev, c) var isNilable = false # check every except the last is an object: @@ -155,7 +195,9 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType = let region = semTypeNode(c, ni, nil) if region.skipTypes({tyGenericInst, tyAlias, tySink}).kind notin { tyError, tyObject}: - message n[i].info, errGenerated, "region needs to be an object type" + message c.config, n[i].info, errGenerated, "region needs to be an object type" + else: + message(c.config, n.info, warnDeprecated, "region for pointer types") addSonSkipIntLit(result, region) addSonSkipIntLit(result, t) if tfPartial in result.flags: @@ -167,7 +209,7 @@ proc semVarType(c: PContext, n: PNode, prev: PType): PType = result = newOrPrevType(tyVar, prev, c) var base = semTypeNode(c, n.sons[0], nil).skipTypes({tyTypeDesc}) if base.kind == tyVar: - localError(n.info, errVarVarTypeNotAllowed) + localError(c.config, n.info, "type 'var var' is not allowed") base = base.sons[0] addSonSkipIntLit(result, base) else: @@ -181,11 +223,15 @@ proc semDistinct(c: PContext, n: PNode, prev: PType): PType = proc semRangeAux(c: PContext, n: PNode, prev: PType): PType = assert isRange(n) - checkSonsLen(n, 3) + checkSonsLen(n, 3, c.config) result = newOrPrevType(tyRange, prev, c) result.n = newNodeI(nkRange, n.info) + # always create a 'valid' range type, but overwrite it later + # because 'semExprWithType' can raise an exception. See bug #6895. + addSonSkipIntLit(result, errorType(c)) + if (n[1].kind == nkEmpty) or (n[2].kind == nkEmpty): - localError(n.info, errRangeIsEmpty) + localError(c.config, n.info, "range is empty") var range: array[2, PNode] range[0] = semExprWithType(c, n[1], {efDetermineType}) @@ -200,23 +246,23 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType = if not hasUnknownTypes: if not sameType(rangeT[0].skipTypes({tyRange}), rangeT[1].skipTypes({tyRange})): - localError(n.info, errPureTypeMismatch) - elif not rangeT[0].isOrdinalType: - localError(n.info, errOrdinalTypeExpected) + localError(c.config, n.info, "type mismatch") + elif not rangeT[0].isOrdinalType and rangeT[0].kind notin tyFloat..tyFloat128: + localError(c.config, n.info, "ordinal or float type expected") elif enumHasHoles(rangeT[0]): - localError(n.info, errEnumXHasHoles, rangeT[0].sym.name.s) + localError(c.config, n.info, "enum '$1' has holes" % typeToString(rangeT[0])) for i in 0..1: - if hasGenericArguments(range[i]): + if hasUnresolvedArgs(c, range[i]): result.n.addSon makeStaticExpr(c, range[i]) result.flags.incl tfUnresolved else: result.n.addSon semConstExpr(c, range[i]) if weakLeValue(result.n[0], result.n[1]) == impNo: - localError(n.info, errRangeIsEmpty) + localError(c.config, n.info, "range is empty") - addSonSkipIntLit(result, rangeT[0]) + result[0] = rangeT[0] proc semRange(c: PContext, n: PNode, prev: PType): PType = result = nil @@ -235,13 +281,13 @@ proc semRange(c: PContext, n: PNode, prev: PType): PType = n.sons[1].floatVal < 0.0: incl(result.flags, tfNeedsInit) else: - if n[1].kind == nkInfix and considerQuotedIdent(n[1][0]).s == "..<": - localError(n[0].info, "range types need to be constructed with '..', '..<' is not supported") + if n[1].kind == nkInfix and considerQuotedIdent(c, n[1][0]).s == "..<": + localError(c.config, n[0].info, "range types need to be constructed with '..', '..<' is not supported") else: - localError(n.sons[0].info, errRangeExpected) + localError(c.config, n.sons[0].info, "expected range") result = newOrPrevType(tyError, prev, c) else: - localError(n.info, errXExpectsOneTypeParam, "range") + localError(c.config, n.info, errXExpectsOneTypeParam % "range") result = newOrPrevType(tyError, prev, c) proc semArrayIndex(c: PContext, n: PNode): PType = @@ -252,17 +298,21 @@ proc semArrayIndex(c: PContext, n: PNode): PType = if e.typ.kind == tyFromExpr: result = makeRangeWithStaticExpr(c, e.typ.n) elif e.kind in {nkIntLit..nkUInt64Lit}: + if e.intVal < 0: + localError(c.config, n[1].info, + "Array length can't be negative, but was " & $e.intVal) result = makeRangeType(c, 0, e.intVal-1, n.info, e.typ) elif e.kind == nkSym and e.typ.kind == tyStatic: if e.sym.ast != nil: return semArrayIndex(c, e.sym.ast) if not isOrdinalType(e.typ.lastSon): - localError(n[1].info, errOrdinalTypeExpected) + let info = if n.safeLen > 1: n[1].info else: n.info + localError(c.config, info, errOrdinalTypeExpected) result = makeRangeWithStaticExpr(c, e) if c.inGenericContext > 0: result.flags.incl tfUnresolved - elif e.kind in nkCallKinds and hasGenericArguments(e): + elif e.kind in (nkCallKinds + {nkBracketExpr}) and hasUnresolvedArgs(c, e): if not isOrdinalType(e.typ): - localError(n[1].info, errOrdinalTypeExpected) + localError(c.config, n[1].info, errOrdinalTypeExpected) # This is an int returning call, depending on an # yet unknown generic param (see tgenericshardcases). # We are going to construct a range type that will be @@ -278,7 +328,7 @@ proc semArrayIndex(c: PContext, n: PNode): PType = x.typ.skipTypes({tyTypeDesc})) else: result = x.typ.skipTypes({tyTypeDesc}) - #localError(n[1].info, errConstExprExpected) + #localError(c.config, n[1].info, errConstExprExpected) proc semArray(c: PContext, n: PNode, prev: PType): PType = var base: PType @@ -291,9 +341,9 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType = if indxB.skipTypes({tyRange}).kind in {tyUInt, tyUInt64}: discard elif not isOrdinalType(indxB): - localError(n.sons[1].info, errOrdinalTypeExpected) + localError(c.config, n.sons[1].info, errOrdinalTypeExpected) elif enumHasHoles(indxB): - localError(n.sons[1].info, errEnumXHasHoles, + localError(c.config, n.sons[1].info, "enum '$1' has holes" % typeToString(indxB.skipTypes({tyRange}))) base = semTypeNode(c, n.sons[2], nil) # ensure we only construct a tyArray when there was no error (bug #3048): @@ -303,7 +353,7 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType = rawAddSonNoPropagationOfTypeFlags(result, indx) addSonSkipIntLit(result, base) else: - localError(n.info, errArrayExpectsTwoTypeParams) + localError(c.config, n.info, errArrayExpectsTwoTypeParams) result = newOrPrevType(tyError, prev, c) proc semOrdinal(c: PContext, n: PNode, prev: PType): PType = @@ -312,22 +362,23 @@ proc semOrdinal(c: PContext, n: PNode, prev: PType): PType = var base = semTypeNode(c, n.sons[1], nil) if base.kind != tyGenericParam: if not isOrdinalType(base): - localError(n.sons[1].info, errOrdinalTypeExpected) + localError(c.config, n.sons[1].info, errOrdinalTypeExpected) addSonSkipIntLit(result, base) else: - localError(n.info, errXExpectsOneTypeParam, "ordinal") + localError(c.config, n.info, errXExpectsOneTypeParam % "ordinal") result = newOrPrevType(tyError, prev, c) proc semTypeIdent(c: PContext, n: PNode): PSym = if n.kind == nkSym: result = getGenSym(c, n.sym) else: - result = pickSym(c, n, {skType, skGenericParam}) + result = pickSym(c, n, {skType, skGenericParam, skParam}) if result.isNil: result = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared}) if result != nil: - markUsed(n.info, result, c.graph.usageSym) + markUsed(c.config, n.info, result, c.graph.usageSym) styleCheckUse(n.info, result) + if result.kind == skParam and result.typ.kind == tyTypeDesc: # This is a typedesc param. is it already bound? # it's not bound when it's used multiple times in the @@ -337,7 +388,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym = if bound != nil: return bound return result if result.typ.sym == nil: - localError(n.info, errTypeExpected) + localError(c.config, n.info, errTypeExpected) return errorSym(c, n) result = result.typ.sym.copySym result.typ = copyType(result.typ, result.typ.owner, true) @@ -351,10 +402,9 @@ proc semTypeIdent(c: PContext, n: PNode): PSym = result.typ.flags.excl tfWildcard return else: - localError(n.info, errTypeExpected) + localError(c.config, n.info, errTypeExpected) return errorSym(c, n) - - if result.kind != skType: + if result.kind != skType and result.magic notin (mStaticTy + mTypeTy): # this implements the wanted ``var v: V, x: V`` feature ... var ov: TOverloadIter var amb = initOverloadIter(ov, c, n) @@ -362,7 +412,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym = amb = nextOverloadIter(ov, c, n) if amb != nil: result = amb else: - if result.kind != skError: localError(n.info, errTypeExpected) + if result.kind != skError: localError(c.config, n.info, errTypeExpected) return errorSym(c, n) if result.typ.kind != tyGenericParam: # XXX get rid of this hack! @@ -377,15 +427,15 @@ proc semTypeIdent(c: PContext, n: PNode): PSym = n.info = oldInfo n.typ = result.typ else: - localError(n.info, errIdentifierExpected) + localError(c.config, n.info, "identifier expected") result = errorSym(c, n) proc semAnonTuple(c: PContext, n: PNode, prev: PType): PType = if sonsLen(n) == 0: - localError(n.info, errTypeExpected) + localError(c.config, n.info, errTypeExpected) result = newOrPrevType(tyTuple, prev, c) - for i in countup(0, sonsLen(n) - 1): - addSonSkipIntLit(result, semTypeNode(c, n.sons[i], nil)) + for it in n: + addSonSkipIntLit(result, semTypeNode(c, it, nil)) proc semTuple(c: PContext, n: PNode, prev: PType): PType = var typ: PType @@ -395,27 +445,27 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType = var counter = 0 for i in countup(ord(n.kind == nkBracketExpr), sonsLen(n) - 1): var a = n.sons[i] - if (a.kind != nkIdentDefs): illFormedAst(a) - checkMinSonsLen(a, 3) + if (a.kind != nkIdentDefs): illFormedAst(a, c.config) + checkMinSonsLen(a, 3, c.config) var length = sonsLen(a) if a.sons[length - 2].kind != nkEmpty: typ = semTypeNode(c, a.sons[length - 2], nil) else: - localError(a.info, errTypeExpected) + localError(c.config, a.info, errTypeExpected) typ = errorType(c) if a.sons[length - 1].kind != nkEmpty: - localError(a.sons[length - 1].info, errInitHereNotAllowed) + localError(c.config, a.sons[length - 1].info, errInitHereNotAllowed) for j in countup(0, length - 3): var field = newSymG(skField, a.sons[j], c) field.typ = typ field.position = counter inc(counter) if containsOrIncl(check, field.name.id): - localError(a.sons[j].info, errAttemptToRedefine, field.name.s) + localError(c.config, a.sons[j].info, "attempt to redefine: '" & field.name.s & "'") else: addSon(result.n, newSymNode(field)) addSonSkipIntLit(result, typ) - if gCmd == cmdPretty: styleCheckDef(a.sons[j].info, field) + styleCheckDef(c.config, a.sons[j].info, field) if result.n.len == 0: result.n = nil proc semIdentVis(c: PContext, kind: TSymKind, n: PNode, @@ -426,23 +476,23 @@ proc semIdentVis(c: PContext, kind: TSymKind, n: PNode, # for gensym'ed identifiers the identifier may already have been # transformed to a symbol and we need to use that here: result = newSymG(kind, n.sons[1], c) - var v = considerQuotedIdent(n.sons[0]) + var v = considerQuotedIdent(c, n.sons[0]) if sfExported in allowed and v.id == ord(wStar): incl(result.flags, sfExported) else: if not (sfExported in allowed): - localError(n.sons[0].info, errXOnlyAtModuleScope, "export") + localError(c.config, n.sons[0].info, errXOnlyAtModuleScope % "export") else: - localError(n.sons[0].info, errInvalidVisibilityX, renderTree(n[0])) + localError(c.config, n.sons[0].info, errInvalidVisibilityX % renderTree(n[0])) else: - illFormedAst(n) + illFormedAst(n, c.config) else: result = newSymG(kind, n, c) proc semIdentWithPragma(c: PContext, kind: TSymKind, n: PNode, allowed: TSymFlags): PSym = if n.kind == nkPragmaExpr: - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) result = semIdentVis(c, kind, n.sons[0], allowed) case kind of skType: @@ -455,7 +505,7 @@ proc semIdentWithPragma(c: PContext, kind: TSymKind, n: PNode, else: discard else: result = semIdentVis(c, kind, n, allowed) - if gCmd == cmdPretty: styleCheckDef(n.info, result) + styleCheckDef(c.config, n.info, result) proc checkForOverlap(c: PContext, t: PNode, currentEx, branchIndex: int) = let ex = t[branchIndex][currentEx].skipConv @@ -463,10 +513,10 @@ proc checkForOverlap(c: PContext, t: PNode, currentEx, branchIndex: int) = for j in countup(0, sonsLen(t.sons[i]) - 2): if i == branchIndex and j == currentEx: break if overlap(t.sons[i].sons[j].skipConv, ex): - localError(ex.info, errDuplicateCaseLabel) + localError(c.config, ex.info, errDuplicateCaseLabel) proc semBranchRange(c: PContext, t, a, b: PNode, covered: var BiggestInt): PNode = - checkMinSonsLen(t, 1) + checkMinSonsLen(t, 1, c.config) let ac = semConstExpr(c, a) let bc = semConstExpr(c, b) let at = fitNode(c, t.sons[0].typ, ac, ac.info).skipConvTakeType @@ -475,21 +525,21 @@ proc semBranchRange(c: PContext, t, a, b: PNode, covered: var BiggestInt): PNode result = newNodeI(nkRange, a.info) result.add(at) result.add(bt) - if emptyRange(ac, bc): localError(b.info, errRangeIsEmpty) + if emptyRange(ac, bc): localError(c.config, b.info, "range is empty") else: covered = covered + getOrdValue(bc) - getOrdValue(ac) + 1 proc semCaseBranchRange(c: PContext, t, b: PNode, covered: var BiggestInt): PNode = - checkSonsLen(b, 3) + checkSonsLen(b, 3, c.config) result = semBranchRange(c, t, b.sons[1], b.sons[2], covered) proc semCaseBranchSetElem(c: PContext, t, b: PNode, covered: var BiggestInt): PNode = if isRange(b): - checkSonsLen(b, 3) + checkSonsLen(b, 3, c.config) result = semBranchRange(c, t, b.sons[1], b.sons[2], covered) elif b.kind == nkRange: - checkSonsLen(b, 2) + checkSonsLen(b, 2, c.config) result = semBranchRange(c, t, b.sons[0], b.sons[1], covered) else: result = fitNode(c, t.sons[0].typ, b, b.info) @@ -512,12 +562,15 @@ proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int, delSon(branch, 0) return elif r.kind notin {nkCurly, nkBracket} or len(r) == 0: - checkMinSonsLen(t, 1) - branch.sons[i] = skipConv(fitNode(c, t.sons[0].typ, r, r.info)) + checkMinSonsLen(t, 1, c.config) + var tmp = fitNode(c, t.sons[0].typ, r, r.info) + # the call to fitNode may introduce a call to a converter + if tmp.kind in {nkHiddenCallConv}: tmp = semConstExpr(c, tmp) + branch.sons[i] = skipConv(tmp) inc(covered) else: if r.kind == nkCurly: - r = r.deduplicate + r = deduplicate(c.config, r) # first element is special and will overwrite: branch.sons[i]: branch.sons[i] = semCaseBranchSetElem(c, t, r[0], covered) @@ -538,37 +591,37 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int, proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int, father: PNode, rectype: PType) = var a = copyNode(n) - checkMinSonsLen(n, 2) + checkMinSonsLen(n, 2, c.config) semRecordNodeAux(c, n.sons[0], check, pos, a, rectype) if a.sons[0].kind != nkSym: - internalError("semRecordCase: discriminant is no symbol") + internalError(c.config, "semRecordCase: discriminant is no symbol") return incl(a.sons[0].sym.flags, sfDiscriminant) var covered: BiggestInt = 0 var typ = skipTypes(a.sons[0].typ, abstractVar-{tyTypeDesc}) if not isOrdinalType(typ): - localError(n.info, errSelectorMustBeOrdinal) - elif firstOrd(typ) != 0: - localError(n.info, errGenerated, "low(" & $a.sons[0].sym.name.s & + localError(c.config, n.info, "selector must be of an ordinal type") + elif firstOrd(c.config, typ) != 0: + localError(c.config, n.info, "low(" & $a.sons[0].sym.name.s & ") must be 0 for discriminant") - elif lengthOrd(typ) > 0x00007FFF: - localError(n.info, errLenXinvalid, a.sons[0].sym.name.s) + elif lengthOrd(c.config, typ) > 0x00007FFF: + localError(c.config, n.info, "len($1) must be less than 32768" % a.sons[0].sym.name.s) var chckCovered = true for i in countup(1, sonsLen(n) - 1): var b = copyTree(n.sons[i]) addSon(a, b) case n.sons[i].kind of nkOfBranch: - checkMinSonsLen(b, 2) + checkMinSonsLen(b, 2, c.config) semCaseBranch(c, a, b, i, covered) of nkElse: chckCovered = false - checkSonsLen(b, 1) - else: illFormedAst(n) + checkSonsLen(b, 1, c.config) + else: illFormedAst(n, c.config) delSon(b, sonsLen(b) - 1) semRecordNodeAux(c, lastSon(n.sons[i]), check, pos, b, rectype) - if chckCovered and (covered != lengthOrd(a.sons[0].typ)): - localError(a.info, errNotAllCasesCovered) + if chckCovered and covered != lengthOrd(c.config, a.sons[0].typ): + localError(c.config, a.info, "not all cases are covered") addSon(father, a) proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int, @@ -579,22 +632,22 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int, var branch: PNode = nil # the branch to take for i in countup(0, sonsLen(n) - 1): var it = n.sons[i] - if it == nil: illFormedAst(n) + if it == nil: illFormedAst(n, c.config) var idx = 1 case it.kind of nkElifBranch: - checkSonsLen(it, 2) + checkSonsLen(it, 2, c.config) if c.inGenericContext == 0: var e = semConstBoolExpr(c, it.sons[0]) - if e.kind != nkIntLit: internalError(e.info, "semRecordNodeAux") + if e.kind != nkIntLit: internalError(c.config, e.info, "semRecordNodeAux") elif e.intVal != 0 and branch == nil: branch = it.sons[1] else: it.sons[0] = forceBool(c, semExprWithType(c, it.sons[0])) of nkElse: - checkSonsLen(it, 1) + checkSonsLen(it, 1, c.config) if branch == nil: branch = it.sons[0] idx = 0 - else: illFormedAst(n) + else: illFormedAst(n, c.config) if c.inGenericContext > 0: # use a new check intset here for each branch: var newCheck: IntSet @@ -618,65 +671,67 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int, semRecordNodeAux(c, n.sons[i], check, pos, a, rectype) if a != father: addSon(father, a) of nkIdentDefs: - checkMinSonsLen(n, 3) + checkMinSonsLen(n, 3, c.config) var length = sonsLen(n) var a: PNode if father.kind != nkRecList and length>=4: a = newNodeI(nkRecList, n.info) - else: a = ast.emptyNode + else: a = newNodeI(nkEmpty, n.info) if n.sons[length-1].kind != nkEmpty: - localError(n.sons[length-1].info, errInitHereNotAllowed) + localError(c.config, n.sons[length-1].info, errInitHereNotAllowed) var typ: PType if n.sons[length-2].kind == nkEmpty: - localError(n.info, errTypeExpected) + localError(c.config, n.info, errTypeExpected) typ = errorType(c) else: typ = semTypeNode(c, n.sons[length-2], nil) propagateToOwner(rectype, typ) - let rec = rectype.sym + var fieldOwner = if c.inGenericContext > 0: c.getCurrOwner + else: rectype.sym for i in countup(0, sonsLen(n)-3): var f = semIdentWithPragma(c, skField, n.sons[i], {sfExported}) - suggestSym(n.sons[i].info, f, c.graph.usageSym) + suggestSym(c.config, n.sons[i].info, f, c.graph.usageSym) f.typ = typ f.position = pos - if (rec != nil) and ({sfImportc, sfExportc} * rec.flags != {}) and - (f.loc.r == nil): + if fieldOwner != nil and + {sfImportc, sfExportc} * fieldOwner.flags != {} and + f.loc.r == nil: f.loc.r = rope(f.name.s) - f.flags = f.flags + ({sfImportc, sfExportc} * rec.flags) + f.flags = f.flags + ({sfImportc, sfExportc} * fieldOwner.flags) inc(pos) if containsOrIncl(check, f.name.id): - localError(n.sons[i].info, errAttemptToRedefine, f.name.s) + localError(c.config, n.sons[i].info, "attempt to redefine: '" & f.name.s & "'") if a.kind == nkEmpty: addSon(father, newSymNode(f)) else: addSon(a, newSymNode(f)) - styleCheckDef(f) + styleCheckDef(c.config, f) if a.kind != nkEmpty: addSon(father, a) of nkSym: # This branch only valid during generic object # inherited from generic/partial specialized parent second check. # There is no branch validity check here if containsOrIncl(check, n.sym.name.id): - localError(n.info, errAttemptToRedefine, n.sym.name.s) + localError(c.config, n.info, "attempt to redefine: '" & n.sym.name.s & "'") addSon(father, n) of nkEmpty: discard - else: illFormedAst(n) + else: illFormedAst(n, c.config) proc addInheritedFieldsAux(c: PContext, check: var IntSet, pos: var int, n: PNode) = case n.kind of nkRecCase: - if (n.sons[0].kind != nkSym): internalError(n.info, "addInheritedFieldsAux") + if (n.sons[0].kind != nkSym): internalError(c.config, n.info, "addInheritedFieldsAux") addInheritedFieldsAux(c, check, pos, n.sons[0]) for i in countup(1, sonsLen(n) - 1): case n.sons[i].kind of nkOfBranch, nkElse: addInheritedFieldsAux(c, check, pos, lastSon(n.sons[i])) - else: internalError(n.info, "addInheritedFieldsAux(record case branch)") + else: internalError(c.config, n.info, "addInheritedFieldsAux(record case branch)") of nkRecList: for i in countup(0, sonsLen(n) - 1): addInheritedFieldsAux(c, check, pos, n.sons[i]) of nkSym: incl(check, n.sym.name.id) inc(pos) - else: internalError(n.info, "addInheritedFieldsAux()") + else: internalError(c.config, n.info, "addInheritedFieldsAux()") proc skipGenericInvocation(t: PType): PType {.inline.} = result = t @@ -699,12 +754,12 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType = var pos = 0 var base, realBase: PType = nil # n.sons[0] contains the pragmas (if any). We process these later... - checkSonsLen(n, 3) + checkSonsLen(n, 3, c.config) if n.sons[1].kind != nkEmpty: realBase = semTypeNode(c, n.sons[1].sons[0], nil) base = skipTypesOrNil(realBase, skipPtrs) if base.isNil: - localError(n.info, errIllegalRecursionInTypeX, "object") + localError(c.config, n.info, "cannot inherit from a type that is not an object type") else: var concreteBase = skipGenericInvocation(base) if concreteBase.kind in {tyObject, tyGenericParam, @@ -717,10 +772,11 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType = addInheritedFields(c, check, pos, concreteBase) else: if concreteBase.kind != tyError: - localError(n.sons[1].info, errInheritanceOnlyWithNonFinalObjects) + localError(c.config, n.sons[1].info, "inheritance only works with non-final objects; " & + "to enable inheritance write '" & typeToString(realBase) & " of RootObj'") base = nil realBase = nil - if n.kind != nkObjectTy: internalError(n.info, "semObjectNode") + if n.kind != nkObjectTy: internalError(c.config, n.info, "semObjectNode") result = newOrPrevType(tyObject, prev, c) rawAddSon(result, realBase) if result.n.isNil: @@ -731,7 +787,7 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType = semRecordNodeAux(c, n.sons[2], check, pos, result.n, result) if n.sons[0].kind != nkEmpty: # dummy symbol for `pragma`: - var s = newSymS(skType, newIdentNode(getIdent("dummy"), n.info), c) + var s = newSymS(skType, newIdentNode(getIdent(c.cache, "dummy"), n.info), c) s.typ = result pragma(c, s, n.sons[0], typePragmas) if base == nil and tfInheritable notin result.flags: @@ -758,18 +814,15 @@ proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind) = addDecl(c, param) else: # within a macro, every param has the type NimNode! - let nn = if getCompilerProc("NimNode") != nil: getSysSym"NimNode" - else: getSysSym"PNimrodNode" + let nn = getSysSym(c.graph, param.info, "NimNode") var a = copySym(param) a.typ = nn.typ addDecl(c, a) else: if sfGenSym notin param.flags: addDecl(c, param) -let typedescId = getIdent"typedesc" - template shouldHaveMeta(t) = - internalAssert tfHasMeta in t.flags + internalAssert c.config, tfHasMeta in t.flags # result.lastSon.flags.incl tfHasMeta proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, @@ -777,13 +830,13 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, info: TLineInfo, anon = false): PType = if paramType == nil: return # (e.g. proc return type) - proc addImplicitGenericImpl(typeClass: PType, typId: PIdent): PType = + proc addImplicitGenericImpl(c: PContext; typeClass: PType, typId: PIdent): PType = if genericParams == nil: # This happens with anonymous proc types appearing in signatures # XXX: we need to lift these earlier return let finalTypId = if typId != nil: typId - else: getIdent(paramName & ":type") + else: getIdent(c.cache, paramName & ":type") # is this a bindOnce type class already present in the param list? for i in countup(0, genericParams.len - 1): if genericParams.sons[i].sym.name.id == finalTypId.id: @@ -814,35 +867,40 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, (if lifted != nil: lifted else: typ) template addImplicitGeneric(e): untyped = - addImplicitGenericImpl(e, paramTypId) + addImplicitGenericImpl(c, e, paramTypId) case paramType.kind: of tyAnything: - result = addImplicitGenericImpl(newTypeS(tyGenericParam, c), nil) + result = addImplicitGenericImpl(c, newTypeS(tyGenericParam, c), nil) of tyStatic: - # proc(a: expr{string}, b: expr{nkLambda}) - # overload on compile time values and AST trees - if paramType.n != nil: return # this is a concrete type + if paramType.base.kind != tyNone and paramType.n != nil: + # this is a concrete static value + return if tfUnresolved in paramType.flags: return # already lifted let base = paramType.base.maybeLift if base.isMetaType and procKind == skMacro: - localError(info, errMacroBodyDependsOnGenericTypes, paramName) + localError(c.config, info, errMacroBodyDependsOnGenericTypes % paramName) result = addImplicitGeneric(c.newTypeWithSons(tyStatic, @[base])) - result.flags.incl({tfHasStatic, tfUnresolved}) + if result != nil: result.flags.incl({tfHasStatic, tfUnresolved}) of tyTypeDesc: if tfUnresolved notin paramType.flags: # naked typedescs are not bindOnce types if paramType.base.kind == tyNone and paramTypId != nil and - paramTypId.id == typedescId.id: paramTypId = nil + paramTypId.id == getIdent(c.cache, "typedesc").id: + # XXX Why doesn't this check for tyTypeDesc instead? + paramTypId = nil result = addImplicitGeneric( c.newTypeWithSons(tyTypeDesc, @[paramType.base])) of tyDistinct: if paramType.sonsLen == 1: # disable the bindOnce behavior for the type class - result = liftingWalk(paramType.sons[0], true) + result = liftingWalk(paramType.base, true) + + of tyAlias: + result = liftingWalk(paramType.base) of tySequence, tySet, tyArray, tyOpenArray, tyVar, tyLent, tyPtr, tyRef, tyProc: @@ -859,7 +917,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, else: for i in 0 ..< paramType.len: if paramType.sons[i] == paramType: - globalError(info, errIllegalRecursionInTypeX, typeToString(paramType)) + globalError(c.config, info, errIllegalRecursionInTypeX % typeToString(paramType)) var lifted = liftingWalk(paramType.sons[i]) if lifted != nil: paramType.sons[i] = lifted @@ -928,7 +986,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, result = addImplicitGeneric(copyType(paramType, getCurrOwner(c), false)) of tyGenericParam: - markUsed(info, paramType.sym, c.graph.usageSym) + markUsed(c.config, info, paramType.sym, c.graph.usageSym) styleCheckUse(info, paramType.sym) if tfWildcard in paramType.flags: paramType.flags.excl tfWildcard @@ -941,7 +999,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, proc semParamType(c: PContext, n: PNode, constraint: var PNode): PType = if n.kind == nkCurlyExpr: result = semTypeNode(c, n.sons[0], nil) - constraint = semNodeKindConstraints(n) + constraint = semNodeKindConstraints(n, c.config) else: result = semTypeNode(c, n, nil) @@ -959,13 +1017,11 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, prev: PType, kind: TSymKind; isType=false): PType = # for historical reasons (code grows) this is invoked for parameter # lists too and then 'isType' is false. - var cl: IntSet - checkMinSonsLen(n, 1) + checkMinSonsLen(n, 1, c.config) result = newProcType(c, n.info, prev) - if genericParams != nil and sonsLen(genericParams) == 0: - cl = initIntSet() var check = initIntSet() var counter = 0 + for i in countup(1, n.len - 1): var a = n.sons[i] if a.kind != nkIdentDefs: @@ -974,8 +1030,9 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, # skip this parameter here. It'll then be re-generated in another LL # pass over this instantiation: if a.kind == nkSym and sfFromGeneric in a.sym.flags: continue - illFormedAst(a) - checkMinSonsLen(a, 3) + illFormedAst(a, c.config) + + checkMinSonsLen(a, 3, c.config) var typ: PType = nil def: PNode = nil @@ -983,33 +1040,59 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, length = sonsLen(a) hasType = a.sons[length-2].kind != nkEmpty hasDefault = a.sons[length-1].kind != nkEmpty + if hasType: typ = semParamType(c, a.sons[length-2], constraint) if hasDefault: - def = semExprWithType(c, a.sons[length-1]) - # check type compatibility between def.typ and typ: + def = a[^1] + block determineType: + if genericParams != nil and genericParams.len > 0: + def = semGenericStmt(c, def) + if hasUnresolvedArgs(c, def): + def.typ = makeTypeFromExpr(c, def.copyTree) + break determineType + + def = semExprWithType(c, def, {efDetermineType}) + if def.referencesAnotherParam(getCurrOwner(c)): + def.flags.incl nfDefaultRefsParam + if typ == nil: typ = def.typ - elif def != nil: - # and def.typ != nil and def.typ.kind != tyNone: + if typ.kind == tyTypeDesc: + # consider a proc such as: + # proc takesType(T = int) + # a naive analysis may conclude that the proc type is type[int] + # which will prevent other types from matching - clearly a very + # surprising behavior. We must instead fix the expected type of + # the proc to be the unbound typedesc type: + typ = newTypeWithSons(c, tyTypeDesc, @[newTypeS(tyNone, c)]) + + else: + # if def.typ != nil and def.typ.kind != tyNone: # example code that triggers it: # proc sort[T](cmp: proc(a, b: T): int = cmp) if not containsGenericType(typ): + # check type compatibility between def.typ and typ: + def = fitNode(c, typ, def, def.info) + elif typ.kind == tyStatic: + def = semConstExpr(c, def) def = fitNode(c, typ, def, def.info) + if not hasType and not hasDefault: - if isType: localError(a.info, "':' expected") + if isType: localError(c.config, a.info, "':' expected") if kind in {skTemplate, skMacro}: typ = newTypeS(tyExpr, c) elif skipTypes(typ, {tyGenericInst, tyAlias, tySink}).kind == tyVoid: continue + for j in countup(0, length-3): var arg = newSymG(skParam, a.sons[j], c) if not hasType and not hasDefault and kind notin {skTemplate, skMacro}: let param = strTableGet(c.signatures, arg.name) if param != nil: typ = param.typ else: - localError(a.info, "typeless parameters are obsolete") + localError(c.config, a.info, "typeless parameters are obsolete") typ = errorType(c) let lifted = liftParamType(c, kind, genericParams, typ, arg.name.s, arg.info) @@ -1018,13 +1101,14 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, arg.position = counter arg.constraint = constraint inc(counter) - if def != nil and def.kind != nkEmpty: arg.ast = copyTree(def) + if def != nil and def.kind != nkEmpty: + arg.ast = copyTree(def) if containsOrIncl(check, arg.name.id): - localError(a.sons[j].info, errAttemptToRedefine, arg.name.s) + localError(c.config, a.sons[j].info, "attempt to redefine: '" & arg.name.s & "'") addSon(result.n, newSymNode(arg)) rawAddSon(result, finalType) addParamOrResult(c, arg, kind) - if gCmd == cmdPretty: styleCheckDef(a.sons[j].info, arg) + styleCheckDef(c.config, a.sons[j].info, arg) var r: PType if n.sons[0].kind != nkEmpty: @@ -1074,7 +1158,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, n.sym.typ.flags.excl tfWildcard proc semStmtListType(c: PContext, n: PNode, prev: PType): PType = - checkMinSonsLen(n, 1) + checkMinSonsLen(n, 1, c.config) var length = sonsLen(n) for i in countup(0, length - 2): n.sons[i] = semStmt(c, n.sons[i]) @@ -1087,7 +1171,7 @@ proc semStmtListType(c: PContext, n: PNode, prev: PType): PType = proc semBlockType(c: PContext, n: PNode, prev: PType): PType = inc(c.p.nestedBlockCounter) - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) openScope(c) if n.sons[0].kind notin {nkEmpty, nkSym}: addDecl(c, newSymS(skLabel, n.sons[0], c)) @@ -1109,20 +1193,20 @@ proc semObjectTypeForInheritedGenericInst(c: PContext, n: PNode, t: PType) = realBase = t.sons[0] base = skipTypesOrNil(realBase, skipPtrs) if base.isNil: - localError(n.info, errIllegalRecursionInTypeX, "object") + localError(c.config, n.info, errIllegalRecursionInTypeX % "object") else: let concreteBase = skipGenericInvocation(base) if concreteBase.kind == tyObject and tfFinal notin concreteBase.flags: addInheritedFields(c, check, pos, concreteBase) else: if concreteBase.kind != tyError: - localError(n.info, errInheritanceOnlyWithNonFinalObjects) + localError(c.config, n.info, errInheritanceOnlyWithNonFinalObjects) var newf = newNodeI(nkRecList, n.info) semRecordNodeAux(c, t.n, check, pos, newf, t) proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = if s.typ == nil: - localError(n.info, "cannot instantiate the '$1' $2" % + localError(c.config, n.info, "cannot instantiate the '$1' $2" % [s.name.s, ($s.kind).substr(2).toLowerAscii]) return newOrPrevType(tyError, prev, c) @@ -1135,7 +1219,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = template addToResult(typ) = if typ.isNil: - internalAssert false + internalAssert c.config, false rawAddSon(result, typ) else: addSonSkipIntLit(result, typ) @@ -1147,7 +1231,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = elif t.kind != tyGenericBody: # we likely got code of the form TypeA[TypeB] where TypeA is # not generic. - localError(n.info, errNoGenericParamsAllowedForX, s.name.s) + localError(c.config, n.info, errNoGenericParamsAllowedForX % s.name.s) return newOrPrevType(tyError, prev, c) else: var m = newCandidate(c, t) @@ -1158,7 +1242,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = let err = "cannot instantiate " & typeToString(t) & "\n" & "got: <" & describeArgs(c, n) & ">\n" & "but expected: <" & describeArgs(c, t.n, 0) & ">" - localError(n.info, errGenerated, err) + localError(c.config, n.info, errGenerated, err) return newOrPrevType(tyError, prev, c) var isConcrete = true @@ -1176,7 +1260,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = if isConcrete: if s.ast == nil and s.typ.kind != tyCompositeTypeClass: # XXX: What kind of error is this? is it still relevant? - localError(n.info, errCannotInstantiateX, s.name.s) + localError(c.config, n.info, errCannotInstantiateX % s.name.s) result = newOrPrevType(tyError, prev, c) else: result = instGenericContainer(c, n.info, result, @@ -1186,7 +1270,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = # generic/partial specialized parent let tx = result.skipTypes(abstractPtrs, 50) if tx.isNil: - localError(n.info, "invalid recursion in type '$1'" % typeToString(result[0])) + localError(c.config, n.info, "invalid recursion in type '$1'" % typeToString(result[0])) return errorType(c) if tx != result and tx.kind == tyObject and tx.sons[0] != nil: semObjectTypeForInheritedGenericInst(c, n, tx) @@ -1215,7 +1299,7 @@ proc semTypeExpr(c: PContext, n: PNode; prev: PType): PType = let alias = maybeAliasType(c, result, prev) if alias != nil: result = alias else: - localError(n.info, errTypeExpected, n.renderTree) + localError(c.config, n.info, "expected type, but got: " & n.renderTree) result = errorType(c) proc freshType(res, prev: PType): PType {.inline.} = @@ -1261,33 +1345,38 @@ proc semTypeClass(c: PContext, n: PNode, prev: PType): PType = if modifier != tyNone: dummyName = param[0] dummyType = c.makeTypeWithModifier(modifier, candidateTypeSlot) - if modifier == tyTypeDesc: dummyType.flags.incl tfExplicit + if modifier == tyTypeDesc: dummyType.flags.incl tfConceptMatchedTypeSym else: dummyName = param dummyType = candidateTypeSlot - internalAssert dummyName.kind == nkIdent + # this can be true for 'nim check' on incomplete concepts, + # see bug #8230 + if dummyName.kind == nkEmpty: continue + + internalAssert c.config, dummyName.kind == nkIdent var dummyParam = newSym(if modifier == tyTypeDesc: skType else: skVar, - dummyName.ident, owner, owner.info) + dummyName.ident, owner, param.info) dummyParam.typ = dummyType + incl dummyParam.flags, sfUsed addDecl(c, dummyParam) - result.n.sons[3] = semConceptBody(c, n[3]) + result.n[3] = semConceptBody(c, n[3]) closeScope(c) proc semProcTypeWithScope(c: PContext, n: PNode, prev: PType, kind: TSymKind): PType = - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) openScope(c) result = semProcTypeNode(c, n.sons[0], nil, prev, kind, isType=true) # start with 'ccClosure', but of course pragmas can overwrite this: result.callConv = ccClosure # dummy symbol for `pragma`: - var s = newSymS(kind, newIdentNode(getIdent("dummy"), n.info), c) + var s = newSymS(kind, newIdentNode(getIdent(c.cache, "dummy"), n.info), c) s.typ = result if n.sons[1].kind != nkEmpty and n.sons[1].len > 0: pragma(c, s, n.sons[1], procTypePragmas) - when useEffectSystem: setEffectsForProcType(result, n.sons[1]) + when useEffectSystem: setEffectsForProcType(c.graph, result, n.sons[1]) closeScope(c) proc maybeAliasType(c: PContext; typeExpr, prev: PType): PType = @@ -1306,21 +1395,27 @@ proc fixupTypeOf(c: PContext, prev: PType, typExpr: PNode) = proc symFromExpectedTypeNode(c: PContext, n: PNode): PSym = if n.kind == nkType: - result = symFromType(n.typ, n.info) + result = symFromType(c, n.typ, n.info) else: - localError(n.info, errTypeExpected) + localError(c.config, n.info, errTypeExpected) result = errorSym(c, n) +proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType = + result = newOrPrevType(tyStatic, prev, c) + var base = semTypeNode(c, childNode, nil).skipTypes({tyTypeDesc, tyAlias}) + result.rawAddSon(base) + result.flags.incl tfHasStatic + proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = nil inc c.inTypeContext - if gCmd == cmdIdeTools: suggestExpr(c, n) + if c.config.cmd == cmdIdeTools: suggestExpr(c, n) case n.kind of nkEmpty: discard of nkTypeOfExpr: # for ``type(countup(1,3))``, see ``tests/ttoseq``. - checkSonsLen(n, 1) + checkSonsLen(n, 1, c.config) let typExpr = semExprWithType(c, n.sons[0], {efInTypeof}) fixupTypeOf(c, prev, typExpr) result = typExpr.typ @@ -1329,6 +1424,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = if sonsLen(n) == 1: result = semTypeNode(c, n.sons[0], prev) else: result = semAnonTuple(c, n, prev) + of nkTupleConstr: result = semAnonTuple(c, n, prev) of nkCallKinds: let x = n[0] let ident = case x.kind @@ -1349,21 +1445,21 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = freshType(result, prev) result.flags.excl(tfNotNil) else: - localError(n.info, errGenerated, "invalid type") + localError(c.config, n.info, errGenerated, "invalid type") elif n[0].kind notin nkIdentKinds: result = semTypeExpr(c, n, prev) else: - let op = considerQuotedIdent(n.sons[0]) + let op = considerQuotedIdent(c, n.sons[0]) if op.id in {ord(wAnd), ord(wOr)} or op.s == "|": - checkSonsLen(n, 3) + checkSonsLen(n, 3, c.config) var t1 = semTypeNode(c, n.sons[1], nil) t2 = semTypeNode(c, n.sons[2], nil) if t1 == nil: - localError(n.sons[1].info, errTypeExpected) + localError(c.config, n.sons[1].info, errTypeExpected) result = newOrPrevType(tyError, prev, c) elif t2 == nil: - localError(n.sons[2].info, errTypeExpected) + localError(c.config, n.sons[2].info, errTypeExpected) result = newOrPrevType(tyError, prev, c) else: result = if op.id == ord(wAnd): makeAndType(c, t1, t2) @@ -1376,30 +1472,35 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = n.sons[2].kind == nkNilLit: result = freshType(result, prev) result.flags.incl(tfNotNil) + if notnil notin c.features: + localError(c.config, n.info, "enable the 'not nil' annotation with {.experimental: \"notnil\".}") else: - localError(n.info, errGenerated, "invalid type") + localError(c.config, n.info, errGenerated, "invalid type") of 2: let negated = semTypeNode(c, n.sons[1], prev) result = makeNotType(c, negated) else: - localError(n.info, errGenerated, "invalid type") + localError(c.config, n.info, errGenerated, "invalid type") elif op.id == ord(wPtr): result = semAnyRef(c, n, tyPtr, prev) elif op.id == ord(wRef): result = semAnyRef(c, n, tyRef, prev) elif op.id == ord(wType): - checkSonsLen(n, 2) + checkSonsLen(n, 2, c.config) let typExpr = semExprWithType(c, n.sons[1], {efInTypeof}) fixupTypeOf(c, prev, typExpr) result = typExpr.typ else: - result = semTypeExpr(c, n, prev) + if c.inGenericContext > 0 and n.kind == nkCall: + result = makeTypeFromExpr(c, n.copyTree) + else: + result = semTypeExpr(c, n, prev) of nkWhenStmt: var whenResult = semWhen(c, n, false) if whenResult.kind == nkStmtList: whenResult.kind = nkStmtListType result = semTypeNode(c, whenResult, prev) of nkBracketExpr: - checkMinSonsLen(n, 2) + checkMinSonsLen(n, 2, c.config) var head = n.sons[0] var s = if head.kind notin nkCallKinds: semTypeIdent(c, head) else: symFromExpectedTypeNode(c, semExpr(c, head)) @@ -1409,10 +1510,31 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of mRange: result = semRange(c, n, prev) of mSet: result = semSet(c, n, prev) of mOrdinal: result = semOrdinal(c, n, prev) - of mSeq: result = semContainer(c, n, tySequence, "seq", prev) + of mSeq: + if c.config.selectedGc == gcDestructors: + let s = c.graph.sysTypes[tySequence] + assert s != nil + assert prev == nil + result = copyType(s, s.owner, keepId=false) + # XXX figure out why this has children already... + result.sons.setLen 0 + result.n = nil + if c.config.selectedGc == gcDestructors: + result.flags = {tfHasAsgn} + else: + result.flags = {} + semContainerArg(c, n, "seq", result) + else: + result = semContainer(c, n, tySequence, "seq", prev) + if c.config.selectedGc == gcDestructors: + incl result.flags, tfHasAsgn of mOpt: result = semContainer(c, n, tyOpt, "opt", prev) of mVarargs: result = semVarargs(c, n, prev) - of mTypeDesc: result = makeTypeDesc(c, semTypeNode(c, n[1], nil)) + of mTypeDesc, mTypeTy: + result = makeTypeDesc(c, semTypeNode(c, n[1], nil)) + result.flags.incl tfExplicit + of mStaticTy: + result = semStaticType(c, n[1], prev) of mExpr: result = semTypeNode(c, n.sons[0], nil) if result != nil: @@ -1426,7 +1548,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = newOrPrevType(tyVar, prev, c) var base = semTypeNode(c, n.sons[1], nil) if base.kind in {tyVar, tyLent}: - localError(n.info, errVarVarTypeNotAllowed) + localError(c.config, n.info, "type 'var var' is not allowed") base = base.sons[0] addSonSkipIntLit(result, base) of mRef: result = semAnyRef(c, n, tyRef, prev) @@ -1436,13 +1558,13 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of nkDotExpr: let typeExpr = semExpr(c, n) if typeExpr.typ.isNil: - localError(n.info, "object constructor needs an object type;" & + localError(c.config, n.info, "object constructor needs an object type;" & " for named arguments use '=' instead of ':'") result = errorType(c) elif typeExpr.typ.kind == tyFromExpr: result = typeExpr.typ elif typeExpr.typ.kind != tyTypeDesc: - localError(n.info, errTypeExpected) + localError(c.config, n.info, errTypeExpected) result = errorType(c) else: result = typeExpr.typ.base @@ -1458,10 +1580,10 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of nkIdent, nkAccQuoted: var s = semTypeIdent(c, n) if s.typ == nil: - if s.kind != skError: localError(n.info, errTypeExpected) + if s.kind != skError: localError(c.config, n.info, errTypeExpected) result = newOrPrevType(tyError, prev, c) elif s.kind == skParam and s.typ.kind == tyTypeDesc: - internalAssert s.typ.base.kind != tyNone and prev == nil + internalAssert c.config, s.typ.base.kind != tyNone and prev == nil result = s.typ.base elif prev == nil: result = s.typ @@ -1478,8 +1600,14 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = prev of nkSym: let s = getGenSym(c, n.sym) - if s.kind == skType and s.typ != nil: - var t = s.typ + if s.kind == skType and s.typ != nil or + s.kind == skParam and s.typ.kind == tyTypeDesc: + var t = + if s.kind == skType: + s.typ + else: + internalAssert c.config, s.typ.base.kind != tyNone and prev == nil + s.typ.base let alias = maybeAliasType(c, t, prev) if alias != nil: result = alias @@ -1488,10 +1616,10 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = else: assignType(prev, t) result = prev - markUsed(n.info, n.sym, c.graph.usageSym) + markUsed(c.config, n.info, n.sym, c.graph.usageSym) styleCheckUse(n.info, n.sym) else: - if s.kind != skError: localError(n.info, errTypeExpected) + if s.kind != skError: localError(c.config, n.info, errTypeExpected) result = newOrPrevType(tyError, prev, c) of nkObjectTy: result = semObjectNode(c, n, prev) of nkTupleTy: result = semTuple(c, n, prev) @@ -1501,11 +1629,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of nkPtrTy: result = semAnyRef(c, n, tyPtr, prev) of nkVarTy: result = semVarType(c, n, prev) of nkDistinctTy: result = semDistinct(c, n, prev) - of nkStaticTy: - result = newOrPrevType(tyStatic, prev, c) - var base = semTypeNode(c, n.sons[0], nil).skipTypes({tyTypeDesc}) - result.rawAddSon(base) - result.flags.incl tfHasStatic + of nkStaticTy: result = semStaticType(c, n[0], prev) of nkIteratorTy: if n.sonsLen == 0: result = newTypeS(tyBuiltInTypeClass, c) @@ -1529,7 +1653,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of nkStmtListType: result = semStmtListType(c, n, prev) of nkBlockType: result = semBlockType(c, n, prev) else: - localError(n.info, errTypeExpected) + localError(c.config, n.info, errTypeExpected) result = newOrPrevType(tyError, prev, c) n.typ = result dec c.inTypeContext @@ -1540,7 +1664,7 @@ when false: result = semTypeNodeInner(c, n, prev) instAllTypeBoundOp(c, n.info) -proc setMagicType(m: PSym, kind: TTypeKind, size: int) = +proc setMagicType(conf: ConfigRef; m: PSym, kind: TTypeKind, size: int) = # source : https://en.wikipedia.org/wiki/Data_structure_alignment#x86 m.typ.kind = kind m.typ.size = size @@ -1551,10 +1675,10 @@ proc setMagicType(m: PSym, kind: TTypeKind, size: int) = # FIXME: proper support for clongdouble should be added. # long double size can be 8, 10, 12, 16 bytes depending on platform & compiler - if targetCPU == cpuI386 and size == 8: + if conf.target.targetCPU == cpuI386 and size == 8: #on Linux/BSD i386, double are aligned to 4bytes (except with -malign-double) if kind in {tyFloat64, tyFloat} and - targetOS in {osLinux, osAndroid, osNetbsd, osFreebsd, osOpenbsd, osDragonfly}: + conf.target.targetOS in {osLinux, osAndroid, osNetbsd, osFreebsd, osOpenbsd, osDragonfly}: m.typ.align = 4 # on i386, all known compiler, 64bits ints are aligned to 4bytes (except with -malign-double) elif kind in {tyInt, tyUInt, tyInt64, tyUInt64}: @@ -1564,75 +1688,85 @@ proc setMagicType(m: PSym, kind: TTypeKind, size: int) = proc processMagicType(c: PContext, m: PSym) = case m.magic - of mInt: setMagicType(m, tyInt, intSize) - of mInt8: setMagicType(m, tyInt8, 1) - of mInt16: setMagicType(m, tyInt16, 2) - of mInt32: setMagicType(m, tyInt32, 4) - of mInt64: setMagicType(m, tyInt64, 8) - of mUInt: setMagicType(m, tyUInt, intSize) - of mUInt8: setMagicType(m, tyUInt8, 1) - of mUInt16: setMagicType(m, tyUInt16, 2) - of mUInt32: setMagicType(m, tyUInt32, 4) - of mUInt64: setMagicType(m, tyUInt64, 8) - of mFloat: setMagicType(m, tyFloat, floatSize) - of mFloat32: setMagicType(m, tyFloat32, 4) - of mFloat64: setMagicType(m, tyFloat64, 8) - of mFloat128: setMagicType(m, tyFloat128, 16) - of mBool: setMagicType(m, tyBool, 1) - of mChar: setMagicType(m, tyChar, 1) + of mInt: setMagicType(c.config, m, tyInt, c.config.target.intSize) + of mInt8: setMagicType(c.config, m, tyInt8, 1) + of mInt16: setMagicType(c.config, m, tyInt16, 2) + of mInt32: setMagicType(c.config, m, tyInt32, 4) + of mInt64: setMagicType(c.config, m, tyInt64, 8) + of mUInt: setMagicType(c.config, m, tyUInt, c.config.target.intSize) + of mUInt8: setMagicType(c.config, m, tyUInt8, 1) + of mUInt16: setMagicType(c.config, m, tyUInt16, 2) + of mUInt32: setMagicType(c.config, m, tyUInt32, 4) + of mUInt64: setMagicType(c.config, m, tyUInt64, 8) + of mFloat: setMagicType(c.config, m, tyFloat, c.config.target.floatSize) + of mFloat32: setMagicType(c.config, m, tyFloat32, 4) + of mFloat64: setMagicType(c.config, m, tyFloat64, 8) + of mFloat128: setMagicType(c.config, m, tyFloat128, 16) + of mBool: setMagicType(c.config, m, tyBool, 1) + of mChar: setMagicType(c.config, m, tyChar, 1) of mString: - setMagicType(m, tyString, ptrSize) - rawAddSon(m.typ, getSysType(tyChar)) + setMagicType(c.config, m, tyString, c.config.target.ptrSize) + rawAddSon(m.typ, getSysType(c.graph, m.info, tyChar)) + when false: + if c.config.selectedGc == gcDestructors: + incl m.typ.flags, tfHasAsgn of mCstring: - setMagicType(m, tyCString, ptrSize) - rawAddSon(m.typ, getSysType(tyChar)) - of mPointer: setMagicType(m, tyPointer, ptrSize) + setMagicType(c.config, m, tyCString, c.config.target.ptrSize) + rawAddSon(m.typ, getSysType(c.graph, m.info, tyChar)) + of mPointer: setMagicType(c.config, m, tyPointer, c.config.target.ptrSize) of mEmptySet: - setMagicType(m, tySet, 1) + setMagicType(c.config, m, tySet, 1) rawAddSon(m.typ, newTypeS(tyEmpty, c)) - of mIntSetBaseType: setMagicType(m, tyRange, intSize) - of mNil: setMagicType(m, tyNil, ptrSize) + of mIntSetBaseType: setMagicType(c.config, m, tyRange, c.config.target.intSize) + of mNil: setMagicType(c.config, m, tyNil, c.config.target.ptrSize) of mExpr: if m.name.s == "auto": - setMagicType(m, tyAnything, 0) + setMagicType(c.config, m, tyAnything, 0) else: - setMagicType(m, tyExpr, 0) + setMagicType(c.config, m, tyExpr, 0) if m.name.s == "expr": m.typ.flags.incl tfOldSchoolExprStmt of mStmt: - setMagicType(m, tyStmt, 0) + setMagicType(c.config, m, tyStmt, 0) if m.name.s == "stmt": m.typ.flags.incl tfOldSchoolExprStmt - of mTypeDesc: - setMagicType(m, tyTypeDesc, 0) + of mTypeDesc, mType: + setMagicType(c.config, m, tyTypeDesc, 0) + rawAddSon(m.typ, newTypeS(tyNone, c)) + of mStatic: + setMagicType(c.config, m, tyStatic, 0) rawAddSon(m.typ, newTypeS(tyNone, c)) of mVoidType: - setMagicType(m, tyVoid, 0) + setMagicType(c.config, m, tyVoid, 0) of mArray: - setMagicType(m, tyArray, 0) + setMagicType(c.config, m, tyArray, 0) of mOpenArray: - setMagicType(m, tyOpenArray, 0) + setMagicType(c.config, m, tyOpenArray, 0) of mVarargs: - setMagicType(m, tyVarargs, 0) + setMagicType(c.config, m, tyVarargs, 0) of mRange: - setMagicType(m, tyRange, 0) + setMagicType(c.config, m, tyRange, 0) rawAddSon(m.typ, newTypeS(tyNone, c)) of mSet: - setMagicType(m, tySet, 0) + setMagicType(c.config, m, tySet, 0) of mSeq: - setMagicType(m, tySequence, 0) + setMagicType(c.config, m, tySequence, 0) + if c.config.selectedGc == gcDestructors: + incl m.typ.flags, tfHasAsgn + assert c.graph.sysTypes[tySequence] == nil + c.graph.sysTypes[tySequence] = m.typ of mOpt: - setMagicType(m, tyOpt, 0) + setMagicType(c.config, m, tyOpt, 0) of mOrdinal: - setMagicType(m, tyOrdinal, 0) + setMagicType(c.config, m, tyOrdinal, 0) rawAddSon(m.typ, newTypeS(tyNone, c)) of mPNimrodNode: incl m.typ.flags, tfTriggersCompileTime of mException: discard of mBuiltinType: case m.name.s - of "lent": setMagicType(m, tyLent, ptrSize) - of "sink": setMagicType(m, tySink, 0) - else: localError(m.info, errTypeExpected) - else: localError(m.info, errTypeExpected) + of "lent": setMagicType(c.config, m, tyLent, c.config.target.ptrSize) + of "sink": setMagicType(c.config, m, tySink, 0) + else: localError(c.config, m.info, errTypeExpected) + else: localError(c.config, m.info, errTypeExpected) proc semGenericConstraints(c: PContext, x: PType): PType = result = newTypeWithSons(c, tyGenericParam, @[x]) @@ -1640,11 +1774,11 @@ proc semGenericConstraints(c: PContext, x: PType): PType = proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode = result = copyNode(n) if n.kind != nkGenericParams: - illFormedAst(n) + illFormedAst(n, c.config) return for i in countup(0, sonsLen(n)-1): var a = n.sons[i] - if a.kind != nkIdentDefs: illFormedAst(n) + if a.kind != nkIdentDefs: illFormedAst(n, c.config) let L = a.len var def = a[^1] let constraint = a[^2] @@ -1690,7 +1824,7 @@ proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode = if paramName.safeLen == 2: if not nimEnableCovariance or paramName[0].ident.s == "in": if father == nil or sfImportc notin father.sym.flags: - localError(paramName.info, errInOutFlagNotExtern, paramName[0].ident.s) + localError(c.config, paramName.info, errInOutFlagNotExtern % $paramName[0]) covarianceFlag = if paramName[0].ident.s == "in": tfContravariant else: tfCovariant if father != nil: father.flags.incl tfCovariant diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index d9b368b0e..c315cbebb 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -9,26 +9,27 @@ # This module does the instantiation of generic types. -import ast, astalgo, msgs, types, magicsys, semdata, renderer, options +import ast, astalgo, msgs, types, magicsys, semdata, renderer, options, + lineinfos const tfInstClearedFlags = {tfHasMeta, tfUnresolved} -proc checkPartialConstructedType(info: TLineInfo, t: PType) = +proc checkPartialConstructedType(conf: ConfigRef; info: TLineInfo, t: PType) = if tfAcyclic in t.flags and skipTypes(t, abstractInst).kind != tyObject: - localError(info, errInvalidPragmaX, "acyclic") + localError(conf, info, "invalid pragma: acyclic") elif t.kind in {tyVar, tyLent} and t.sons[0].kind in {tyVar, tyLent}: - localError(info, errVarVarTypeNotAllowed) + localError(conf, info, "type 'var var' is not allowed") -proc checkConstructedType*(info: TLineInfo, typ: PType) = +proc checkConstructedType*(conf: ConfigRef; info: TLineInfo, typ: PType) = var t = typ.skipTypes({tyDistinct}) if t.kind in tyTypeClasses: discard elif tfAcyclic in t.flags and skipTypes(t, abstractInst).kind != tyObject: - localError(info, errInvalidPragmaX, "acyclic") + localError(conf, info, "invalid pragma: acyclic") elif t.kind in {tyVar, tyLent} and t.sons[0].kind in {tyVar, tyLent}: - localError(info, errVarVarTypeNotAllowed) - elif computeSize(t) == szIllegalRecursion: - localError(info, errIllegalRecursionInTypeX, typeToString(t)) + localError(conf, info, "type 'var var' is not allowed") + elif computeSize(conf, t) == szIllegalRecursion: + localError(conf, info, "illegal recursion in type '" & typeToString(t) & "'") when false: if t.kind == tyObject and t.sons[0] != nil: if t.sons[0].kind != tyObject or tfFinal in t.sons[0].flags: @@ -36,12 +37,11 @@ proc checkConstructedType*(info: TLineInfo, typ: PType) = proc searchInstTypes*(key: PType): PType = let genericTyp = key.sons[0] - internalAssert genericTyp.kind == tyGenericBody and - key.sons[0] == genericTyp and - genericTyp.sym != nil + if not (genericTyp.kind == tyGenericBody and + key.sons[0] == genericTyp and genericTyp.sym != nil): return - if genericTyp.sym.typeInstCache == nil: - return + when not defined(nimNoNilSeqs): + if genericTyp.sym.typeInstCache == nil: return for inst in genericTyp.sym.typeInstCache: if inst.id == key.id: return inst @@ -144,17 +144,6 @@ proc isTypeParam(n: PNode): bool = (n.sym.kind == skGenericParam or (n.sym.kind == skType and sfFromGeneric in n.sym.flags)) -proc hasGenericArguments*(n: PNode): bool = - if n.kind == nkSym: - return n.sym.kind == skGenericParam or - tfInferrableStatic in n.sym.typ.flags or - (n.sym.kind == skType and - n.sym.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} != {}) - else: - for i in 0..<n.safeLen: - if hasGenericArguments(n.sons[i]): return true - return false - proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode = # This is needed for tgenericshardcases # It's possible that a generic param will be used in a proc call to a @@ -195,19 +184,19 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0): PNode = var branch: PNode = nil # the branch to take for i in countup(0, sonsLen(n) - 1): var it = n.sons[i] - if it == nil: illFormedAst(n) + if it == nil: illFormedAst(n, cl.c.config) case it.kind of nkElifBranch: - checkSonsLen(it, 2) + checkSonsLen(it, 2, cl.c.config) var cond = prepareNode(cl, it.sons[0]) var e = cl.c.semConstExpr(cl.c, cond) if e.kind != nkIntLit: - internalError(e.info, "ReplaceTypeVarsN: when condition not a bool") + internalError(cl.c.config, e.info, "ReplaceTypeVarsN: when condition not a bool") if e.intVal != 0 and branch == nil: branch = it.sons[1] of nkElse: - checkSonsLen(it, 1) + checkSonsLen(it, 1, cl.c.config) if branch == nil: branch = it.sons[0] - else: illFormedAst(n) + else: illFormedAst(n, cl.c.config) if branch != nil: result = replaceTypeVarsN(cl, branch) else: @@ -231,6 +220,17 @@ proc replaceTypeVarsS(cl: var TReplTypeVars, s: PSym): PSym = # symbol is not our business: if cl.owner != nil and s.owner != cl.owner: return s + + # XXX: Bound symbols in default parameter expressions may reach here. + # We cannot process them, becase `sym.n` may point to a proc body with + # cyclic references that will lead to an infinite recursion. + # Perhaps we should not use a black-list here, but a whitelist instead + # (e.g. skGenericParam and skType). + # Note: `s.magic` may be `mType` in an example such as: + # proc foo[T](a: T, b = myDefault(type(a))) + if s.kind == skProc or s.magic != mNone: + return s + #result = PSym(idTableGet(cl.symMap, s)) #if result == nil: result = copySym(s, false) @@ -244,14 +244,14 @@ proc lookupTypeVar(cl: var TReplTypeVars, t: PType): PType = result = cl.typeMap.lookup(t) if result == nil: if cl.allowMetaTypes or tfRetType in t.flags: return - localError(t.sym.info, errCannotInstantiateX, typeToString(t)) + localError(cl.c.config, t.sym.info, "cannot instantiate: '" & typeToString(t) & "'") result = errorType(cl.c) # In order to prevent endless recursions, we must remember # this bad lookup and replace it with errorType everywhere. # These code paths are only active in "nim check" cl.typeMap.put(t, result) elif result.kind == tyGenericParam and not cl.allowMetaTypes: - internalError(cl.info, "substitution with generic parameter") + internalError(cl.c.config, cl.info, "substitution with generic parameter") proc instCopyType*(cl: var TReplTypeVars, t: PType): PType = # XXX: relying on allowMetaTypes is a kludge @@ -278,7 +278,8 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType = # is difficult to handle: const eqFlags = eqTypeFlags + {tfGcSafe} var body = t.sons[0] - if body.kind != tyGenericBody: internalError(cl.info, "no generic body") + if body.kind != tyGenericBody: + internalError(cl.c.config, cl.info, "no generic body") var header: PType = t # search for some instantiation here: if cl.allowMetaTypes: @@ -351,7 +352,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType = # handleGenericInvocation will handle the alias-to-alias-to-alias case if newbody.isGenericAlias: newbody = newbody.skipGenericAlias rawAddSon(result, newbody) - checkPartialConstructedType(cl.info, newbody) + checkPartialConstructedType(cl.c.config, cl.info, newbody) let dc = newbody.deepCopy if cl.allowMetaTypes == false: if dc != nil and sfFromGeneric notin newbody.deepCopy.flags: @@ -368,10 +369,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType = assert newbody.kind in {tyRef, tyPtr} assert newbody.lastSon.typeInst == nil newbody.lastSon.typeInst = result - if newDestructors: - cl.c.typesWithOps.add((newbody, result)) - else: - typeBound(cl.c, newbody, result, assignment, cl.info) + cl.c.typesWithOps.add((newbody, result)) let methods = skipTypes(bbody, abstractPtrs).methods for col, meth in items(methods): # we instantiate the known methods belonging to that type, this causes @@ -417,7 +415,7 @@ proc propagateFieldFlags(t: PType, n: PNode) = # The type must be fully instantiated! if n.isNil: return - internalAssert n.kind != nkRecWhen + #internalAssert n.kind != nkRecWhen case n.kind of nkSym: propagateToOwner(t, n.sym.typ) @@ -454,12 +452,16 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = result.kind = tyUserTypeClassInst of tyGenericBody: - localError(cl.info, errCannotInstantiateX, typeToString(t)) + localError(cl.c.config, cl.info, "cannot instantiate: '" & typeToString(t) & "'") result = errorType(cl.c) #result = replaceTypeVarsT(cl, lastSon(t)) of tyFromExpr: if cl.allowMetaTypes: return + # This assert is triggered when a tyFromExpr was created in a cyclic + # way. You should break the cycle at the point of creation by introducing + # a call such as: `n.typ = makeTypeFromExpr(c, n.copyTree)` + # Otherwise, the cycle will be fatal for the prepareNode call below assert t.n.typ != t var n = prepareNode(cl, t.n) if n.kind != nkEmpty: @@ -529,7 +531,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = case result.kind of tyArray: let idx = result.sons[0] - internalAssert idx.kind != tyStatic + internalAssert cl.c.config, idx.kind != tyStatic of tyObject, tyTuple: propagateFieldFlags(result, result.n) @@ -541,7 +543,6 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = else: discard proc instAllTypeBoundOp*(c: PContext, info: TLineInfo) = - if not newDestructors: return var i = 0 while i < c.typesWithOps.len: let (newty, oldty) = c.typesWithOps[i] @@ -565,35 +566,35 @@ proc replaceTypesInBody*(p: PContext, pt: TIdTable, n: PNode; var typeMap = initLayeredTypeMap(pt) var cl = initTypeVars(p, addr(typeMap), n.info, owner) cl.allowMetaTypes = allowMetaTypes - pushInfoContext(n.info) + pushInfoContext(p.config, n.info) result = replaceTypeVarsN(cl, n) - popInfoContext() + popInfoContext(p.config) proc replaceTypesForLambda*(p: PContext, pt: TIdTable, n: PNode; original, new: PSym): PNode = var typeMap = initLayeredTypeMap(pt) var cl = initTypeVars(p, addr(typeMap), n.info, original) idTablePut(cl.symMap, original, new) - pushInfoContext(n.info) + pushInfoContext(p.config, n.info) result = replaceTypeVarsN(cl, n) - popInfoContext() + popInfoContext(p.config) proc generateTypeInstance*(p: PContext, pt: TIdTable, info: TLineInfo, t: PType): PType = var typeMap = initLayeredTypeMap(pt) var cl = initTypeVars(p, addr(typeMap), info, nil) - pushInfoContext(info) + pushInfoContext(p.config, info) result = replaceTypeVarsT(cl, t) - popInfoContext() + popInfoContext(p.config) proc prepareMetatypeForSigmatch*(p: PContext, pt: TIdTable, info: TLineInfo, t: PType): PType = var typeMap = initLayeredTypeMap(pt) var cl = initTypeVars(p, addr(typeMap), info, nil) cl.allowMetaTypes = true - pushInfoContext(info) + pushInfoContext(p.config, info) result = replaceTypeVarsT(cl, t) - popInfoContext() + popInfoContext(p.config) template generateTypeInstance*(p: PContext, pt: TIdTable, arg: PNode, t: PType): untyped = diff --git a/compiler/service.nim b/compiler/service.nim deleted file mode 100644 index ac04b7860..000000000 --- a/compiler/service.nim +++ /dev/null @@ -1,87 +0,0 @@ -# -# -# The Nim Compiler -# (c) Copyright 2015 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## Implements the "compiler as a service" feature. - -import - times, commands, options, msgs, nimconf, - extccomp, strutils, os, platform, parseopt, idents - -when useCaas: - import net - -# We cache modules and the dependency graph. However, we don't check for -# file changes but expect the client to tell us about them, otherwise the -# repeated hash calculations may turn out to be too slow. - -var - curCaasCmd* = "" - lastCaasCmd* = "" - # in caas mode, the list of defines and options will be given at start-up? - # it's enough to check that the previous compilation command is the same? - -proc processCmdLine*(pass: TCmdLinePass, cmd: string) = - var p = parseopt.initOptParser(cmd) - var argsCount = 0 - while true: - parseopt.next(p) - case p.kind - of cmdEnd: break - of cmdLongoption, cmdShortOption: - if p.key == " ": - p.key = "-" - if processArgument(pass, p, argsCount): break - else: - processSwitch(pass, p) - of cmdArgument: - if processArgument(pass, p, argsCount): break - if pass == passCmd2: - if optRun notin gGlobalOptions and arguments != "" and options.command.normalize != "run": - rawMessage(errArgsNeedRunOption, []) - -proc serve*(cache: IdentCache; action: proc (cache: IdentCache){.nimcall.}) = - template execute(cmd) = - curCaasCmd = cmd - processCmdLine(passCmd2, cmd) - action(cache) - gErrorCounter = 0 - - let typ = getConfigVar("server.type") - case typ - of "stdin": - while true: - var line = stdin.readLine.string - if line == "quit": quit() - execute line - echo "" - flushFile(stdout) - - of "tcp", "": - when useCaas: - var server = newSocket() - let p = getConfigVar("server.port") - let port = if p.len > 0: parseInt(p).Port else: 6000.Port - server.bindAddr(port, getConfigVar("server.address")) - var inp = "".TaintedString - server.listen() - var stdoutSocket = newSocket() - msgs.writelnHook = proc (line: string) = - stdoutSocket.send(line & "\c\L") - - while true: - accept(server, stdoutSocket) - stdoutSocket.readLine(inp) - execute inp.string - stdoutSocket.send("\c\L") - stdoutSocket.close() - else: - msgQuit "server.type not supported; compiler built without caas support" - else: - echo "Invalid server.type:", typ - msgQuit 1 diff --git a/compiler/sighashes.nim b/compiler/sighashes.nim index 5d6b5978d..8f95175e5 100644 --- a/compiler/sighashes.nim +++ b/compiler/sighashes.nim @@ -9,10 +9,10 @@ ## Computes hash values for routine (proc, method etc) signatures. -import ast, md5 +import ast, md5, tables, ropes from hashes import Hash from astalgo import debug -from types import typeToString, preferDesc +import types from strutils import startsWith, contains when false: @@ -148,19 +148,23 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) = of tyGenericInvocation: for i in countup(0, sonsLen(t) - 1): c.hashType t.sons[i], flags - return of tyDistinct: if CoType in flags: c.hashType t.lastSon, flags else: c.hashSym(t.sym) - return - of tyAlias, tyGenericInst, tyUserTypeClasses: + of tyGenericInst: + if sfInfixCall in t.base.sym.flags: + # This is an imported C++ generic type. + # We cannot trust the `lastSon` to hold a properly populated and unique + # value for each instantiation, so we hash the generic parameters here: + let normalizedType = t.skipGenericAlias + for i in 0 .. normalizedType.len - 2: + c.hashType t.sons[i], flags + else: + c.hashType t.lastSon, flags + of tyAlias, tySink, tyUserTypeClasses, tyInferred: c.hashType t.lastSon, flags - return - else: - discard - case t.kind of tyBool, tyChar, tyInt..tyUInt64: # no canonicalization for integral types, so that e.g. ``pid_t`` is # produced instead of ``NI``: @@ -168,11 +172,12 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) = if t.sym != nil and {sfImportc, sfExportc} * t.sym.flags != {}: c.hashSym(t.sym) of tyObject, tyEnum: - c &= char(t.kind) if t.typeInst != nil: assert t.typeInst.kind == tyGenericInst - for i in countup(1, sonsLen(t.typeInst) - 2): + for i in countup(0, sonsLen(t.typeInst) - 2): c.hashType t.typeInst.sons[i], flags + return + c &= char(t.kind) # Every cyclic type in Nim need to be constructed via some 't.sym', so this # is actually safe without an infinite recursion check: if t.sym != nil: @@ -184,18 +189,19 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) = c.hashTypeSym(t.sym) else: c.hashSym(t.sym) - if sfAnon in t.sym.flags: + if {sfAnon, sfGenSym} * t.sym.flags != {}: # generated object names can be identical, so we need to # disambiguate furthermore by hashing the field types and names: # mild hack to prevent endless recursions (makes nimforum compile again): - excl t.sym.flags, sfAnon + let oldFlags = t.sym.flags + t.sym.flags = t.sym.flags - {sfAnon, sfGenSym} let n = t.n for i in 0 ..< n.len: assert n[i].kind == nkSym let s = n[i].sym c.hashSym s c.hashType s.typ, flags - incl t.sym.flags, sfAnon + t.sym.flags = oldFlags else: c &= t.id if t.len > 0 and t.sons[0] != nil: @@ -326,3 +332,32 @@ proc hashOwner*(s: PSym): SigHash = c &= m.name.s md5Final c, result.Md5Digest + +proc idOrSig*(s: PSym, currentModule: string, + sigCollisions: var CountTable[SigHash]): Rope = + if s.kind in routineKinds and s.typ != nil: + # signatures for exported routines are reliable enough to + # produce a unique name and this means produced C++ is more stable wrt + # Nim changes: + let sig = hashProc(s) + result = rope($sig) + #let m = if s.typ.callConv != ccInline: findPendingModule(m, s) else: m + let counter = sigCollisions.getOrDefault(sig) + #if sigs == "_jckmNePK3i2MFnWwZlp6Lg" and s.name.s == "contains": + # echo "counter ", counter, " ", s.id + if counter != 0: + result.add "_" & rope(counter+1) + # this minor hack is necessary to make tests/collections/thashes compile. + # The inlined hash function's original module is ambiguous so we end up + # generating duplicate names otherwise: + if s.typ.callConv == ccInline: + result.add rope(currentModule) + sigCollisions.inc(sig) + else: + let sig = hashNonProc(s) + result = rope($sig) + let counter = sigCollisions.getOrDefault(sig) + if counter != 0: + result.add "_" & rope(counter+1) + sigCollisions.inc(sig) + diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 9f802a10e..407e34619 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -13,9 +13,9 @@ import intsets, ast, astalgo, semdata, types, msgs, renderer, lookups, semtypinst, magicsys, condsyms, idents, lexer, options, parampatterns, strutils, trees, - nimfix.pretty + linter, lineinfos -when not defined(noDocgen): +when defined(booting) or defined(nimsuggest): import docgen type @@ -26,6 +26,7 @@ type sym*: PSym unmatchedVarParam*, firstMismatch*: int diagnostics*: seq[string] + enabled*: bool CandidateErrors* = seq[CandidateError] @@ -60,7 +61,8 @@ type # matching. they will be reset if the matching # is not successful. may replace the bindings # table in the future. - diagnostics*: seq[string] # when this is not nil, the matching process + diagnostics*: seq[string] # \ + # when diagnosticsEnabled, the matching process # will collect extra diagnostics that will be # displayed to the user. # triggered when overload resolution fails @@ -70,6 +72,7 @@ type inheritancePenalty: int # to prefer closest father object type firstMismatch*: int # position of the first type mismatch for # better error messages + diagnosticsEnabled*: bool TTypeRelFlag* = enum trDontBind @@ -96,7 +99,7 @@ type const isNilConversion = isConvertible # maybe 'isIntConv' fits better? -proc markUsed*(info: TLineInfo, s: PSym; usageSym: var PSym) +proc markUsed*(conf: ConfigRef; info: TLineInfo, s: PSym; usageSym: var PSym) template hasFauxMatch*(c: TCandidate): bool = c.fauxMatch != tyNone @@ -124,7 +127,8 @@ proc put(c: var TCandidate, key, val: PType) {.inline.} = idTablePut(c.bindings, key, val.skipIntLit) proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym, - binding: PNode, calleeScope = -1, diagnostics = false) = + binding: PNode, calleeScope = -1, + diagnosticsEnabled = false) = initCandidateAux(ctx, c, callee.typ) c.calleeSym = callee if callee.kind in skProcKinds and calleeScope == -1: @@ -139,7 +143,8 @@ proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym, c.calleeScope = 1 else: c.calleeScope = calleeScope - c.diagnostics = if diagnostics: @[] else: nil + c.diagnostics = @[] # if diagnosticsEnabled: @[] else: nil + c.diagnosticsEnabled = diagnosticsEnabled c.magic = c.calleeSym.magic initIdTable(c.bindings) if binding != nil and callee.kind in routineKinds: @@ -147,13 +152,13 @@ proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym, for i in 1..min(sonsLen(typeParams), sonsLen(binding)-1): var formalTypeParam = typeParams.sons[i-1].typ var bound = binding[i].typ - internalAssert bound != nil - if formalTypeParam.kind == tyTypeDesc: - if bound.kind != tyTypeDesc: - bound = makeTypeDesc(ctx, bound) - else: - bound = bound.skipTypes({tyTypeDesc}) - put(c, formalTypeParam, bound) + if bound != nil: + if formalTypeParam.kind == tyTypeDesc: + if bound.kind != tyTypeDesc: + bound = makeTypeDesc(ctx, bound) + else: + bound = bound.skipTypes({tyTypeDesc}) + put(c, formalTypeParam, bound) proc newCandidate*(ctx: PContext, callee: PSym, binding: PNode, calleeScope = -1): TCandidate = @@ -248,7 +253,7 @@ proc complexDisambiguation(a, b: PType): int = result = x - y proc writeMatches*(c: TCandidate) = - echo "Candidate '", c.calleeSym.name.s, "' at ", c.calleeSym.info + echo "Candidate '", c.calleeSym.name.s, "' at ", c.c.config $ c.calleeSym.info echo " exact matches: ", c.exactMatches echo " generic matches: ", c.genericMatches echo " subtype matches: ", c.subtypeMatches @@ -357,8 +362,8 @@ proc concreteType(c: TCandidate, t: PType): PType = # proc sort[T](cmp: proc(a, b: T): int = cmp) if result.kind != tyGenericParam: break of tyGenericInvocation: - internalError("cannot resolve type: " & typeToString(t)) result = t + doAssert(false, "cannot resolve type: " & typeToString(t)) else: result = t # Note: empty is valid here @@ -371,8 +376,10 @@ proc handleRange(f, a: PType, min, max: TTypeKind): TTypeRelation = if k == f.kind: result = isSubrange elif k == tyInt and f.kind in {tyRange, tyInt8..tyInt64, tyUInt..tyUInt64} and - isIntLit(ab) and ab.n.intVal >= firstOrd(f) and - ab.n.intVal <= lastOrd(f): + isIntLit(ab) and ab.n.intVal >= firstOrd(nil, f) and + ab.n.intVal <= lastOrd(nil, f): + # passing 'nil' to firstOrd/lastOrd here as type checking rules should + # not depent on the target integer size configurations! # integer literal in the proper range; we want ``i16 + 4`` to stay an # ``int16`` operation so we declare the ``4`` pseudo-equal to int16 result = isFromIntLit @@ -382,8 +389,10 @@ proc handleRange(f, a: PType, min, max: TTypeKind): TTypeRelation = result = isConvertible elif a.kind == tyRange and a.sons[0].kind in {tyInt..tyInt64, tyUInt8..tyUInt32} and - a.n[0].intVal >= firstOrd(f) and - a.n[1].intVal <= lastOrd(f): + a.n[0].intVal >= firstOrd(nil, f) and + a.n[1].intVal <= lastOrd(nil, f): + # passing 'nil' to firstOrd/lastOrd here as type checking rules should + # not depent on the target integer size configurations! result = isConvertible else: result = isNone #elif f.kind == tyInt and k in {tyInt..tyInt32}: result = isIntConv @@ -514,8 +523,8 @@ proc recordRel(c: var TCandidate, f, a: PType): TTypeRelation = if f.n != nil and a.n != nil: for i in countup(0, sonsLen(f.n) - 1): # check field names: - if f.n.sons[i].kind != nkSym: internalError(f.n.info, "recordRel") - elif a.n.sons[i].kind != nkSym: internalError(a.n.info, "recordRel") + if f.n.sons[i].kind != nkSym: return isNone + elif a.n.sons[i].kind != nkSym: return isNone else: var x = f.n.sons[i].sym var y = a.n.sons[i].sym @@ -526,6 +535,12 @@ proc recordRel(c: var TCandidate, f, a: PType): TTypeRelation = proc allowsNil(f: PType): TTypeRelation {.inline.} = result = if tfNotNil notin f.flags: isSubtype else: isNone +proc allowsNilDeprecated(c: TCandidate, f: PType): TTypeRelation = + if optNilSeqs in c.c.config.options: + result = allowsNil(f) + else: + result = isNone + proc inconsistentVarTypes(f, a: PType): bool {.inline.} = result = f.kind != a.kind and (f.kind in {tyVar, tyLent} or a.kind in {tyVar, tyLent}) @@ -560,7 +575,10 @@ proc procParamTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = # signature. There is a change that the target # type is already fully-determined, so we are # going to try resolve it - f = generateTypeInstance(c.c, c.bindings, c.call.info, f) + if c.call != nil: + f = generateTypeInstance(c.c, c.bindings, c.call.info, f) + else: + f = nil if f == nil or f.isMetaType: # no luck resolving the type, so the inference fails return isBothMetaConvertible @@ -607,7 +625,7 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = if tfNoSideEffect in f.flags and tfNoSideEffect notin a.flags: return isNone elif tfThread in f.flags and a.flags * {tfThread, tfNoSideEffect} == {} and - optThreadAnalysis in gGlobalOptions: + optThreadAnalysis in c.c.config.globalOptions: # noSideEffect implies ``tfThread``! return isNone elif f.flags * {tfIterator} != a.flags * {tfIterator}: @@ -628,20 +646,22 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = else: discard proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} = - let - a0 = firstOrd(a) - a1 = lastOrd(a) - f0 = firstOrd(f) - f1 = lastOrd(f) - if a0 == f0 and a1 == f1: - result = isEqual - elif a0 >= f0 and a1 <= f1: - result = isConvertible - elif a0 <= f1 and f0 <= a1: - # X..Y and C..D overlap iff (X <= D and C <= Y) - result = isConvertible + template checkRange[T](a0, a1, f0, f1: T): TTypeRelation = + if a0 == f0 and a1 == f1: + isEqual + elif a0 >= f0 and a1 <= f1: + isConvertible + elif a0 <= f1 and f0 <= a1: + # X..Y and C..D overlap iff (X <= D and C <= Y) + isConvertible + else: + isNone + + if f.isOrdinalType: + checkRange(firstOrd(nil, a), lastOrd(nil, a), firstOrd(nil, f), lastOrd(nil, f)) else: - result = isNone + checkRange(firstFloat(a), lastFloat(a), firstFloat(f), lastFloat(f)) + proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = var @@ -656,7 +676,7 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = matchedConceptContext.prev = prevMatchedConcept matchedConceptContext.depth = prevMatchedConcept.depth + 1 if prevMatchedConcept.depth > 4: - localError(body.info, $body & " too nested for type matching") + localError(m.c.graph.config, body.info, $body & " too nested for type matching") return nil openScope(c) @@ -681,7 +701,7 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = if alreadyBound != nil: typ = alreadyBound template paramSym(kind): untyped = - newSym(kind, typeParamName, typeClass.sym, typeClass.sym.info) + newSym(kind, typeParamName, typeClass.sym, typeClass.sym.info, {}) block addTypeParam: for prev in typeParams: @@ -713,21 +733,21 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = addDecl(c, param) var - oldWriteHook: type(writelnHook) + oldWriteHook: type(m.c.config.writelnHook) diagnostics: seq[string] errorPrefix: string flags: TExprFlags = {} - collectDiagnostics = m.diagnostics != nil or + collectDiagnostics = m.diagnosticsEnabled or sfExplain in typeClass.sym.flags if collectDiagnostics: - oldWriteHook = writelnHook + oldWriteHook = m.c.config.writelnHook # XXX: we can't write to m.diagnostics directly, because # Nim doesn't support capturing var params in closures diagnostics = @[] flags = {efExplain} - writelnHook = proc (s: string) = - if errorPrefix == nil: errorPrefix = typeClass.sym.name.s & ":" + m.c.config.writelnHook = proc (s: string) = + if errorPrefix.len == 0: errorPrefix = typeClass.sym.name.s & ":" let msg = s.replace("Error:", errorPrefix) if oldWriteHook != nil: oldWriteHook msg diagnostics.add msg @@ -735,8 +755,10 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = var checkedBody = c.semTryExpr(c, body.copyTree, flags) if collectDiagnostics: - writelnHook = oldWriteHook - for msg in diagnostics: m.diagnostics.safeAdd msg + m.c.config.writelnHook = oldWriteHook + for msg in diagnostics: + m.diagnostics.safeAdd msg + m.diagnosticsEnabled = true if checkedBody == nil: return nil @@ -753,19 +775,20 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = result.n = checkedBody -proc shouldSkipDistinct(rules: PNode, callIdent: PIdent): bool = +proc shouldSkipDistinct(m: TCandidate; rules: PNode, callIdent: PIdent): bool = + # XXX This is bad as 'considerQuotedIdent' can produce an error! if rules.kind == nkWith: for r in rules: - if r.considerQuotedIdent == callIdent: return true + if considerQuotedIdent(m.c, r) == callIdent: return true return false else: for r in rules: - if r.considerQuotedIdent == callIdent: return false + if considerQuotedIdent(m.c, r) == callIdent: return false return true -proc maybeSkipDistinct(t: PType, callee: PSym): PType = +proc maybeSkipDistinct(m: TCandidate; t: PType, callee: PSym): PType = if t != nil and t.kind == tyDistinct and t.n != nil and - shouldSkipDistinct(t.n, callee.name): + shouldSkipDistinct(m, t.n, callee.name): result = t.base else: result = t @@ -841,6 +864,10 @@ proc inferStaticParam*(c: var TCandidate, lhs: PNode, rhs: BiggestInt): bool = if lhs[2].kind == nkIntLit: return inferStaticParam(c, lhs[1], rhs shl lhs[2].intVal) + of mAshrI: + if lhs[2].kind == nkIntLit: + return inferStaticParam(c, lhs[1], ashr(rhs, lhs[2].intVal)) + of mUnaryMinusI: return inferStaticParam(c, lhs[1], -rhs) @@ -861,11 +888,11 @@ proc inferStaticParam*(c: var TCandidate, lhs: PNode, rhs: BiggestInt): bool = return false -proc failureToInferStaticParam(n: PNode) = +proc failureToInferStaticParam(conf: ConfigRef; n: PNode) = let staticParam = n.findUnresolvedStatic let name = if staticParam != nil: staticParam.sym.name.s else: "unknown" - localError(n.info, errCannotInferStaticParam, name) + localError(conf, n.info, "cannot infer the value of the static param '" & name & "'") proc inferStaticsInRange(c: var TCandidate, inferred, concrete: PType): TTypeRelation = @@ -879,17 +906,17 @@ proc inferStaticsInRange(c: var TCandidate, if inferStaticParam(c, exp, rhs): return isGeneric else: - failureToInferStaticParam exp + failureToInferStaticParam(c.c.config, exp) if lowerBound.kind == nkIntLit: if upperBound.kind == nkIntLit: - if lengthOrd(concrete) == upperBound.intVal - lowerBound.intVal + 1: + if lengthOrd(c.c.config, concrete) == upperBound.intVal - lowerBound.intVal + 1: return isGeneric else: return isNone - doInferStatic(upperBound, lengthOrd(concrete) + lowerBound.intVal - 1) + doInferStatic(upperBound, lengthOrd(c.c.config, concrete) + lowerBound.intVal - 1) elif upperBound.kind == nkIntLit: - doInferStatic(lowerBound, upperBound.intVal + 1 - lengthOrd(concrete)) + doInferStatic(lowerBound, upperBound.intVal + 1 - lengthOrd(c.c.config, concrete)) template subtypeCheck() = if result <= isSubrange and f.lastSon.skipTypes(abstractInst).kind in {tyRef, tyPtr, tyVar, tyLent}: @@ -966,7 +993,8 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, useTypeLoweringRuleInTypeClass = c.c.matchedConcept != nil and not c.isNoCall and f.kind != tyTypeDesc and - tfExplicit notin aOrig.flags + tfExplicit notin aOrig.flags and + tfConceptMatchedTypeSym notin aOrig.flags aOrig = if useTypeLoweringRuleInTypeClass: aOrig.skipTypes({tyTypeDesc}) @@ -992,7 +1020,8 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, of tyStatic: candidate = computedType else: - localError(f.n.info, errTypeExpected) + # XXX What is this non-sense? Error reporting in signature matching? + discard "localError(f.n.info, errTypeExpected)" else: discard @@ -1006,7 +1035,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, template doBind: bool = trDontBind notin flags # var and static arguments match regular modifier-free types - var a = aOrig.skipTypes({tyStatic, tyVar, tyLent}).maybeSkipDistinct(c.calleeSym) + var a = maybeSkipDistinct(c, aOrig.skipTypes({tyStatic, tyVar, tyLent}), c.calleeSym) # XXX: Theoretically, maybeSkipDistinct could be called before we even # start the param matching process. This could be done in `prepareOperand` # for example, but unfortunately `prepareOperand` is not called in certain @@ -1076,8 +1105,8 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, else: isNone of tyAnything: - return if f.kind == tyAnything: isGeneric - else: isNone + if f.kind == tyAnything: return isGeneric + else: return isNone of tyUserTypeClass, tyUserTypeClassInst: if c.c.matchedConcept != nil and c.c.matchedConcept.depth <= 4: @@ -1145,7 +1174,8 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, else: fRange = prev let ff = f.sons[1].skipTypes({tyTypeDesc}) - let aa = a.sons[1].skipTypes({tyTypeDesc}) + # This typeDesc rule is wrong, see bug #7331 + let aa = a.sons[1] #.skipTypes({tyTypeDesc}) if f.sons[0].kind != tyGenericParam and aa.kind == tyEmpty: result = isGeneric @@ -1166,7 +1196,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, elif c.c.matchedConcept != nil and aRange.rangeHasUnresolvedStatic: return inferStaticsInRange(c, aRange, f) else: - if lengthOrd(fRange) != lengthOrd(aRange): + if lengthOrd(c.c.config, fRange) != lengthOrd(c.c.config, aRange): result = isNone else: discard of tyOpenArray, tyVarargs: @@ -1230,7 +1260,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, result = isNone elif tfNotNil in f.flags and tfNotNil notin a.flags: result = isNilConversion - of tyNil: result = f.allowsNil + of tyNil: result = allowsNilDeprecated(c, f) else: discard of tyOrdinal: if isOrdinalType(a): @@ -1242,7 +1272,9 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, if result < isGeneric: result = isNone elif a.kind == tyGenericParam: result = isGeneric - of tyForward: internalError("forward type in typeRel()") + of tyForward: + #internalError("forward type in typeRel()") + result = isNone of tyNil: if a.kind == f.kind: result = isEqual of tyTuple: @@ -1313,7 +1345,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, result = isNilConversion else: result = isEqual - of tyNil: result = f.allowsNil + of tyNil: result = allowsNilDeprecated(c, f) else: discard of tyCString: # conversion from string to cstring is automatic: @@ -1330,7 +1362,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, if a.len == 1: let pointsTo = a.sons[0].skipTypes(abstractInst) if pointsTo.kind == tyChar: result = isConvertible - elif pointsTo.kind == tyArray and firstOrd(pointsTo.sons[0]) == 0 and + elif pointsTo.kind == tyArray and firstOrd(nil, pointsTo.sons[0]) == 0 and skipTypes(pointsTo.sons[0], {tyRange}).kind in {tyInt..tyInt64} and pointsTo.sons[1].kind == tyChar: result = isConvertible @@ -1385,8 +1417,13 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, var aAsObject = roota.lastSon - if fKind in {tyRef, tyPtr} and aAsObject.kind == fKind: - aAsObject = aAsObject.base + if fKind in {tyRef, tyPtr}: + if aAsObject.kind == tyObject: + # bug #7600, tyObject cannot be passed + # as argument to tyRef/tyPtr + return isNone + elif aAsObject.kind == fKind: + aAsObject = aAsObject.base if aAsObject.kind == tyObject: let baseType = aAsObject.base @@ -1402,7 +1439,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, of tyGenericBody: considerPreviousT: - if a.kind == tyGenericInst and a.sons[0] == f: + if a == f or a.kind == tyGenericInst and a.sons[0] == f: bindingRet isGeneric let ff = lastSon(f) if ff != nil: @@ -1427,7 +1464,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, (sonsLen(x) - 1 == sonsLen(f)): for i in countup(1, sonsLen(f) - 1): if x.sons[i].kind == tyGenericParam: - internalError("wrong instantiated type!") + internalError(c.c.graph.config, "wrong instantiated type!") elif typeRel(c, f.sons[i], x.sons[i]) <= isSubtype: # Workaround for regression #4589 if f.sons[i].kind != tyTypeDesc: return @@ -1459,7 +1496,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, if x == nil: discard "maybe fine (for eg. a==tyNil)" elif x.kind in {tyGenericInvocation, tyGenericParam}: - internalError("wrong instantiated type!") + internalError(c.c.graph.config, "wrong instantiated type!") else: put(c, f.sons[i], x) @@ -1582,7 +1619,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, if f.sonsLen == 0: result = isGeneric else: - internalAssert a.sons != nil and a.sons.len > 0 + internalAssert c.c.graph.config, a.len > 0 c.typedescMatched = true var aa = a while aa.kind in {tyTypeDesc, tyGenericParam} and aa.len > 0: @@ -1645,13 +1682,17 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, let prev = PType(idTableGet(c.bindings, f)) if prev == nil: if aOrig.kind == tyStatic: - result = typeRel(c, f.lastSon, a) - if result != isNone and f.n != nil: - if not exprStructuralEquivalent(f.n, aOrig.n): - result = isNone + if f.base.kind != tyNone: + result = typeRel(c, f.base, a) + if result != isNone and f.n != nil: + if not exprStructuralEquivalent(f.n, aOrig.n): + result = isNone + else: + result = isGeneric if result != isNone: put(c, f, aOrig) elif aOrig.n != nil and aOrig.n.typ != nil: - result = typeRel(c, f.lastSon, aOrig.n.typ) + result = if f.base.kind != tyNone: typeRel(c, f.lastSon, aOrig.n.typ) + else: isGeneric if result != isNone: var boundType = newTypeWithSons(c.c, tyStatic, @[aOrig.n.typ]) boundType.n = aOrig.n @@ -1686,9 +1727,13 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, # proc foo(T: typedesc, x: T) # when `f` is an unresolved typedesc, `a` could be any # type, so we should not perform this check earlier - if a.kind != tyTypeDesc: return isNone - - if f.base.kind == tyNone: + if a.kind != tyTypeDesc: + if a.kind == tyGenericParam and tfWildcard in a.flags: + # TODO: prevent `a` from matching as a wildcard again + result = isGeneric + else: + result = isNone + elif f.base.kind == tyNone: result = isGeneric else: result = typeRel(c, f.base, a.base) @@ -1724,13 +1769,13 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, if not exprStructuralEquivalent(aOrig.n, reevaluated.typ.n): result = isNone else: - localError(f.n.info, errTypeExpected) + localError(c.c.graph.config, f.n.info, "type expected") result = isNone of tyNone: if a.kind == tyNone: result = isEqual else: - internalError " unknown type kind " & $f.kind + internalError c.c.graph.config, " unknown type kind " & $f.kind proc cmpTypes*(c: PContext, f, a: PType): TTypeRelation = var m: TCandidate @@ -1743,7 +1788,7 @@ proc getInstantiatedType(c: PContext, arg: PNode, m: TCandidate, if result == nil: result = generateTypeInstance(c, m.bindings, arg, f) if result == nil: - internalError(arg.info, "getInstantiatedType") + internalError(c.graph.config, arg.info, "getInstantiatedType") result = errorType(c) proc implicitConv(kind: TNodeKind, f: PType, arg: PNode, m: TCandidate, @@ -1756,8 +1801,8 @@ proc implicitConv(kind: TNodeKind, f: PType, arg: PNode, m: TCandidate, result.typ = errorType(c) else: result.typ = f - if result.typ == nil: internalError(arg.info, "implicitConv") - addSon(result, ast.emptyNode) + if result.typ == nil: internalError(c.graph.config, arg.info, "implicitConv") + addSon(result, c.graph.emptyNode) addSon(result, arg) proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, @@ -1770,20 +1815,29 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, # 'f <- dest' in order to not break the unification: # see tests/tgenericconverter: let srca = typeRel(m, src, a) - if srca notin {isEqual, isGeneric}: continue + if srca notin {isEqual, isGeneric, isSubtype}: continue + + let constraint = c.converters[i].typ.n[1].sym.constraint + if not constraint.isNil and not matchNodeKinds(constraint, arg): + continue let destIsGeneric = containsGenericType(dest) if destIsGeneric: dest = generateTypeInstance(c, m.bindings, arg, dest) let fdest = typeRel(m, f, dest) if fdest in {isEqual, isGeneric}: - markUsed(arg.info, c.converters[i], c.graph.usageSym) + markUsed(c.config, arg.info, c.converters[i], c.graph.usageSym) var s = newSymNode(c.converters[i]) s.typ = c.converters[i].typ s.info = arg.info result = newNodeIT(nkHiddenCallConv, arg.info, dest) addSon(result, s) - addSon(result, copyTree(arg)) + var param: PNode = nil + if srca == isSubtype: + param = implicitConv(nkHiddenSubConv, src, copyTree(arg), m, c) + else: + param = copyTree(arg) + addSon(result, param) inc(m.convMatches) m.genericConverter = srca == isGeneric or destIsGeneric return result @@ -1867,6 +1921,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, result.typ.n = arg return + let oldInheritancePenalty = m.inheritancePenalty var r = typeRel(m, f, a) # This special typing rule for macros and templates is not documented @@ -1881,7 +1936,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, return arg elif f.kind == tyTypeDesc: return arg - elif f.kind == tyStatic: + elif f.kind == tyStatic and arg.typ.n != nil: return arg.typ.n else: return argSemantized # argOrig @@ -1947,7 +2002,8 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, inc(m.genericMatches) if arg.typ == nil: result = arg - elif skipTypes(arg.typ, abstractVar-{tyTypeDesc}).kind == tyTuple: + elif skipTypes(arg.typ, abstractVar-{tyTypeDesc}).kind == tyTuple or + m.inheritancePenalty > oldInheritancePenalty: result = implicitConv(nkHiddenSubConv, f, arg, m, c) elif arg.typ.isEmptyContainer: result = arg.copyTree @@ -1975,7 +2031,10 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, return arg elif a.kind == tyVoid and f.matchesVoidProc and argOrig.kind == nkStmtList: # lift do blocks without params to lambdas - let lifted = c.semExpr(c, newProcNode(nkDo, argOrig.info, argOrig), {}) + let p = c.graph + let lifted = c.semExpr(c, newProcNode(nkDo, argOrig.info, body = argOrig, + params = p.emptyNode, name = p.emptyNode, pattern = p.emptyNode, + genericParams = p.emptyNode, pragmas = p.emptyNode, exceptions = p.emptyNode), {}) if f.kind == tyBuiltInTypeClass: inc m.genericMatches put(m, f, lifted.typ) @@ -1995,6 +2054,14 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, if r == isGeneric: result.typ = getInstantiatedType(c, arg, m, base(f)) m.baseTypeMatch = true + # bug #4799, varargs accepting subtype relation object + elif r == isSubtype: + inc(m.subtypeMatches) + if f.kind == tyTypeDesc: + result = arg + else: + result = implicitConv(nkHiddenSubConv, f, arg, m, c) + m.baseTypeMatch = true else: result = userConvMatch(c, m, base(f), a, arg) if result != nil: m.baseTypeMatch = true @@ -2017,8 +2084,9 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType, y.calleeSym = m.calleeSym z.calleeSym = m.calleeSym var best = -1 - for i in countup(0, sonsLen(arg) - 1): - if arg.sons[i].sym.kind in {skProc, skFunc, skMethod, skConverter, skIterator}: + for i in 0 ..< arg.len: + if arg.sons[i].sym.kind in {skProc, skFunc, skMethod, skConverter, + skIterator, skMacro, skTemplate}: copyCandidate(z, m) z.callee = arg.sons[i].typ if tfUnresolved in z.callee.flags: continue @@ -2047,23 +2115,27 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType, x = z elif cmp == 0: y = z # z is as good as x + if x.state == csEmpty: result = nil elif y.state == csMatch and cmpCandidates(x, y) == 0: if x.state != csMatch: - internalError(arg.info, "x.state is not csMatch") + internalError(m.c.graph.config, arg.info, "x.state is not csMatch") # ambiguous: more than one symbol fits! # See tsymchoice_for_expr as an example. 'f.kind == tyExpr' should match # anyway: - if f.kind == tyExpr: result = arg + if f.kind in {tyExpr, tyStmt}: result = arg else: result = nil else: # only one valid interpretation found: - markUsed(arg.info, arg.sons[best].sym, m.c.graph.usageSym) + markUsed(m.c.config, arg.info, arg.sons[best].sym, m.c.graph.usageSym) styleCheckUse(arg.info, arg.sons[best].sym) result = paramTypesMatchAux(m, f, arg.sons[best].typ, arg.sons[best], argOrig) - + when false: + if m.calleeSym != nil and m.calleeSym.name.s == "[]": + echo m.c.config $ arg.info, " for ", m.calleeSym.name.s, " ", m.c.config $ m.calleeSym.info + writeMatches(m) proc setSon(father: PNode, at: int, son: PNode) = let oldLen = father.len @@ -2100,10 +2172,10 @@ proc prepareOperand(c: PContext; a: PNode): PNode = result = a considerGenSyms(c, result) -proc prepareNamedParam(a: PNode) = +proc prepareNamedParam(a: PNode; c: PContext) = if a.sons[0].kind != nkIdent: var info = a.sons[0].info - a.sons[0] = newIdentNode(considerQuotedIdent(a.sons[0]), info) + a.sons[0] = newIdentNode(considerQuotedIdent(c, a.sons[0]), info) proc arrayConstr(c: PContext, n: PNode): PType = result = newTypeS(tyArray, c) @@ -2156,26 +2228,37 @@ proc matchesAux(c: PContext, n, nOrig: PNode, var formal: PSym = if formalLen > 1: m.callee.n.sons[1].sym else: nil while a < n.len: - if a >= formalLen-1 and formal != nil and formal.typ.isVarargsUntyped: + if a >= formalLen-1 and f < formalLen and m.callee.n[f].typ.isVarargsUntyped: + formal = m.callee.n.sons[f].sym incl(marker, formal.position) - if container.isNil: - container = newNodeIT(nkArgList, n.sons[a].info, arrayConstr(c, n.info)) - setSon(m.call, formal.position + 1, container) + + if n.sons[a].kind == nkHiddenStdConv: + doAssert n.sons[a].sons[0].kind == nkEmpty and + n.sons[a].sons[1].kind == nkArgList and + n.sons[a].sons[1].len == 0 + # Steal the container and pass it along + setSon(m.call, formal.position + 1, n.sons[a].sons[1]) else: - incrIndexType(container.typ) - addSon(container, n.sons[a]) + if container.isNil: + container = newNodeIT(nkArgList, n.sons[a].info, arrayConstr(c, n.info)) + setSon(m.call, formal.position + 1, container) + else: + incrIndexType(container.typ) + addSon(container, n.sons[a]) elif n.sons[a].kind == nkExprEqExpr: # named param # check if m.callee has such a param: - prepareNamedParam(n.sons[a]) + prepareNamedParam(n.sons[a], c) if n.sons[a].sons[0].kind != nkIdent: - localError(n.sons[a].info, errNamedParamHasToBeIdent) + localError(c.config, n.sons[a].info, "named parameter has to be an identifier") m.state = csNoMatch + m.firstMismatch = -a return formal = getSymFromList(m.callee.n, n.sons[a].sons[0].ident, 1) if formal == nil: # no error message! m.state = csNoMatch + m.firstMismatch = -a return if containsOrIncl(marker, formal.position): # already in namedParams, so no match @@ -2186,12 +2269,14 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m.state = csNoMatch return m.baseTypeMatch = false + m.typedescMatched = false n.sons[a].sons[1] = prepareOperand(c, formal.typ, n.sons[a].sons[1]) n.sons[a].typ = n.sons[a].sons[1].typ var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, n.sons[a].sons[1], n.sons[a].sons[1]) if arg == nil: m.state = csNoMatch + m.firstMismatch = a return checkConstraint(n.sons[a].sons[1]) if m.baseTypeMatch: @@ -2212,14 +2297,16 @@ proc matchesAux(c: PContext, n, nOrig: PNode, # we have no formal here to snoop at: n.sons[a] = prepareOperand(c, n.sons[a]) if skipTypes(n.sons[a].typ, abstractVar-{tyTypeDesc}).kind==tyString: - addSon(m.call, implicitConv(nkHiddenStdConv, getSysType(tyCString), - copyTree(n.sons[a]), m, c)) + addSon(m.call, implicitConv(nkHiddenStdConv, + getSysType(c.graph, n.sons[a].info, tyCString), + copyTree(n.sons[a]), m, c)) else: addSon(m.call, copyTree(n.sons[a])) elif formal != nil and formal.typ.kind == tyVarargs: # beware of the side-effects in 'prepareOperand'! So only do it for # varargs matching. See tests/metatype/tstatic_overloading. m.baseTypeMatch = false + m.typedescMatched = false incl(marker, formal.position) n.sons[a] = prepareOperand(c, formal.typ, n.sons[a]) var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, @@ -2236,7 +2323,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, return else: if m.callee.n.sons[f].kind != nkSym: - internalError(n.sons[a].info, "matches") + internalError(c.config, n.sons[a].info, "matches") return formal = m.callee.n.sons[f].sym if containsOrIncl(marker, formal.position) and container.isNil: @@ -2254,6 +2341,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, addSon(container, n.sons[a]) else: m.baseTypeMatch = false + m.typedescMatched = false n.sons[a] = prepareOperand(c, formal.typ, n.sons[a]) var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, n.sons[a], nOrig.sons[a]) @@ -2262,6 +2350,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m.firstMismatch = f return if m.baseTypeMatch: + assert formal.typ.kind == tyVarargs #assert(container == nil) if container.isNil: container = newNodeIT(nkBracket, n.sons[a].info, arrayConstr(c, arg)) @@ -2275,10 +2364,19 @@ proc matchesAux(c: PContext, n, nOrig: PNode, # pick the formal from the end, so that 'x, y, varargs, z' works: f = max(f, formalLen - n.len + a + 1) - else: + elif formal.typ.kind != tyVarargs or container == nil: setSon(m.call, formal.position + 1, arg) inc(f) container = nil + else: + # we end up here if the argument can be converted into the varargs + # formal (eg. seq[T] -> varargs[T]) but we have already instantiated + # a container + assert arg.kind == nkHiddenStdConv + localError(c.config, n.sons[a].info, "cannot convert $1 to $2" % [ + typeToString(n.sons[a].typ), typeToString(formal.typ) ]) + m.state = csNoMatch + return checkConstraint(n.sons[a]) inc(a) @@ -2297,6 +2395,11 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) = if m.magic in {mArrGet, mArrPut}: m.state = csMatch m.call = n + # Note the following doesn't work as it would produce ambiguities. + # Instead we patch system.nim, see bug #8049. + when false: + inc m.genericMatches + inc m.exactMatches return var marker = initIntSet() matchesAux(c, n, nOrig, m, marker) @@ -2308,7 +2411,10 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) = if not containsOrIncl(marker, formal.position): if formal.ast == nil: if formal.typ.kind == tyVarargs: - var container = newNodeIT(nkBracket, n.info, arrayConstr(c, n.info)) + # For consistency with what happens in `matchesAux` select the + # container node kind accordingly + let cnKind = if formal.typ.isVarargsUntyped: nkArgList else: nkBracket + var container = newNodeIT(cnKind, n.info, arrayConstr(c, n.info)) setSon(m.call, formal.position + 1, implicitConv(nkHiddenStdConv, formal.typ, container, m, c)) else: @@ -2317,12 +2423,22 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) = m.firstMismatch = f break else: - # use default value: + if formal.ast.kind == nkEmpty: + # The default param value is set to empty in `instantiateProcType` + # when the type of the default expression doesn't match the type + # of the instantiated proc param: + localError(c.config, m.call.info, + ("The default parameter '$1' has incompatible type " & + "with the explicitly requested proc instantiation") % + formal.name.s) + if nfDefaultRefsParam in formal.ast.flags: + m.call.flags.incl nfDefaultRefsParam var def = copyTree(formal.ast) if def.kind == nkNilLit: def = implicitConv(nkHiddenStdConv, formal.typ, def, m, c) if {tfImplicitTypeParam, tfGenericTypeParam} * formal.typ.flags != {}: put(m, formal.typ, def.typ) + def.flags.incl nfDefaultParam setSon(m.call, formal.position + 1, def) inc(f) # forget all inferred types if the overload matching failed @@ -2330,21 +2446,25 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) = for t in m.inferredTypes: if t.sonsLen > 1: t.sons.setLen 1 -proc argtypeMatches*(c: PContext, f, a: PType): bool = +proc argtypeMatches*(c: PContext, f, a: PType, fromHlo = false): bool = var m: TCandidate initCandidate(c, m, f) - let res = paramTypesMatch(m, f, a, ast.emptyNode, nil) + let res = paramTypesMatch(m, f, a, c.graph.emptyNode, nil) #instantiateGenericConverters(c, res, m) # XXX this is used by patterns.nim too; I think it's better to not # instantiate generic converters for that - result = res != nil + if not fromHlo: + res != nil + else: + # pattern templates do not allow for conversions except from int literal + res != nil and m.convMatches == 0 and m.intConvMatches in [0, 256] proc instTypeBoundOp*(c: PContext; dc: PSym; t: PType; info: TLineInfo; op: TTypeAttachedOp; col: int): PSym {.procvar.} = var m: TCandidate initCandidate(c, m, dc.typ) if col >= dc.typ.len: - localError(info, errGenerated, "cannot instantiate '" & dc.name.s & "'") + localError(c.config, info, "cannot instantiate: '" & dc.name.s & "'") return nil var f = dc.typ.sons[col] @@ -2353,7 +2473,7 @@ proc instTypeBoundOp*(c: PContext; dc: PSym; t: PType; info: TLineInfo; else: if f.kind == tyVar: f = f.lastSon if typeRel(m, f, t) == isNone: - localError(info, errGenerated, "cannot instantiate '" & dc.name.s & "'") + localError(c.config, info, "cannot instantiate: '" & dc.name.s & "'") else: result = c.semGenerateInstance(c, dc, m.bindings, info) if op == attachedDeepCopy: diff --git a/compiler/suggest.nim b/compiler/suggest.nim index af31495aa..f99a2d432 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -32,7 +32,7 @@ # included from sigmatch.nim -import algorithm, prefixmatches +import algorithm, prefixmatches, lineinfos from wordrecg import wDeprecated when defined(nimsuggest): @@ -41,38 +41,13 @@ when defined(nimsuggest): const sep = '\t' -type - Suggest* = ref object - section*: IdeCmd - qualifiedPath*: seq[string] - name*: PIdent # not used beyond sorting purposes; name is also - # part of 'qualifiedPath' - filePath*: string - line*: int # Starts at 1 - column*: int # Starts at 0 - doc*: string # Not escaped (yet) - symkind*: TSymKind - forth*: string # type - quality*: range[0..100] # matching quality - isGlobal*: bool # is a global variable - contextFits*: bool # type/non-type context matches - prefix*: PrefixMatch - scope*, localUsages*, globalUsages*: int # more usages is better - tokenLen*: int - Suggestions* = seq[Suggest] - -var - suggestionResultHook*: proc (result: Suggest) {.closure.} - suggestVersion*: int - suggestMaxResults* = 10_000 - #template sectionSuggest(): expr = "##begin\n" & getStackTrace() & "##end\n" template origModuleName(m: PSym): string = m.name.s proc findDocComment(n: PNode): PNode = if n == nil: return nil - if not isNil(n.comment): return n + if n.comment.len > 0: return n if n.kind in {nkStmtList, nkStmtListExpr, nkObjectTy, nkRecList} and n.len > 0: result = findDocComment(n.sons[0]) if result != nil: return @@ -104,9 +79,9 @@ proc cmpSuggestions(a, b: Suggest): int = cf globalUsages # if all is equal, sort alphabetically for deterministic output, # independent of hashing order: - result = cmp(a.name.s, b.name.s) + result = cmp(a.name[], b.name[]) -proc symToSuggest(s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo; +proc symToSuggest(conf: ConfigRef; s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo; quality: range[0..100]; prefix: PrefixMatch; inTypeContext: bool; scope: int): Suggest = new(result) @@ -117,15 +92,15 @@ proc symToSuggest(s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo; result.prefix = prefix result.contextFits = inTypeContext == (s.kind in {skType, skGenericParam}) result.scope = scope - result.name = s.name + result.name = addr s.name.s when defined(nimsuggest): result.globalUsages = s.allUsages.len var c = 0 for u in s.allUsages: if u.fileIndex == info.fileIndex: inc c result.localUsages = c - result.symkind = s.kind - if optIdeTerse notin gGlobalOptions: + result.symkind = byte s.kind + if optIdeTerse notin conf.globalOptions: result.qualifiedPath = @[] if not isLocal and s.kind != skModule: let ow = s.owner @@ -140,23 +115,24 @@ proc symToSuggest(s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo; result.forth = typeToString(s.typ) else: result.forth = "" - when not defined(noDocgen): + when defined(nimsuggest) and not defined(noDocgen): result.doc = s.extractDocComment let infox = if section in {ideUse, ideHighlight, ideOutline}: info else: s.info - result.filePath = toFullPath(infox) + result.filePath = toFullPath(conf, infox) result.line = toLinenumber(infox) result.column = toColumn(infox) + result.version = conf.suggestVersion proc `$`*(suggest: Suggest): string = result = $suggest.section result.add(sep) if suggest.section == ideHighlight: - if suggest.symkind == skVar and suggest.isGlobal: + if suggest.symkind.TSymKind == skVar and suggest.isGlobal: result.add("skGlobalVar") - elif suggest.symkind == skLet and suggest.isGlobal: + elif suggest.symkind.TSymKind == skLet and suggest.isGlobal: result.add("skGlobalLet") else: - result.add($suggest.symkind) + result.add($suggest.symkind.TSymKind) result.add(sep) result.add($suggest.line) result.add(sep) @@ -164,9 +140,9 @@ proc `$`*(suggest: Suggest): string = result.add(sep) result.add($suggest.tokenLen) else: - result.add($suggest.symkind) + result.add($suggest.symkind.TSymKind) result.add(sep) - if suggest.qualifiedPath != nil: + if suggest.qualifiedPath.len != 0: result.add(suggest.qualifiedPath.join(".")) result.add(sep) result.add(suggest.forth) @@ -177,34 +153,34 @@ proc `$`*(suggest: Suggest): string = result.add(sep) result.add($suggest.column) result.add(sep) - when not defined(noDocgen): + when defined(nimsuggest) and not defined(noDocgen): result.add(suggest.doc.escape) - if suggestVersion == 0: + if suggest.version == 0: result.add(sep) result.add($suggest.quality) if suggest.section == ideSug: result.add(sep) result.add($suggest.prefix) -proc suggestResult(s: Suggest) = - if not isNil(suggestionResultHook): - suggestionResultHook(s) +proc suggestResult(conf: ConfigRef; s: Suggest) = + if not isNil(conf.suggestionResultHook): + conf.suggestionResultHook(s) else: - suggestWriteln($s) + conf.suggestWriteln($s) -proc produceOutput(a: var Suggestions) = - if gIdeCmd in {ideSug, ideCon}: +proc produceOutput(a: var Suggestions; conf: ConfigRef) = + if conf.ideCmd in {ideSug, ideCon}: a.sort cmpSuggestions when defined(debug): # debug code writeStackTrace() - if a.len > suggestMaxResults: a.setLen(suggestMaxResults) - if not isNil(suggestionResultHook): + if a.len > conf.suggestMaxResults: a.setLen(conf.suggestMaxResults) + if not isNil(conf.suggestionResultHook): for s in a: - suggestionResultHook(s) + conf.suggestionResultHook(s) else: for s in a: - suggestWriteln($s) + conf.suggestWriteln($s) proc filterSym(s: PSym; prefix: PNode; res: var PrefixMatch): bool {.inline.} = proc prefixMatch(s: PSym; n: PNode): PrefixMatch = @@ -237,7 +213,7 @@ proc fieldVisible*(c: PContext, f: PSym): bool {.inline.} = proc suggestField(c: PContext, s: PSym; f: PNode; info: TLineInfo; outputs: var Suggestions) = var pm: PrefixMatch if filterSym(s, f, pm) and fieldVisible(c, s): - outputs.add(symToSuggest(s, isLocal=true, ideSug, info, 100, pm, c.inTypeContext > 0, 0)) + outputs.add(symToSuggest(c.config, s, isLocal=true, ideSug, info, 100, pm, c.inTypeContext > 0, 0)) proc getQuality(s: PSym): range[0..100] = if s.typ != nil and s.typ.len > 1: @@ -256,7 +232,7 @@ template wholeSymTab(cond, section: untyped) = let it {.inject.} = item var pm {.inject.}: PrefixMatch if cond: - outputs.add(symToSuggest(it, isLocal = isLocal, section, info, getQuality(it), + outputs.add(symToSuggest(c.config, it, isLocal = isLocal, section, info, getQuality(it), pm, c.inTypeContext > 0, scopeN)) proc suggestSymList(c: PContext, list, f: PNode; info: TLineInfo, outputs: var Suggestions) = @@ -330,7 +306,7 @@ proc suggestEverything(c: PContext, n, f: PNode, outputs: var Suggestions) = for it in items(scope.symbols): var pm: PrefixMatch if filterSym(it, f, pm): - outputs.add(symToSuggest(it, isLocal = isLocal, ideSug, n.info, 0, pm, + outputs.add(symToSuggest(c.config, it, isLocal = isLocal, ideSug, n.info, 0, pm, c.inTypeContext > 0, scopeN)) #if scope == c.topLevelScope and f.isNil: break @@ -340,20 +316,20 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) var typ = n.typ var pm: PrefixMatch when defined(nimsuggest): - if n.kind == nkSym and n.sym.kind == skError and suggestVersion == 0: + if n.kind == nkSym and n.sym.kind == skError and c.config.suggestVersion == 0: # consider 'foo.|' where 'foo' is some not imported module. - let fullPath = findModule(n.sym.name.s, n.info.toFullPath) + let fullPath = findModule(c.config, n.sym.name.s, toFullPath(c.config, n.info)) if fullPath.len == 0: # error: no known module name: typ = nil else: - let m = gImportModule(c.graph, c.module, fullpath.fileInfoIdx, c.cache) + let m = c.graph.importModuleCallback(c.graph, c.module, fileInfoIdx(c.config, fullpath)) if m == nil: typ = nil else: for it in items(n.sym.tab): if filterSym(it, field, pm): - outputs.add(symToSuggest(it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -100)) - outputs.add(symToSuggest(m, isLocal=false, ideMod, n.info, 100, PrefixMatch.None, + outputs.add(symToSuggest(c.config, it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -100)) + outputs.add(symToSuggest(c.config, m, isLocal=false, ideMod, n.info, 100, PrefixMatch.None, c.inTypeContext > 0, -99)) if typ == nil: @@ -363,11 +339,11 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) # all symbols accessible, because we are in the current module: for it in items(c.topLevelScope.symbols): if filterSym(it, field, pm): - outputs.add(symToSuggest(it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -99)) + outputs.add(symToSuggest(c.config, it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -99)) else: for it in items(n.sym.tab): if filterSym(it, field, pm): - outputs.add(symToSuggest(it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -99)) + outputs.add(symToSuggest(c.config, it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -99)) else: # fallback: suggestEverything(c, n, field, outputs) @@ -397,17 +373,17 @@ type TCheckPointResult* = enum cpNone, cpFuzzy, cpExact -proc inCheckpoint*(current: TLineInfo): TCheckPointResult = - if current.fileIndex == gTrackPos.fileIndex: - if current.line == gTrackPos.line and - abs(current.col-gTrackPos.col) < 4: +proc inCheckpoint*(current, trackPos: TLineInfo): TCheckPointResult = + if current.fileIndex == trackPos.fileIndex: + if current.line == trackPos.line and + abs(current.col-trackPos.col) < 4: return cpExact - if current.line >= gTrackPos.line: + if current.line >= trackPos.line: return cpFuzzy -proc isTracked*(current: TLineInfo, tokenLen: int): bool = - if current.fileIndex==gTrackPos.fileIndex and current.line==gTrackPos.line: - let col = gTrackPos.col +proc isTracked*(current, trackPos: TLineInfo, tokenLen: int): bool = + if current.fileIndex==trackPos.fileIndex and current.line==trackPos.line: + let col = trackPos.col if col >= current.col and col <= current.col+tokenLen-1: return true @@ -415,7 +391,7 @@ when defined(nimsuggest): # Since TLineInfo defined a == operator that doesn't include the column, # we map TLineInfo to a unique int here for this lookup table: proc infoToInt(info: TLineInfo): int64 = - info.fileIndex + info.line.int64 shl 32 + info.col.int64 shl 48 + info.fileIndex.int64 + info.line.int64 shl 32 + info.col.int64 shl 48 proc addNoDup(s: PSym; info: TLineInfo) = # ensure nothing gets too slow: @@ -425,30 +401,27 @@ when defined(nimsuggest): if infoB.infoToInt == infoAsInt: return s.allUsages.add(info) -var - lastLineInfo*: TLineInfo - -proc findUsages(info: TLineInfo; s: PSym; usageSym: var PSym) = - if suggestVersion == 1: - if usageSym == nil and isTracked(info, s.name.s.len): +proc findUsages(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym) = + if conf.suggestVersion == 1: + if usageSym == nil and isTracked(info, conf.m.trackPos, s.name.s.len): usageSym = s - suggestResult(symToSuggest(s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0)) + suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0)) elif s == usageSym: - if lastLineInfo != info: - suggestResult(symToSuggest(s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0)) - lastLineInfo = info + if conf.lastLineInfo != info: + suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0)) + conf.lastLineInfo = info when defined(nimsuggest): - proc listUsages*(s: PSym) = + proc listUsages*(conf: ConfigRef; s: PSym) = #echo "usages ", len(s.allUsages) for info in s.allUsages: let x = if info == s.info and info.col == s.info.col: ideDef else: ideUse - suggestResult(symToSuggest(s, isLocal=false, x, info, 100, PrefixMatch.None, false, 0)) + suggestResult(conf, symToSuggest(conf, s, isLocal=false, x, info, 100, PrefixMatch.None, false, 0)) -proc findDefinition(info: TLineInfo; s: PSym) = +proc findDefinition(conf: ConfigRef; info: TLineInfo; s: PSym) = if s.isNil: return - if isTracked(info, s.name.s.len): - suggestResult(symToSuggest(s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0)) + if isTracked(info, conf.m.trackPos, s.name.s.len): + suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0)) suggestQuit() proc ensureIdx[T](x: var T, y: int) = @@ -457,60 +430,69 @@ proc ensureIdx[T](x: var T, y: int) = proc ensureSeq[T](x: var seq[T]) = if x == nil: newSeq(x, 0) -proc suggestSym*(info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true) {.inline.} = +proc suggestSym*(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true) {.inline.} = ## misnamed: should be 'symDeclared' when defined(nimsuggest): - if suggestVersion == 0: - if s.allUsages.isNil: + if conf.suggestVersion == 0: + if s.allUsages.len == 0: s.allUsages = @[info] else: s.addNoDup(info) - if gIdeCmd == ideUse: - findUsages(info, s, usageSym) - elif gIdeCmd == ideDef: - findDefinition(info, s) - elif gIdeCmd == ideDus and s != nil: - if isTracked(info, s.name.s.len): - suggestResult(symToSuggest(s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0)) - findUsages(info, s, usageSym) - elif gIdeCmd == ideHighlight and info.fileIndex == gTrackPos.fileIndex: - suggestResult(symToSuggest(s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0)) - elif gIdeCmd == ideOutline and info.fileIndex == gTrackPos.fileIndex and + if conf.ideCmd == ideUse: + findUsages(conf, info, s, usageSym) + elif conf.ideCmd == ideDef: + findDefinition(conf, info, s) + elif conf.ideCmd == ideDus and s != nil: + if isTracked(info, conf.m.trackPos, s.name.s.len): + suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0)) + findUsages(conf, info, s, usageSym) + elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex: + suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0)) + elif conf.ideCmd == ideOutline and info.fileIndex == conf.m.trackPos.fileIndex and isDecl: - suggestResult(symToSuggest(s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0)) + suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0)) + +proc warnAboutDeprecated(conf: ConfigRef; info: TLineInfo; s: PSym) = + var pragmaNode: PNode -proc warnAboutDeprecated(info: TLineInfo; s: PSym) = if s.kind in routineKinds: - let n = s.ast[pragmasPos] - if n.kind != nkEmpty: - for it in n: - if whichPragma(it) == wDeprecated and it.safeLen == 2 and - it[1].kind in {nkStrLit..nkTripleStrLit}: - message(info, warnDeprecated, it[1].strVal & "; " & s.name.s) - return - message(info, warnDeprecated, s.name.s) - -proc markUsed(info: TLineInfo; s: PSym; usageSym: var PSym) = + pragmaNode = s.ast[pragmasPos] + elif s.kind in {skType}: + # s.ast = nkTypedef / nkPragmaExpr / [nkSym, nkPragma] + pragmaNode = s.ast[0][1] + + doAssert pragmaNode == nil or pragmaNode.kind == nkPragma + + if pragmaNode != nil: + for it in pragmaNode: + if whichPragma(it) == wDeprecated and it.safeLen == 2 and + it[1].kind in {nkStrLit..nkTripleStrLit}: + message(conf, info, warnDeprecated, it[1].strVal & "; " & s.name.s) + return + + message(conf, info, warnDeprecated, s.name.s) + +proc markUsed(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym) = incl(s.flags, sfUsed) if s.kind == skEnumField and s.owner != nil: incl(s.owner.flags, sfUsed) if {sfDeprecated, sfError} * s.flags != {}: - if sfDeprecated in s.flags: warnAboutDeprecated(info, s) - if sfError in s.flags: localError(info, errWrongSymbolX, s.name.s) + if sfDeprecated in s.flags: warnAboutDeprecated(conf, info, s) + if sfError in s.flags: localError(conf, info, "usage of '$1' is a user-defined error" % s.name.s) when defined(nimsuggest): - suggestSym(info, s, usageSym, false) + suggestSym(conf, info, s, usageSym, false) -proc useSym*(sym: PSym; usageSym: var PSym): PNode = +proc useSym*(conf: ConfigRef; sym: PSym; usageSym: var PSym): PNode = result = newSymNode(sym) - markUsed(result.info, sym, usageSym) + markUsed(conf, result.info, sym, usageSym) proc safeSemExpr*(c: PContext, n: PNode): PNode = # use only for idetools support! try: result = c.semExpr(c, n) except ERecoverableError: - result = ast.emptyNode + result = c.graph.emptyNode proc sugExpr(c: PContext, n: PNode, outputs: var Suggestions) = if n.kind == nkDotExpr: @@ -519,14 +501,14 @@ proc sugExpr(c: PContext, n: PNode, outputs: var Suggestions) = # of the next line, so we check the 'field' is actually on the same # line as the object to prevent this from happening: let prefix = if n.len == 2 and n[1].info.line == n[0].info.line and - not gTrackPosAttached: n[1] else: nil + not c.config.m.trackPosAttached: n[1] else: nil suggestFieldAccess(c, obj, prefix, outputs) #if optIdeDebug in gGlobalOptions: # echo "expression ", renderTree(obj), " has type ", typeToString(obj.typ) #writeStackTrace() else: - let prefix = if gTrackPosAttached: nil else: n + let prefix = if c.config.m.trackPosAttached: nil else: n suggestEverything(c, n, prefix, outputs) proc suggestExprNoCheck*(c: PContext, n: PNode) = @@ -534,9 +516,9 @@ proc suggestExprNoCheck*(c: PContext, n: PNode) = if c.compilesContextId > 0: return inc(c.compilesContextId) var outputs: Suggestions = @[] - if gIdeCmd == ideSug: + if c.config.ideCmd == ideSug: sugExpr(c, n, outputs) - elif gIdeCmd == ideCon: + elif c.config.ideCmd == ideCon: if n.kind in nkCallKinds: var a = copyNode(n) var x = safeSemExpr(c, n.sons[0]) @@ -550,15 +532,15 @@ proc suggestExprNoCheck*(c: PContext, n: PNode) = suggestCall(c, a, n, outputs) dec(c.compilesContextId) - if outputs.len > 0 and gIdeCmd in {ideSug, ideCon, ideDef}: - produceOutput(outputs) + if outputs.len > 0 and c.config.ideCmd in {ideSug, ideCon, ideDef}: + produceOutput(outputs, c.config) suggestQuit() proc suggestExpr*(c: PContext, n: PNode) = - if exactEquals(gTrackPos, n.info): suggestExprNoCheck(c, n) + if exactEquals(c.config.m.trackPos, n.info): suggestExprNoCheck(c, n) proc suggestDecl*(c: PContext, n: PNode; s: PSym) = - let attached = gTrackPosAttached + let attached = c.config.m.trackPosAttached if attached: inc(c.inTypeContext) defer: if attached: dec(c.inTypeContext) @@ -570,11 +552,11 @@ proc suggestStmt*(c: PContext, n: PNode) = proc suggestEnum*(c: PContext; n: PNode; t: PType) = var outputs: Suggestions = @[] suggestSymList(c, t.n, nil, n.info, outputs) - produceOutput(outputs) + produceOutput(outputs, c.config) if outputs.len > 0: suggestQuit() proc suggestSentinel*(c: PContext) = - if gIdeCmd != ideSug or c.module.position != gTrackPos.fileIndex: return + if c.config.ideCmd != ideSug or c.module.position != c.config.m.trackPos.fileIndex.int32: return if c.compilesContextId > 0: return inc(c.compilesContextId) var outputs: Suggestions = @[] @@ -587,7 +569,9 @@ proc suggestSentinel*(c: PContext) = for it in items(scope.symbols): var pm: PrefixMatch if filterSymNoOpr(it, nil, pm): - outputs.add(symToSuggest(it, isLocal = isLocal, ideSug, newLineInfo(gTrackPos.fileIndex, -1, -1), 0, PrefixMatch.None, false, scopeN)) + outputs.add(symToSuggest(c.config, it, isLocal = isLocal, ideSug, + newLineInfo(c.config.m.trackPos.fileIndex, -1, -1), 0, + PrefixMatch.None, false, scopeN)) dec(c.compilesContextId) - produceOutput(outputs) + produceOutput(outputs, c.config) diff --git a/compiler/syntaxes.nim b/compiler/syntaxes.nim index 4745b1ac7..069f65eee 100644 --- a/compiler/syntaxes.nim +++ b/compiler/syntaxes.nim @@ -11,17 +11,17 @@ import strutils, llstream, ast, astalgo, idents, lexer, options, msgs, parser, - pbraces, filters, filter_tmpl, renderer + filters, filter_tmpl, renderer, lineinfos type TFilterKind* = enum filtNone, filtTemplate, filtReplace, filtStrip TParserKind* = enum - skinStandard, skinStrongSpaces, skinBraces, skinEndX + skinStandard, skinStrongSpaces, skinEndX const parserNames*: array[TParserKind, string] = ["standard", "strongspaces", - "braces", "endx"] + "endx"] filterNames*: array[TFilterKind, string] = ["none", "stdtmpl", "replace", "strip"] @@ -30,40 +30,37 @@ type skin*: TParserKind parser*: TParser +template config(p: TParsers): ConfigRef = p.parser.lex.config + proc parseAll*(p: var TParsers): PNode = case p.skin of skinStandard, skinStrongSpaces: result = parser.parseAll(p.parser) - of skinBraces: - result = pbraces.parseAll(p.parser) of skinEndX: - internalError("parser to implement") - result = ast.emptyNode + internalError(p.config, "parser to implement") proc parseTopLevelStmt*(p: var TParsers): PNode = case p.skin of skinStandard, skinStrongSpaces: result = parser.parseTopLevelStmt(p.parser) - of skinBraces: - result = pbraces.parseTopLevelStmt(p.parser) of skinEndX: - internalError("parser to implement") - result = ast.emptyNode + internalError(p.config, "parser to implement") proc utf8Bom(s: string): int = - if s[0] == '\xEF' and s[1] == '\xBB' and s[2] == '\xBF': + if s.len >= 3 and s[0] == '\xEF' and s[1] == '\xBB' and s[2] == '\xBF': result = 3 else: result = 0 proc containsShebang(s: string, i: int): bool = - if s[i] == '#' and s[i+1] == '!': + if i+1 < s.len and s[i] == '#' and s[i+1] == '!': var j = i + 2 - while s[j] in Whitespace: inc(j) + while j < s.len and s[j] in Whitespace: inc(j) result = s[j] == '/' -proc parsePipe(filename: string, inputStream: PLLStream; cache: IdentCache): PNode = - result = ast.emptyNode +proc parsePipe(filename: string, inputStream: PLLStream; cache: IdentCache; + config: ConfigRef): PNode = + result = newNode(nkEmpty) var s = llStreamOpen(filename, fmRead) if s != nil: var line = newStringOfCap(80) @@ -74,11 +71,11 @@ proc parsePipe(filename: string, inputStream: PLLStream; cache: IdentCache): PNo discard llStreamReadLine(s, line) i = 0 inc linenumber - if line[i] == '#' and line[i+1] == '?': + if i+1 < line.len and line[i] == '#' and line[i+1] == '?': inc(i, 2) - while line[i] in Whitespace: inc(i) + while i < line.len and line[i] in Whitespace: inc(i) var q: TParser - parser.openParser(q, filename, llStreamOpen(substr(line, i)), cache) + parser.openParser(q, filename, llStreamOpen(substr(line, i)), cache, config) result = parser.parseAll(q) parser.closeParser(q) llStreamClose(s) @@ -89,42 +86,44 @@ proc getFilter(ident: PIdent): TFilterKind = return i result = filtNone -proc getParser(ident: PIdent): TParserKind = +proc getParser(conf: ConfigRef; n: PNode; ident: PIdent): TParserKind = for i in countup(low(TParserKind), high(TParserKind)): if cmpIgnoreStyle(ident.s, parserNames[i]) == 0: return i - rawMessage(errInvalidDirectiveX, ident.s) + localError(conf, n.info, "unknown parser: " & ident.s) -proc getCallee(n: PNode): PIdent = +proc getCallee(conf: ConfigRef; n: PNode): PIdent = if n.kind in nkCallKinds and n.sons[0].kind == nkIdent: result = n.sons[0].ident elif n.kind == nkIdent: result = n.ident else: - rawMessage(errXNotAllowedHere, renderTree(n)) + localError(conf, n.info, "invalid filter: " & renderTree(n)) proc applyFilter(p: var TParsers, n: PNode, filename: string, stdin: PLLStream): PLLStream = - var ident = getCallee(n) + var ident = getCallee(p.config, n) var f = getFilter(ident) case f of filtNone: - p.skin = getParser(ident) + p.skin = getParser(p.config, n, ident) result = stdin of filtTemplate: - result = filterTmpl(stdin, filename, n) + result = filterTmpl(stdin, filename, n, p.config) of filtStrip: - result = filterStrip(stdin, filename, n) + result = filterStrip(p.config, stdin, filename, n) of filtReplace: - result = filterReplace(stdin, filename, n) + result = filterReplace(p.config, stdin, filename, n) if f != filtNone: - if hintCodeBegin in gNotes: - rawMessage(hintCodeBegin, []) - msgWriteln(result.s) - rawMessage(hintCodeEnd, []) + assert p.config != nil + if hintCodeBegin in p.config.notes: + rawMessage(p.config, hintCodeBegin, []) + msgWriteln(p.config, result.s) + rawMessage(p.config, hintCodeEnd, []) proc evalPipe(p: var TParsers, n: PNode, filename: string, start: PLLStream): PLLStream = + assert p.config != nil result = start if n.kind == nkEmpty: return if n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s == "|": @@ -138,31 +137,33 @@ proc evalPipe(p: var TParsers, n: PNode, filename: string, else: result = applyFilter(p, n, filename, result) -proc openParsers*(p: var TParsers, fileIdx: int32, inputstream: PLLStream; - cache: IdentCache) = +proc openParsers*(p: var TParsers, fileIdx: FileIndex, inputstream: PLLStream; + cache: IdentCache; config: ConfigRef) = + assert config != nil var s: PLLStream p.skin = skinStandard - let filename = fileIdx.toFullPathConsiderDirty - var pipe = parsePipe(filename, inputstream, cache) + let filename = toFullPathConsiderDirty(config, fileIdx) + var pipe = parsePipe(filename, inputstream, cache, config) + p.config() = config if pipe != nil: s = evalPipe(p, pipe, filename, inputstream) else: s = inputstream case p.skin - of skinStandard, skinBraces, skinEndX: - parser.openParser(p.parser, fileIdx, s, cache, false) + of skinStandard, skinEndX: + parser.openParser(p.parser, fileIdx, s, cache, config, false) of skinStrongSpaces: - parser.openParser(p.parser, fileIdx, s, cache, true) + parser.openParser(p.parser, fileIdx, s, cache, config, true) proc closeParsers*(p: var TParsers) = parser.closeParser(p.parser) -proc parseFile*(fileIdx: int32; cache: IdentCache): PNode {.procvar.} = +proc parseFile*(fileIdx: FileIndex; cache: IdentCache; config: ConfigRef): PNode {.procvar.} = var p: TParsers f: File - let filename = fileIdx.toFullPathConsiderDirty + let filename = toFullPathConsiderDirty(config, fileIdx) if not open(f, filename): - rawMessage(errCannotOpenFile, filename) + rawMessage(config, errGenerated, "cannot open file: " & filename) return - openParsers(p, fileIdx, llStreamOpen(f), cache) + openParsers(p, fileIdx, llStreamOpen(f), cache, config) result = parseAll(p) closeParsers(p) diff --git a/compiler/tccgen.nim b/compiler/tccgen.nim index ea0fb590f..2301ad404 100644 --- a/compiler/tccgen.nim +++ b/compiler/tccgen.nim @@ -39,24 +39,24 @@ proc setupEnvironment = addIncludePath(gTinyC, libpath) when defined(windows): - addSysincludePath(gTinyC, nimrodDir / "tinyc/win32/include") - addSysincludePath(gTinyC, nimrodDir / "tinyc/include") + addSysincludePath(gTinyC, nimDir / "tinyc/win32/include") + addSysincludePath(gTinyC, nimDir / "tinyc/include") when defined(windows): defineSymbol(gTinyC, "_WIN32", nil) # we need Mingw's headers too: var gccbin = getConfigVar("gcc.path") % ["nim", nimDir] addSysincludePath(gTinyC, gccbin /../ "include") - #addFile(nimrodDir / r"tinyc\win32\wincrt1.o") - addFile(nimrodDir / r"tinyc\win32\alloca86.o") - addFile(nimrodDir / r"tinyc\win32\chkstk.o") - #addFile(nimrodDir / r"tinyc\win32\crt1.o") + #addFile(nimDir / r"tinyc\win32\wincrt1.o") + addFile(nimDir / r"tinyc\win32\alloca86.o") + addFile(nimDir / r"tinyc\win32\chkstk.o") + #addFile(nimDir / r"tinyc\win32\crt1.o") - #addFile(nimrodDir / r"tinyc\win32\dllcrt1.o") - #addFile(nimrodDir / r"tinyc\win32\dllmain.o") - addFile(nimrodDir / r"tinyc\win32\libtcc1.o") + #addFile(nimDir / r"tinyc\win32\dllcrt1.o") + #addFile(nimDir / r"tinyc\win32\dllmain.o") + addFile(nimDir / r"tinyc\win32\libtcc1.o") - #addFile(nimrodDir / r"tinyc\win32\lib\crt1.c") - #addFile(nimrodDir / r"tinyc\lib\libtcc1.c") + #addFile(nimDir / r"tinyc\win32\lib\crt1.c") + #addFile(nimDir / r"tinyc\lib\libtcc1.c") else: addSysincludePath(gTinyC, "/usr/include") when defined(amd64): diff --git a/compiler/transf.nim b/compiler/transf.nim index 94899c5d4..347df3e49 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -19,9 +19,10 @@ # * transforms 'defer' into a 'try finally' statement import - intsets, strutils, options, ast, astalgo, trees, treetab, msgs, os, - idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread, - lambdalifting, sempass2, lowerings, lookups, destroyer, liftlocals + intsets, strutils, options, ast, astalgo, trees, treetab, msgs, lookups, + idents, renderer, types, passes, semfold, magicsys, cgmeth, + lambdalifting, sempass2, lowerings, destroyer, liftlocals, closureiters, + modulegraphs, lineinfos type PTransNode* = distinct PNode @@ -44,6 +45,7 @@ type nestedProcs: int # > 0 if we are in a nested proc contSyms, breakSyms: seq[PSym] # to transform 'continue' and 'break' deferDetected, tooEarly, needsDestroyPass: bool + graph: ModuleGraph PTransf = ref TTransfContext proc newTransNode(a: PNode): PTransNode {.inline.} = @@ -84,7 +86,7 @@ proc pushTransCon(c: PTransf, t: PTransCon) = c.transCon = t proc popTransCon(c: PTransf) = - if (c.transCon == nil): internalError("popTransCon") + if (c.transCon == nil): internalError(c.graph.config, "popTransCon") c.transCon = c.transCon.next proc getCurrOwner(c: PTransf): PSym = @@ -92,12 +94,12 @@ proc getCurrOwner(c: PTransf): PSym = else: result = c.module proc newTemp(c: PTransf, typ: PType, info: TLineInfo): PNode = - let r = newSym(skTemp, getIdent(genPrefix), getCurrOwner(c), info) + let r = newSym(skTemp, getIdent(c.graph.cache, genPrefix), getCurrOwner(c), info) r.typ = typ #skipTypes(typ, {tyGenericInst, tyAlias, tySink}) incl(r.flags, sfFromGeneric) let owner = getCurrOwner(c) if owner.isIterator and not c.tooEarly: - result = freshVarForClosureIter(r, owner) + result = freshVarForClosureIter(c.graph, r, owner) else: result = newSymNode(r) @@ -118,10 +120,10 @@ proc transformSymAux(c: PTransf, n: PNode): PNode = if s.typ != nil and s.typ.callConv == ccClosure: if s.kind == skIterator: if c.tooEarly: return n - else: return liftIterSym(n, getCurrOwner(c)) + else: return liftIterSym(c.graph, n, getCurrOwner(c)) elif s.kind in {skProc, skFunc, skConverter, skMethod} and not c.tooEarly: # top level .closure procs are still somewhat supported for 'Nake': - return makeClosure(s, nil, n.info) + return makeClosure(c.graph, s, nil, n.info) #elif n.sym.kind in {skVar, skLet} and n.sym.typ.callConv == ccClosure: # echo n.info, " come heer for ", c.tooEarly # if not c.tooEarly: @@ -130,7 +132,7 @@ proc transformSymAux(c: PTransf, n: PNode): PNode = if sfBorrow in s.flags and s.kind in routineKinds: # simply exchange the symbol: b = s.getBody - if b.kind != nkSym: internalError(n.info, "wrong AST for borrowed symbol") + if b.kind != nkSym: internalError(c.graph.config, n.info, "wrong AST for borrowed symbol") b = newSymNode(b.sym, n.info) else: b = n @@ -151,7 +153,7 @@ proc transformSym(c: PTransf, n: PNode): PTransNode = proc freshVar(c: PTransf; v: PSym): PNode = let owner = getCurrOwner(c) if owner.isIterator and not c.tooEarly: - result = freshVarForClosureIter(v, owner) + result = freshVarForClosureIter(c.graph, v, owner) else: var newVar = copySym(v) incl(newVar.flags, sfFromGeneric) @@ -166,11 +168,11 @@ proc transformVarSection(c: PTransf, v: PNode): PTransNode = result[i] = PTransNode(it) elif it.kind == nkIdentDefs: if it.sons[0].kind == nkSym: - internalAssert(it.len == 3) + internalAssert(c.graph.config, it.len == 3) let x = freshVar(c, it.sons[0].sym) idNodeTablePut(c.transCon.mapping, it.sons[0].sym, x) var defs = newTransNode(nkIdentDefs, it.info, 3) - if importantComments(): + if importantComments(c.graph.config): # keep documentation information: PNode(defs).comment = it.comment defs[0] = x.PTransNode @@ -184,7 +186,7 @@ proc transformVarSection(c: PTransf, v: PNode): PTransNode = result[i] = transform(c, it) else: if it.kind != nkVarTuple: - internalError(it.info, "transformVarSection: not nkVarTuple") + internalError(c.graph.config, it.info, "transformVarSection: not nkVarTuple") var L = sonsLen(it) var defs = newTransNode(it.kind, it.info, L) for j in countup(0, L-3): @@ -192,7 +194,7 @@ proc transformVarSection(c: PTransf, v: PNode): PTransNode = idNodeTablePut(c.transCon.mapping, it.sons[j].sym, x) defs[j] = x.PTransNode assert(it.sons[L-2].kind == nkEmpty) - defs[L-2] = ast.emptyNode.PTransNode + defs[L-2] = newNodeI(nkEmpty, it.info).PTransNode defs[L-1] = transform(c, it.sons[L-1]) result[i] = defs @@ -203,9 +205,9 @@ proc transformConstSection(c: PTransf, v: PNode): PTransNode = if it.kind == nkCommentStmt: result[i] = PTransNode(it) else: - if it.kind != nkConstDef: internalError(it.info, "transformConstSection") + if it.kind != nkConstDef: internalError(c.graph.config, it.info, "transformConstSection") if it.sons[0].kind != nkSym: - internalError(it.info, "transformConstSection") + internalError(c.graph.config, it.info, "transformConstSection") result[i] = PTransNode(it) @@ -219,7 +221,7 @@ proc hasContinue(n: PNode): bool = proc newLabel(c: PTransf, n: PNode): PSym = result = newSym(skLabel, nil, getCurrOwner(c), n.info) - result.name = getIdent(genPrefix & $result.id) + result.name = getIdent(c.graph.cache, genPrefix & $result.id) proc freshLabels(c: PTransf, n: PNode; symMap: var TIdTable) = if n.kind in {nkBlockStmt, nkBlockExpr}: @@ -301,7 +303,7 @@ proc unpackTuple(c: PTransf, n: PNode, father: PTransNode) = # XXX: BUG: what if `n` is an expression with side-effects? for i in countup(0, sonsLen(c.transCon.forStmt) - 3): add(father, newAsgnStmt(c, c.transCon.forStmt.sons[i], - transform(c, newTupleAccess(n, i)))) + transform(c, newTupleAccess(c.graph, n, i)))) proc introduceNewLocalVars(c: PTransf, n: PNode): PTransNode = case n.kind @@ -334,7 +336,7 @@ proc transformYield(c: PTransf, n: PNode): PTransNode = if skipTypes(e.typ, {tyGenericInst, tyAlias, tySink}).kind == tyTuple and c.transCon.forStmt.len != 3: e = skipConv(e) - if e.kind == nkPar: + if e.kind in {nkPar, nkTupleConstr}: for i in countup(0, sonsLen(e) - 1): var v = e.sons[i] if v.kind == nkExprColonExpr: v = v.sons[1] @@ -356,7 +358,7 @@ proc transformYield(c: PTransf, n: PNode): PTransNode = proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode = result = transformSons(c, n) - if gCmd == cmdCompileToCpp or sfCompileToCpp in c.module.flags: return + if c.graph.config.cmd == cmdCompileToCpp or sfCompileToCpp in c.module.flags: return var n = result.PNode case n.sons[0].kind of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64: @@ -367,6 +369,8 @@ proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode = result = PTransNode(n.sons[0]) if n.typ.skipTypes(abstractVar).kind != tyOpenArray: PNode(result).typ = n.typ + elif n.typ.skipTypes(abstractInst).kind in {tyVar}: + PNode(result).typ = toVar(PNode(result).typ) of nkHiddenStdConv, nkHiddenSubConv, nkConv: var m = n.sons[0].sons[1] if m.kind == a or m.kind == b: @@ -375,6 +379,8 @@ proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode = result = PTransNode(n.sons[0]) if n.typ.skipTypes(abstractVar).kind != tyOpenArray: PNode(result).typ = n.typ + elif n.typ.skipTypes(abstractInst).kind in {tyVar}: + PNode(result).typ = toVar(PNode(result).typ) else: if n.sons[0].kind == a or n.sons[0].kind == b: # addr ( deref ( x )) --> x @@ -382,21 +388,21 @@ proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode = if n.typ.skipTypes(abstractVar).kind != tyOpenArray: PNode(result).typ = n.typ -proc generateThunk(prc: PNode, dest: PType): PNode = +proc generateThunk(c: PTransf; prc: PNode, dest: PType): PNode = ## Converts 'prc' into '(thunk, nil)' so that it's compatible with ## a closure. # we cannot generate a proper thunk here for GC-safety reasons # (see internal documentation): - if gCmd == cmdCompileToJS: return prc + if c.graph.config.cmd == cmdCompileToJS: return prc result = newNodeIT(nkClosure, prc.info, dest) var conv = newNodeIT(nkHiddenSubConv, prc.info, dest) - conv.add(emptyNode) + conv.add(newNodeI(nkEmpty, prc.info)) conv.add(prc) if prc.kind == nkClosure: - internalError(prc.info, "closure to closure created") + internalError(c.graph.config, prc.info, "closure to closure created") result.add(conv) - result.add(newNodeIT(nkNilLit, prc.info, getSysType(tyNil))) + result.add(newNodeIT(nkNilLit, prc.info, getSysType(c.graph, prc.info, tyNil))) proc transformConv(c: PTransf, n: PNode): PTransNode = # numeric types need range checks: @@ -408,8 +414,8 @@ proc transformConv(c: PTransf, n: PNode): PTransNode = if not isOrdinalType(source): # float -> int conversions. ugh. result = transformSons(c, n) - elif firstOrd(n.typ) <= firstOrd(n.sons[1].typ) and - lastOrd(n.sons[1].typ) <= lastOrd(n.typ): + elif firstOrd(c.graph.config, n.typ) <= firstOrd(c.graph.config, n.sons[1].typ) and + lastOrd(c.graph.config, n.sons[1].typ) <= lastOrd(c.graph.config, n.typ): # BUGFIX: simply leave n as it is; we need a nkConv node, # but no range check: result = transformSons(c, n) @@ -421,8 +427,8 @@ proc transformConv(c: PTransf, n: PNode): PTransNode = result = newTransNode(nkChckRange, n, 3) dest = skipTypes(n.typ, abstractVar) result[0] = transform(c, n.sons[1]) - result[1] = newIntTypeNode(nkIntLit, firstOrd(dest), dest).PTransNode - result[2] = newIntTypeNode(nkIntLit, lastOrd(dest), dest).PTransNode + result[1] = newIntTypeNode(nkIntLit, firstOrd(c.graph.config, dest), dest).PTransNode + result[2] = newIntTypeNode(nkIntLit, lastOrd(c.graph.config, dest), dest).PTransNode of tyFloat..tyFloat128: # XXX int64 -> float conversion? if skipTypes(n.typ, abstractVar).kind == tyRange: @@ -481,7 +487,7 @@ proc transformConv(c: PTransf, n: PNode): PTransNode = of tyProc: result = transformSons(c, n) if dest.callConv == ccClosure and source.callConv == ccDefault: - result = generateThunk(result[1].PNode, dest).PTransNode + result = generateThunk(c, result[1].PNode, dest).PTransNode else: result = transformSons(c, n) @@ -500,7 +506,7 @@ proc putArgInto(arg: PNode, formal: PType): TPutArgInto = case arg.kind of nkEmpty..nkNilLit: result = paDirectMapping - of nkPar, nkCurly, nkBracket: + of nkPar, nkTupleConstr, nkCurly, nkBracket: result = paFastAsgn for i in countup(0, sonsLen(arg) - 1): if putArgInto(arg.sons[i], formal) != paDirectMapping: return @@ -513,7 +519,7 @@ proc findWrongOwners(c: PTransf, n: PNode) = if n.kind == nkVarSection: let x = n.sons[0].sons[0] if x.kind == nkSym and x.sym.owner != getCurrOwner(c): - internalError(x.info, "bah " & x.sym.name.s & " " & + internalError(c.graph.config, x.info, "bah " & x.sym.name.s & " " & x.sym.owner.name.s & " " & getCurrOwner(c).name.s) else: for i in 0 ..< safeLen(n): findWrongOwners(c, n.sons[i]) @@ -521,7 +527,7 @@ proc findWrongOwners(c: PTransf, n: PNode) = proc transformFor(c: PTransf, n: PNode): PTransNode = # generate access statements for the parameters (unless they are constant) # put mapping from formal parameters to actual parameters - if n.kind != nkForStmt: internalError(n.info, "transformFor") + if n.kind != nkForStmt: internalError(c.graph.config, n.info, "transformFor") var length = sonsLen(n) var call = n.sons[length - 2] @@ -537,11 +543,8 @@ proc transformFor(c: PTransf, n: PNode): PTransNode = if call.kind notin nkCallKinds or call.sons[0].kind != nkSym or call.sons[0].typ.callConv == ccClosure: n.sons[length-1] = transformLoopBody(c, n.sons[length-1]).PNode - if not c.tooEarly: - n.sons[length-2] = transform(c, n.sons[length-2]).PNode - result[1] = lambdalifting.liftForLoop(n, getCurrOwner(c)).PTransNode - else: - result[1] = newNode(nkEmpty).PTransNode + n.sons[length-2] = transform(c, n.sons[length-2]).PNode + result[1] = lambdalifting.liftForLoop(c.graph, n, getCurrOwner(c)).PTransNode discard c.breakSyms.pop return result @@ -596,7 +599,7 @@ proc transformFor(c: PTransf, n: PNode): PTransNode = idNodeTablePut(newC.mapping, formal, temp) var body = iter.getBody.copyTree - pushInfoContext(n.info) + pushInfoContext(c.graph.config, n.info) # XXX optimize this somehow. But the check "c.inlining" is not correct: var symMap: TIdTable initIdTable symMap @@ -606,7 +609,7 @@ proc transformFor(c: PTransf, n: PNode): PTransNode = add(stmtList, transform(c, body)) #findWrongOwners(c, stmtList.pnode) dec(c.inlining) - popInfoContext() + popInfoContext(c.graph.config) popTransCon(c) # echo "transformed: ", stmtList.PNode.renderTree @@ -621,7 +624,11 @@ proc transformCase(c: PTransf, n: PNode): PTransNode = case it.kind of nkElifBranch: if ifs.PNode == nil: - ifs = newTransNode(nkIfStmt, it.info, 0) + # Generate the right node depending on whether `n` is used as a stmt or + # as an expr + let kind = if n.typ != nil: nkIfExpr else: nkIfStmt + ifs = newTransNode(kind, it.info, 0) + ifs.PNode.typ = n.typ ifs.add(e) of nkElse: if ifs.PNode == nil: result.add(e) @@ -689,7 +696,7 @@ proc transformCall(c: PTransf, n: PNode): PTransNode = while (j < sonsLen(n)): let b = transform(c, n.sons[j]).PNode if not isConstExpr(b): break - a = evalOp(op.magic, n, a, b, nil) + a = evalOp(op.magic, n, a, b, nil, c.graph) inc(j) add(result, a.PTransNode) if len(result) == 2: result = result[1] @@ -708,27 +715,27 @@ proc transformCall(c: PTransf, n: PNode): PTransNode = let t = lastSon(s.sons[0].sym.ast) if t.kind != nkSym or sfDispatcher notin t.sym.flags: methodDef(s.sons[0].sym, false) - result = methodCall(s).PTransNode + result = methodCall(s, c.graph.config).PTransNode else: result = s.PTransNode proc transformExceptBranch(c: PTransf, n: PNode): PTransNode = result = transformSons(c, n) - if n[0].isInfixAs(): + if n[0].isInfixAs() and not isImportedException(n[0][1].typ, c.graph.config): let excTypeNode = n[0][1] let actions = newTransNode(nkStmtListExpr, n[1], 2) # Generating `let exc = (excType)(getCurrentException())` # -> getCurrentException() - let excCall = PTransNode(callCodegenProc("getCurrentException", ast.emptyNode)) + let excCall = PTransNode(callCodegenProc(c.graph, "getCurrentException", newNodeI(nkEmpty, n.info))) # -> (excType) let convNode = newTransNode(nkHiddenSubConv, n[1].info, 2) - convNode[0] = PTransNode(ast.emptyNode) + convNode[0] = PTransNode(newNodeI(nkEmpty, n.info)) convNode[1] = excCall PNode(convNode).typ = excTypeNode.typ.toRef() # -> let exc = ... let identDefs = newTransNode(nkIdentDefs, n[1].info, 3) identDefs[0] = PTransNode(n[0][2]) - identDefs[1] = PTransNode(ast.emptyNode) + identDefs[1] = PTransNode(newNodeI(nkEmpty, n.info)) identDefs[2] = convNode let letSection = newTransNode(nkLetSection, n[1].info, 1) @@ -745,13 +752,13 @@ proc transformExceptBranch(c: PTransf, n: PNode): PTransNode = proc dontInlineConstant(orig, cnst: PNode): bool {.inline.} = # symbols that expand to a complex constant (array, etc.) should not be # inlined, unless it's the empty array: - result = orig.kind == nkSym and cnst.kind in {nkCurly, nkPar, nkBracket} and + result = orig.kind == nkSym and cnst.kind in {nkCurly, nkPar, nkTupleConstr, nkBracket} and cnst.len != 0 -proc commonOptimizations*(c: PSym, n: PNode): PNode = +proc commonOptimizations*(g: ModuleGraph; c: PSym, n: PNode): PNode = result = n for i in 0 ..< n.safeLen: - result.sons[i] = commonOptimizations(c, n.sons[i]) + result.sons[i] = commonOptimizations(g, c, n.sons[i]) var op = getMergeOp(n) if (op != nil) and (op.magic != mNone) and (sonsLen(n) >= 3): result = newNodeIT(nkCall, n.info, n.typ) @@ -766,18 +773,55 @@ proc commonOptimizations*(c: PSym, n: PNode): PNode = while j < sonsLen(args): let b = args.sons[j] if not isConstExpr(b): break - a = evalOp(op.magic, result, a, b, nil) + a = evalOp(op.magic, result, a, b, nil, g) inc(j) add(result, a) if len(result) == 2: result = result[1] else: - var cnst = getConstExpr(c, n) + var cnst = getConstExpr(c, n, g) # we inline constants if they are not complex constants: if cnst != nil and not dontInlineConstant(n, cnst): result = cnst else: result = n +proc hoistParamsUsedInDefault(c: PTransf, call, letSection, defExpr: PNode): PNode = + # This takes care of complicated signatures such as: + # proc foo(a: int, b = a) + # proc bar(a: int, b: int, c = a + b) + # + # The recursion may confuse you. It performs two duties: + # + # 1) extracting all referenced params from default expressions + # into a let section preceeding the call + # + # 2) replacing the "references" within the default expression + # with these extracted skLet symbols. + # + # The first duty is carried out directly in the code here, while the second + # duty is activated by returning a non-nil value. The caller is responsible + # for replacing the input to the function with the returned non-nil value. + # (which is the hoisted symbol) + if defExpr.kind == nkSym: + if defExpr.sym.kind == skParam and defExpr.sym.owner == call[0].sym: + let paramPos = defExpr.sym.position + 1 + + if call[paramPos].kind == nkSym and sfHoisted in call[paramPos].sym.flags: + # Already hoisted, we still need to return it in order to replace the + # placeholder expression in the default value. + return call[paramPos] + + let hoistedVarSym = hoistExpr(letSection, + call[paramPos], + getIdent(c.graph.cache, genPrefix), + c.transCon.owner).newSymNode + call[paramPos] = hoistedVarSym + return hoistedVarSym + else: + for i in 0..<defExpr.safeLen: + let hoisted = hoistParamsUsedInDefault(c, call, letSection, defExpr[i]) + if hoisted != nil: defExpr[i] = hoisted + proc transform(c: PTransf, n: PNode): PTransNode = when false: var oldDeferAnchor: PNode @@ -847,6 +891,15 @@ proc transform(c: PTransf, n: PNode): PTransNode = of nkBreakStmt: result = transformBreak(c, n) of nkCallKinds: result = transformCall(c, n) + var call = result.PNode + if nfDefaultRefsParam in call.flags: + # We've found a default value that references another param. + # See the notes in `hoistParamsUsedInDefault` for more details. + var hoistedParams = newNodeI(nkLetSection, call.info, 0) + for i in 1 ..< call.len: + let hoisted = hoistParamsUsedInDefault(c, call, hoistedParams, call[i]) + if hoisted != nil: call[i] = hoisted + result = newTree(nkStmtListExpr, hoistedParams, call).PTransNode of nkAddr, nkHiddenAddr: result = transformAddrDeref(c, n, nkDerefExpr, nkHiddenDeref) of nkDerefExpr, nkHiddenDeref: @@ -861,7 +914,7 @@ proc transform(c: PTransf, n: PNode): PTransNode = # ensure that e.g. discard "some comment" gets optimized away # completely: result = PTransNode(newNode(nkCommentStmt)) - of nkCommentStmt, nkTemplateDef: + of nkCommentStmt, nkTemplateDef, nkImportStmt, nkStaticStmt: return n.PTransNode of nkConstSection: # do not replace ``const c = 3`` with ``const 3 = 3`` @@ -888,7 +941,7 @@ proc transform(c: PTransf, n: PNode): PTransNode = let L = n.len-1 result[L] = transform(c, n.sons[L]) # XXX comment handling really sucks: - if importantComments(): + if importantComments(c.graph.config): PNode(result).comment = n.comment of nkClosure: # it can happen that for-loop-inlining produced a fresh @@ -905,7 +958,7 @@ proc transform(c: PTransf, n: PNode): PTransNode = when false: if oldDeferAnchor != nil: c.deferAnchor = oldDeferAnchor - var cnst = getConstExpr(c.module, PNode(result)) + var cnst = getConstExpr(c.module, PNode(result), c.graph) # we inline constants if they are not complex constants: if cnst != nil and not dontInlineConstant(n, cnst): result = PTransNode(cnst) # do not miss an optimization @@ -914,17 +967,18 @@ proc processTransf(c: PTransf, n: PNode, owner: PSym): PNode = # Note: For interactive mode we cannot call 'passes.skipCodegen' and skip # this step! We have to rely that the semantic pass transforms too errornous # nodes into an empty node. - if c.rd != nil or nfTransf in n.flags: return n + if nfTransf in n.flags: return n pushTransCon(c, newTransCon(owner)) result = PNode(transform(c, n)) popTransCon(c) incl(result.flags, nfTransf) -proc openTransf(module: PSym, filename: string): PTransf = +proc openTransf(g: ModuleGraph; module: PSym, filename: string): PTransf = new(result) result.contSyms = @[] result.breakSyms = @[] result.module = module + result.graph = g proc flattenStmts(n: PNode) = var goOn = true @@ -967,46 +1021,52 @@ template liftDefer(c, root) = if c.deferDetected: liftDeferAux(root) -proc transformBody*(module: PSym, n: PNode, prc: PSym): PNode = +proc transformBody*(g: ModuleGraph; module: PSym, n: PNode, prc: PSym): PNode = if nfTransf in n.flags or prc.kind in {skTemplate}: result = n else: - var c = openTransf(module, "") - result = liftLambdas(prc, n, c.tooEarly) + var c = openTransf(g, module, "") + result = liftLambdas(g, prc, n, c.tooEarly) #result = n result = processTransf(c, result, prc) liftDefer(c, result) #result = liftLambdas(prc, result) - when useEffectSystem: trackProc(prc, result) - result = liftLocalsIfRequested(prc, result) - if c.needsDestroyPass and newDestructors: - result = injectDestructorCalls(prc, result) + when useEffectSystem: trackProc(g, prc, result) + result = liftLocalsIfRequested(prc, result, g.cache, g.config) + if c.needsDestroyPass: #and newDestructors: + result = injectDestructorCalls(g, prc, result) + + if prc.isIterator: + result = g.transformClosureIterator(prc, result) + incl(result.flags, nfTransf) #if prc.name.s == "testbody": # echo renderTree(result) -proc transformStmt*(module: PSym, n: PNode): PNode = +proc transformStmt*(g: ModuleGraph; module: PSym, n: PNode): PNode = if nfTransf in n.flags: result = n else: - var c = openTransf(module, "") + var c = openTransf(g, module, "") result = processTransf(c, n, module) liftDefer(c, result) #result = liftLambdasForTopLevel(module, result) - when useEffectSystem: trackTopLevelStmt(module, result) + when useEffectSystem: trackTopLevelStmt(g, module, result) #if n.info ?? "temp.nim": # echo renderTree(result, {renderIds}) - if c.needsDestroyPass and newDestructors: - result = injectDestructorCalls(module, result) + #if c.needsDestroyPass: + # result = injectDestructorCalls(g, module, result) incl(result.flags, nfTransf) -proc transformExpr*(module: PSym, n: PNode): PNode = +proc transformExpr*(g: ModuleGraph; module: PSym, n: PNode): PNode = if nfTransf in n.flags: result = n else: - var c = openTransf(module, "") + var c = openTransf(g, module, "") result = processTransf(c, n, module) liftDefer(c, result) - if c.needsDestroyPass and newDestructors: - result = injectDestructorCalls(module, result) + # expressions are not to be injected with destructor calls as that + # the list of top level statements needs to be collected before. + #if c.needsDestroyPass: + # result = injectDestructorCalls(g, module, result) incl(result.flags, nfTransf) diff --git a/compiler/trees.nim b/compiler/trees.nim index f69108942..fb523de9d 100644 --- a/compiler/trees.nim +++ b/compiler/trees.nim @@ -97,7 +97,7 @@ proc isDeepConstExpr*(n: PNode): bool = result = true of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv: result = isDeepConstExpr(n.sons[1]) - of nkCurly, nkBracket, nkPar, nkObjConstr, nkClosure, nkRange: + of nkCurly, nkBracket, nkPar, nkTupleConstr, nkObjConstr, nkClosure, nkRange: for i in ord(n.kind == nkObjConstr) ..< n.len: if not isDeepConstExpr(n.sons[i]): return false if n.typ.isNil: result = true diff --git a/compiler/treetab.nim b/compiler/treetab.nim index e6eb8c666..f15974f61 100644 --- a/compiler/treetab.nim +++ b/compiler/treetab.nim @@ -29,8 +29,7 @@ proc hashTree(n: PNode): Hash = if (n.floatVal >= - 1000000.0) and (n.floatVal <= 1000000.0): result = result !& toInt(n.floatVal) of nkStrLit..nkTripleStrLit: - if not n.strVal.isNil: - result = result !& hash(n.strVal) + result = result !& hash(n.strVal) else: for i in countup(0, sonsLen(n) - 1): result = result !& hashTree(n.sons[i]) diff --git a/compiler/types.nim b/compiler/types.nim index 7ae02486e..d0eec35cf 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -10,7 +10,8 @@ # this module contains routines for accessing and iterating over types import - intsets, ast, astalgo, trees, msgs, strutils, platform, renderer + intsets, ast, astalgo, trees, msgs, strutils, platform, renderer, options, + lineinfos type TPreferedDesc* = enum @@ -88,12 +89,16 @@ proc isPureObject*(typ: PType): bool = proc getOrdValue*(n: PNode): BiggestInt = case n.kind - of nkCharLit..nkUInt64Lit: result = n.intVal - of nkNilLit: result = 0 - of nkHiddenStdConv: result = getOrdValue(n.sons[1]) - else: - localError(n.info, errOrdinalTypeExpected) - result = 0 + of nkCharLit..nkUInt64Lit: n.intVal + of nkNilLit: 0 + of nkHiddenStdConv: getOrdValue(n.sons[1]) + else: high(BiggestInt) + +proc getFloatValue*(n: PNode): BiggestFloat = + case n.kind + of nkFloatLiterals: n.floatVal + of nkHiddenStdConv: getFloatValue(n.sons[1]) + else: NaN proc isIntLit*(t: PType): bool {.inline.} = result = t.kind == tyInt and t.n != nil and t.n.kind == nkIntLit @@ -101,45 +106,45 @@ proc isIntLit*(t: PType): bool {.inline.} = proc isFloatLit*(t: PType): bool {.inline.} = result = t.kind == tyFloat and t.n != nil and t.n.kind == nkFloatLit -proc getProcHeader*(sym: PSym; prefer: TPreferedDesc = preferName): string = +proc getProcHeader*(conf: ConfigRef; sym: PSym; prefer: TPreferedDesc = preferName): string = result = sym.owner.name.s & '.' & sym.name.s & '(' var n = sym.typ.n for i in countup(1, sonsLen(n) - 1): - var p = n.sons[i] + let p = n.sons[i] if p.kind == nkSym: add(result, p.sym.name.s) add(result, ": ") add(result, typeToString(p.sym.typ, prefer)) if i != sonsLen(n)-1: add(result, ", ") else: - internalError("getProcHeader") + result.add renderTree(p) add(result, ')') if n.sons[0].typ != nil: result.add(": " & typeToString(n.sons[0].typ, prefer)) result.add "[declared in " - result.add($sym.info) + result.add(conf$sym.info) result.add "]" proc elemType*(t: PType): PType = assert(t != nil) case t.kind - of tyGenericInst, tyDistinct, tyAlias: result = elemType(lastSon(t)) + of tyGenericInst, tyDistinct, tyAlias, tySink: result = elemType(lastSon(t)) of tyArray: result = t.sons[1] else: result = t.lastSon assert(result != nil) -proc isOrdinalType*(t: PType): bool = +proc enumHasHoles*(t: PType): bool = + var b = t.skipTypes({tyRange, tyGenericInst, tyAlias, tySink}) + result = b.kind == tyEnum and tfEnumHasHoles in b.flags + +proc isOrdinalType*(t: PType, allowEnumWithHoles = false): bool = assert(t != nil) const # caution: uint, uint64 are no ordinal types! baseKinds = {tyChar,tyInt..tyInt64,tyUInt8..tyUInt32,tyBool,tyEnum} - parentKinds = {tyRange, tyOrdinal, tyGenericInst, tyAlias, tyDistinct} - t.kind in baseKinds or (t.kind in parentKinds and isOrdinalType(t.sons[0])) - -proc enumHasHoles*(t: PType): bool = - var b = t - while b.kind in {tyRange, tyGenericInst, tyAlias}: b = b.sons[0] - result = b.kind == tyEnum and tfEnumHasHoles in b.flags + parentKinds = {tyRange, tyOrdinal, tyGenericInst, tyAlias, tySink, tyDistinct} + (t.kind in baseKinds and not (t.enumHasHoles and not allowEnumWithHoles)) or + (t.kind in parentKinds and isOrdinalType(t.lastSon)) proc iterOverTypeAux(marker: var IntSet, t: PType, iter: TTypeIter, closure: RootRef): bool @@ -163,7 +168,7 @@ proc iterOverTypeAux(marker: var IntSet, t: PType, iter: TTypeIter, if result: return if not containsOrIncl(marker, t.id): case t.kind - of tyGenericInst, tyGenericBody, tyAlias, tyInferred: + of tyGenericInst, tyGenericBody, tyAlias, tySink, tyInferred: result = iterOverTypeAux(marker, lastSon(t), iter, closure) else: for i in countup(0, sonsLen(t) - 1): @@ -195,10 +200,10 @@ proc searchTypeNodeForAux(n: PNode, p: TTypePredicate, of nkOfBranch, nkElse: result = searchTypeNodeForAux(lastSon(n.sons[i]), p, marker) if result: return - else: internalError("searchTypeNodeForAux(record case branch)") + else: discard of nkSym: result = searchTypeForAux(n.sym.typ, p, marker) - else: internalError(n.info, "searchTypeNodeForAux()") + else: discard proc searchTypeForAux(t: PType, predicate: TTypePredicate, marker: var IntSet): bool = @@ -213,7 +218,7 @@ proc searchTypeForAux(t: PType, predicate: TTypePredicate, if t.sons[0] != nil: result = searchTypeForAux(t.sons[0].skipTypes(skipPtrs), predicate, marker) if not result: result = searchTypeNodeForAux(t.n, predicate, marker) - of tyGenericInst, tyDistinct, tyAlias: + of tyGenericInst, tyDistinct, tyAlias, tySink: result = searchTypeForAux(lastSon(t), predicate, marker) of tyArray, tySet, tyTuple: for i in countup(0, sonsLen(t) - 1): @@ -244,7 +249,7 @@ proc analyseObjectWithTypeFieldAux(t: PType, if t == nil: return case t.kind of tyObject: - if (t.n != nil): + if t.n != nil: if searchTypeNodeForAux(t.n, isObjectWithTypeFieldPredicate, marker): return frEmbedded for i in countup(0, sonsLen(t) - 1): @@ -256,7 +261,7 @@ proc analyseObjectWithTypeFieldAux(t: PType, if res == frHeader: result = frHeader if result == frNone: if isObjectWithTypeFieldPredicate(t): result = frHeader - of tyGenericInst, tyDistinct, tyAlias: + of tyGenericInst, tyDistinct, tyAlias, tySink: result = analyseObjectWithTypeFieldAux(lastSon(t), marker) of tyArray, tyTuple: for i in countup(0, sonsLen(t) - 1): @@ -273,6 +278,8 @@ proc analyseObjectWithTypeField(t: PType): TTypeFieldResult = proc isGCRef(t: PType): bool = result = t.kind in GcTypeKinds or (t.kind == tyProc and t.callConv == ccClosure) + if result and t.kind in {tyString, tySequence} and tfHasAsgn in t.flags: + result = false proc containsGarbageCollectedRef*(typ: PType): bool = # returns true if typ contains a reference, sequence or string (all the @@ -421,9 +428,11 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = sfAnon notin t.sym.flags: if t.kind == tyInt and isIntLit(t): result = t.sym.name.s & " literal(" & $t.n.intVal & ")" + elif t.kind == tyAlias: + result = typeToString(t.sons[0]) elif prefer in {preferName, preferTypeName} or t.sym.owner.isNil: result = t.sym.name.s - if t.kind == tyGenericParam and t.sons != nil and t.sonsLen > 0: + if t.kind == tyGenericParam and t.sonsLen > 0: result.add ": " var first = true for son in t.sons: @@ -453,16 +462,17 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = if t.sons[0].kind == tyNone: result = "typedesc" else: result = "type " & typeToString(t.sons[0]) of tyStatic: - internalAssert t.len > 0 if prefer == preferGenericArg and t.n != nil: result = t.n.renderTree else: - result = "static[" & typeToString(t.sons[0]) & "]" + result = "static[" & (if t.len > 0: typeToString(t.sons[0]) else: "") & "]" if t.n != nil: result.add "(" & renderTree(t.n) & ")" of tyUserTypeClass: - internalAssert t.sym != nil and t.sym.owner != nil - if t.isResolvedUserTypeClass: return typeToString(t.lastSon) - return t.sym.owner.name.s + if t.sym != nil and t.sym.owner != nil: + if t.isResolvedUserTypeClass: return typeToString(t.lastSon) + return t.sym.owner.name.s + else: + result = "<invalid tyUserTypeClass>" of tyBuiltInTypeClass: result = case t.base.kind: of tyVar: "var" @@ -490,16 +500,25 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = add(result, typeToString(t.sons[i])) result.add "]" of tyAnd: - result = typeToString(t.sons[0]) & " and " & typeToString(t.sons[1]) + for i, son in t.sons: + result.add(typeToString(son)) + if i < t.sons.high: + result.add(" and ") of tyOr: - result = typeToString(t.sons[0]) & " or " & typeToString(t.sons[1]) + for i, son in t.sons: + result.add(typeToString(son)) + if i < t.sons.high: + result.add(" or ") of tyNot: result = "not " & typeToString(t.sons[0]) of tyExpr: - internalAssert t.len == 0 + #internalAssert t.len == 0 result = "untyped" of tyFromExpr: - result = renderTree(t.n) + if t.n == nil: + result = "unknown" + else: + result = "type(" & renderTree(t.n) & ")" of tyArray: if t.sons[0].kind == tyRange: result = "array[" & rangeToStr(t.sons[0].n) & ", " & @@ -587,18 +606,19 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = result = typeToStr[t.kind] result.addTypeFlags(t) -proc firstOrd*(t: PType): BiggestInt = + +proc firstOrd*(conf: ConfigRef; t: PType): BiggestInt = case t.kind of tyBool, tyChar, tySequence, tyOpenArray, tyString, tyVarargs, tyProxy: result = 0 - of tySet, tyVar: result = firstOrd(t.sons[0]) - of tyArray: result = firstOrd(t.sons[0]) + of tySet, tyVar: result = firstOrd(conf, t.sons[0]) + of tyArray: result = firstOrd(conf, t.sons[0]) of tyRange: assert(t.n != nil) # range directly given: assert(t.n.kind == nkRange) result = getOrdValue(t.n.sons[0]) of tyInt: - if platform.intSize == 4: result = - (2147483646) - 2 + if conf != nil and conf.target.intSize == 4: result = - (2147483646) - 2 else: result = 0x8000000000000000'i64 of tyInt8: result = - 128 of tyInt16: result = - 32768 @@ -608,38 +628,55 @@ proc firstOrd*(t: PType): BiggestInt = of tyEnum: # if basetype <> nil then return firstOrd of basetype if sonsLen(t) > 0 and t.sons[0] != nil: - result = firstOrd(t.sons[0]) + result = firstOrd(conf, t.sons[0]) else: assert(t.n.sons[0].kind == nkSym) result = t.n.sons[0].sym.position - of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias: - result = firstOrd(lastSon(t)) + of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink, + tyStatic, tyInferred, tyUserTypeClasses: + result = firstOrd(conf, lastSon(t)) of tyOrdinal: - if t.len > 0: result = firstOrd(lastSon(t)) - else: internalError("invalid kind for first(" & $t.kind & ')') + if t.len > 0: result = firstOrd(conf, lastSon(t)) + else: internalError(conf, "invalid kind for firstOrd(" & $t.kind & ')') else: - internalError("invalid kind for first(" & $t.kind & ')') + internalError(conf, "invalid kind for firstOrd(" & $t.kind & ')') result = 0 -proc lastOrd*(t: PType; fixedUnsigned = false): BiggestInt = + +proc firstFloat*(t: PType): BiggestFloat = + case t.kind + of tyFloat..tyFloat128: -Inf + of tyRange: + assert(t.n != nil) # range directly given: + assert(t.n.kind == nkRange) + getFloatValue(t.n.sons[0]) + of tyVar: firstFloat(t.sons[0]) + of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink, + tyStatic, tyInferred, tyUserTypeClasses: + firstFloat(lastSon(t)) + else: + internalError(newPartialConfigRef(), "invalid kind for firstFloat(" & $t.kind & ')') + NaN + +proc lastOrd*(conf: ConfigRef; t: PType; fixedUnsigned = false): BiggestInt = case t.kind of tyBool: result = 1 of tyChar: result = 255 - of tySet, tyVar: result = lastOrd(t.sons[0]) - of tyArray: result = lastOrd(t.sons[0]) + of tySet, tyVar: result = lastOrd(conf, t.sons[0]) + of tyArray: result = lastOrd(conf, t.sons[0]) of tyRange: assert(t.n != nil) # range directly given: assert(t.n.kind == nkRange) result = getOrdValue(t.n.sons[1]) of tyInt: - if platform.intSize == 4: result = 0x7FFFFFFF + if conf != nil and conf.target.intSize == 4: result = 0x7FFFFFFF else: result = 0x7FFFFFFFFFFFFFFF'i64 of tyInt8: result = 0x0000007F of tyInt16: result = 0x00007FFF of tyInt32: result = 0x7FFFFFFF of tyInt64: result = 0x7FFFFFFFFFFFFFFF'i64 of tyUInt: - if platform.intSize == 4: result = 0xFFFFFFFF + if conf != nil and conf.target.intSize == 4: result = 0xFFFFFFFF elif fixedUnsigned: result = 0xFFFFFFFFFFFFFFFF'i64 else: result = 0x7FFFFFFFFFFFFFFF'i64 of tyUInt8: result = 0xFF @@ -651,28 +688,46 @@ proc lastOrd*(t: PType; fixedUnsigned = false): BiggestInt = of tyEnum: assert(t.n.sons[sonsLen(t.n) - 1].kind == nkSym) result = t.n.sons[sonsLen(t.n) - 1].sym.position - of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias: - result = lastOrd(lastSon(t)) + of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink, + tyStatic, tyInferred, tyUserTypeClasses: + result = lastOrd(conf, lastSon(t)) of tyProxy: result = 0 of tyOrdinal: - if t.len > 0: result = lastOrd(lastSon(t)) - else: internalError("invalid kind for last(" & $t.kind & ')') + if t.len > 0: result = lastOrd(conf, lastSon(t)) + else: internalError(conf, "invalid kind for lastOrd(" & $t.kind & ')') else: - internalError("invalid kind for last(" & $t.kind & ')') + internalError(conf, "invalid kind for lastOrd(" & $t.kind & ')') result = 0 -proc lengthOrd*(t: PType): BiggestInt = + +proc lastFloat*(t: PType): BiggestFloat = case t.kind - of tyInt64, tyInt32, tyInt: result = lastOrd(t) - of tyDistinct: result = lengthOrd(t.sons[0]) + of tyFloat..tyFloat128: Inf + of tyVar: lastFloat(t.sons[0]) + of tyRange: + assert(t.n != nil) # range directly given: + assert(t.n.kind == nkRange) + getFloatValue(t.n.sons[1]) + of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink, + tyStatic, tyInferred, tyUserTypeClasses: + lastFloat(lastSon(t)) + else: + internalError(newPartialConfigRef(), "invalid kind for lastFloat(" & $t.kind & ')') + NaN + + +proc lengthOrd*(conf: ConfigRef; t: PType): BiggestInt = + case t.skipTypes(tyUserTypeClasses).kind + of tyInt64, tyInt32, tyInt: result = lastOrd(conf, t) + of tyDistinct: result = lengthOrd(conf, t.sons[0]) else: - let last = lastOrd t - let first = firstOrd t + let last = lastOrd(conf, t) + let first = firstOrd(conf, t) # XXX use a better overflow check here: if last == high(BiggestInt) and first <= 0: result = last else: - result = lastOrd(t) - firstOrd(t) + 1 + result = last - first + 1 # -------------- type equality ----------------------------------------------- @@ -706,9 +761,10 @@ proc initSameTypeClosure: TSameTypeClosure = discard proc containsOrIncl(c: var TSameTypeClosure, a, b: PType): bool = - result = not isNil(c.s) and c.s.contains((a.id, b.id)) + result = c.s.len > 0 and c.s.contains((a.id, b.id)) if not result: - if isNil(c.s): c.s = @[] + when not defined(nimNoNilSeqs): + if isNil(c.s): c.s = @[] c.s.add((a.id, b.id)) proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool @@ -748,7 +804,7 @@ proc equalParam(a, b: PSym): TParamsEquality = proc sameConstraints(a, b: PNode): bool = if isNil(a) and isNil(b): return true - internalAssert a.len == b.len + if a.len != b.len: return false for i in 1 ..< a.len: if not exprStructuralEquivalent(a[i].sym.constraint, b[i].sym.constraint): @@ -777,8 +833,8 @@ proc equalParams(a, b: PNode): TParamsEquality = return paramsNotEqual # paramsIncompatible; # continue traversal! If not equal, we can return immediately; else # it stays incompatible - if not sameTypeOrNil(a.sons[0].typ, b.sons[0].typ, {ExactTypeDescValues}): - if (a.sons[0].typ == nil) or (b.sons[0].typ == nil): + if not sameTypeOrNil(a.typ, b.typ, {ExactTypeDescValues}): + if (a.typ == nil) or (b.typ == nil): result = paramsNotEqual # one proc has a result, the other not is OK else: result = paramsIncompatible # overloading by different @@ -807,7 +863,8 @@ proc sameTuple(a, b: PType, c: var TSameTypeClosure): bool = var y = b.n.sons[i].sym result = x.name.id == y.name.id if not result: break - else: internalError(a.n.info, "sameTuple") + else: + return false elif a.n != b.n and (a.n == nil or b.n == nil) and IgnoreTupleFields notin c.flags: result = false @@ -970,7 +1027,7 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool = result = sameFlags(a, b) of tyGenericParam: result = sameChildrenAux(a, b, c) and sameFlags(a, b) - if result and ExactGenericParams in c.flags: + if result and {ExactGenericParams, ExactTypeDescValues} * c.flags != {}: result = a.sym.position == b.sym.position of tyGenericInvocation, tyGenericBody, tySequence, tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyLent, tySink, @@ -997,7 +1054,7 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool = cycleCheck() result = sameTypeAux(a.lastSon, b.lastSon, c) of tyNone: result = false - of tyUnused, tyOptAsRef: internalError("sameFlags") + of tyUnused, tyOptAsRef: result = false proc sameBackendType*(x, y: PType): bool = var c = initSameTypeClosure() @@ -1056,8 +1113,12 @@ proc commonSuperclass*(a, b: PType): PType = x = x.sons[0] var y = b while y != nil: + var t = y # bug #7818, save type before skip y = skipTypes(y, skipPtrs) - if ancestors.contains(y.id): return y + if ancestors.contains(y.id): + # bug #7818, defer the previous skipTypes + if t.kind != tyGenericInst: t = y + return t y = y.sons[0] type @@ -1136,13 +1197,15 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, of tyTypeClasses: if tfGenericTypeParam in t.flags or taConcept in flags: #or taField notin flags: discard + elif t.isResolvedUserTypeClass: + result = typeAllowedAux(marker, t.lastSon, kind, flags) elif kind notin {skParam, skResult}: result = t of tyGenericBody, tyGenericParam, tyGenericInvocation, tyNone, tyForward, tyFromExpr: result = t of tyNil: - if kind != skConst: result = t + if kind != skConst and kind != skParam: result = t of tyString, tyBool, tyChar, tyEnum, tyInt..tyUInt64, tyCString, tyPointer: result = nil of tyOrdinal: @@ -1158,9 +1221,13 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, of tySequence, tyOpt: if t.sons[0].kind != tyEmpty: result = typeAllowedAux(marker, t.sons[0], skVar, flags+{taHeap}) + elif kind in {skVar, skLet}: + result = t.sons[0] of tyArray: if t.sons[1].kind != tyEmpty: result = typeAllowedAux(marker, t.sons[1], skVar, flags) + elif kind in {skVar, skLet}: + result = t.sons[1] of tyRef: if kind == skConst: result = t else: result = typeAllowedAux(marker, t.lastSon, skVar, flags+{taHeap}) @@ -1179,11 +1246,13 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, if result != nil: break if result.isNil and t.n != nil: result = typeAllowedNode(marker, t.n, kind, flags) - of tyProxy, tyEmpty: + of tyEmpty: + if kind in {skVar, skLet}: result = t + of tyProxy: # for now same as error node; we say it's a valid type as it should # prevent cascading errors: result = nil - of tyUnused, tyOptAsRef: internalError("typeAllowedAux") + of tyUnused, tyOptAsRef: result = t proc typeAllowed*(t: PType, kind: TSymKind; flags: TTypeAllowedFlags = {}): PType = # returns 'nil' on success and otherwise the part of the type that is @@ -1194,85 +1263,29 @@ proc typeAllowed*(t: PType, kind: TSymKind; flags: TTypeAllowedFlags = {}): PTyp proc align(address, alignment: BiggestInt): BiggestInt = result = (address + (alignment - 1)) and not (alignment - 1) -type - OptKind* = enum ## What to map 'opt T' to internally. - oBool ## opt[T] requires an additional 'bool' field - oNil ## opt[T] has no overhead since 'nil' - ## is available - oEnum ## We can use some enum value that is not yet - ## used for opt[T] - oPtr ## opt[T] actually introduces a hidden pointer - ## in order for the type recursion to work - -proc optKind*(typ: PType): OptKind = - ## return true iff 'opt[T]' can be mapped to 'T' internally - ## because we have a 'nil' value available: - assert typ.kind == tyOpt - case typ.sons[0].skipTypes(abstractInst).kind - of tyRef, tyPtr, tyProc: - result = oNil - of tyArray, tyObject, tyTuple: - result = oPtr - of tyBool: result = oEnum - of tyEnum: - assert(typ.n.sons[0].kind == nkSym) - if typ.n.sons[0].sym.position != low(int): - result = oEnum - else: - result = oBool - else: - result = oBool - -proc optLowering*(typ: PType): PType = - case optKind(typ) - of oNil: result = typ.sons[0] - of oPtr: - result = newType(tyOptAsRef, typ.owner) - result.rawAddSon typ.sons[0] - of oBool: - result = newType(tyTuple, typ.owner) - result.rawAddSon newType(tyBool, typ.owner) - result.rawAddSon typ.sons[0] - of oEnum: - if lastOrd(typ) + 1 < `shl`(BiggestInt(1), 32): - result = newType(tyInt32, typ.owner) - else: - result = newType(tyInt64, typ.owner) - -proc optEnumValue*(typ: PType): BiggestInt = - assert typ.kind == tyOpt - assert optKind(typ) == oEnum - let elem = typ.sons[0].skipTypes(abstractInst).kind - if elem == tyBool: - result = 2 - else: - assert elem == tyEnum - assert typ.n.sons[0].sym.position != low(int) - result = typ.n.sons[0].sym.position - 1 - - const szNonConcreteType* = -3 szIllegalRecursion* = -2 szUnknownSize* = -1 -proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt -proc computeRecSizeAux(n: PNode, a, currOffset: var BiggestInt): BiggestInt = +proc computeSizeAux(conf: ConfigRef; typ: PType, a: var BiggestInt): BiggestInt +proc computeRecSizeAux(conf: ConfigRef; n: PNode, a, currOffset: var BiggestInt): BiggestInt = var maxAlign, maxSize, b, res: BiggestInt case n.kind of nkRecCase: assert(n.sons[0].kind == nkSym) - result = computeRecSizeAux(n.sons[0], a, currOffset) + result = computeRecSizeAux(conf, n.sons[0], a, currOffset) maxSize = 0 maxAlign = 1 for i in countup(1, sonsLen(n) - 1): case n.sons[i].kind of nkOfBranch, nkElse: - res = computeRecSizeAux(lastSon(n.sons[i]), b, currOffset) + res = computeRecSizeAux(conf, lastSon(n.sons[i]), b, currOffset) if res < 0: return res maxSize = max(maxSize, res) maxAlign = max(maxAlign, b) - else: internalError("computeRecSizeAux(record case branch)") + else: + return szIllegalRecursion currOffset = align(currOffset, maxAlign) + maxSize result = align(result, maxAlign) + maxSize a = maxAlign @@ -1280,20 +1293,20 @@ proc computeRecSizeAux(n: PNode, a, currOffset: var BiggestInt): BiggestInt = result = 0 maxAlign = 1 for i in countup(0, sonsLen(n) - 1): - res = computeRecSizeAux(n.sons[i], b, currOffset) + res = computeRecSizeAux(conf, n.sons[i], b, currOffset) if res < 0: return res currOffset = align(currOffset, b) + res result = align(result, b) + res if b > maxAlign: maxAlign = b a = maxAlign of nkSym: - result = computeSizeAux(n.sym.typ, a) + result = computeSizeAux(conf, n.sym.typ, a) n.sym.offset = int(currOffset) else: a = 1 result = szNonConcreteType -proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt = +proc computeSizeAux(conf: ConfigRef; typ: PType, a: var BiggestInt): BiggestInt = var res, maxAlign, length, currOffset: BiggestInt if typ.size == szIllegalRecursion: # we are already computing the size of the type @@ -1307,7 +1320,7 @@ proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt = typ.size = szIllegalRecursion # mark as being computed case typ.kind of tyInt, tyUInt: - result = intSize + result = conf.target.intSize a = result of tyInt8, tyUInt8, tyBool, tyChar: result = 1 @@ -1325,27 +1338,39 @@ proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt = result = 16 a = result of tyFloat: - result = floatSize + result = conf.target.floatSize a = result of tyProc: - if typ.callConv == ccClosure: result = 2 * ptrSize - else: result = ptrSize - a = ptrSize - of tyNil, tyCString, tyString, tySequence, tyPtr, tyRef, tyVar, tyLent, tyOpenArray: + if typ.callConv == ccClosure: result = 2 * conf.target.ptrSize + else: result = conf.target.ptrSize + a = conf.target.ptrSize + of tyString: + if tfHasAsgn in typ.flags: + result = conf.target.ptrSize * 2 + else: + result = conf.target.ptrSize + of tyNil: + result = conf.target.ptrSize + a = result + of tyCString, tySequence, tyPtr, tyRef, tyVar, tyLent, tyOpenArray: let base = typ.lastSon if base == typ or (base.kind == tyTuple and base.size==szIllegalRecursion): result = szIllegalRecursion - else: result = ptrSize + else: + if typ.kind == tySequence and tfHasAsgn in typ.flags: + result = conf.target.ptrSize * 2 + else: + result = conf.target.ptrSize a = result of tyArray: - let elemSize = computeSizeAux(typ.sons[1], a) + let elemSize = computeSizeAux(conf, typ.sons[1], a) if elemSize < 0: return elemSize - result = lengthOrd(typ.sons[0]) * elemSize + result = lengthOrd(conf, typ.sons[0]) * elemSize of tyEnum: - if firstOrd(typ) < 0: + if firstOrd(conf, typ) < 0: result = 4 # use signed int32 else: - length = lastOrd(typ) # BUGFIX: use lastOrd! + length = lastOrd(conf, typ) # BUGFIX: use lastOrd! if length + 1 < `shl`(1, 8): result = 1 elif length + 1 < `shl`(1, 16): result = 2 elif length + 1 < `shl`(BiggestInt(1), 32): result = 4 @@ -1355,7 +1380,7 @@ proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt = if typ.sons[0].kind == tyGenericParam: result = szUnknownSize else: - length = lengthOrd(typ.sons[0]) + length = lengthOrd(conf, typ.sons[0]) if length <= 8: result = 1 elif length <= 16: result = 2 elif length <= 32: result = 4 @@ -1364,12 +1389,12 @@ proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt = else: result = align(length, 8) div 8 + 1 a = result of tyRange: - result = computeSizeAux(typ.sons[0], a) + result = computeSizeAux(conf, typ.sons[0], a) of tyTuple: result = 0 maxAlign = 1 for i in countup(0, sonsLen(typ) - 1): - res = computeSizeAux(typ.sons[i], a) + res = computeSizeAux(conf, typ.sons[i], a) if res < 0: return res maxAlign = max(maxAlign, a) result = align(result, a) + res @@ -1377,60 +1402,52 @@ proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt = a = maxAlign of tyObject: if typ.sons[0] != nil: - result = computeSizeAux(typ.sons[0].skipTypes(skipPtrs), a) + result = computeSizeAux(conf, typ.sons[0].skipTypes(skipPtrs), a) if result < 0: return maxAlign = a elif isObjectWithTypeFieldPredicate(typ): - result = intSize + result = conf.target.intSize maxAlign = result else: result = 0 maxAlign = 1 currOffset = result - result = computeRecSizeAux(typ.n, a, currOffset) + result = computeRecSizeAux(conf, typ.n, a, currOffset) if result < 0: return if a < maxAlign: a = maxAlign result = align(result, a) of tyInferred: if typ.len > 1: - result = computeSizeAux(typ.lastSon, a) - of tyGenericInst, tyDistinct, tyGenericBody, tyAlias: - result = computeSizeAux(lastSon(typ), a) + result = computeSizeAux(conf, typ.lastSon, a) + of tyGenericInst, tyDistinct, tyGenericBody, tyAlias, tySink: + result = computeSizeAux(conf, lastSon(typ), a) of tyTypeClasses: - result = if typ.isResolvedUserTypeClass: computeSizeAux(typ.lastSon, a) + result = if typ.isResolvedUserTypeClass: computeSizeAux(conf, typ.lastSon, a) else: szUnknownSize of tyTypeDesc: - result = computeSizeAux(typ.base, a) + result = computeSizeAux(conf, typ.base, a) of tyForward: return szIllegalRecursion of tyStatic: - result = if typ.n != nil: computeSizeAux(typ.lastSon, a) + result = if typ.n != nil: computeSizeAux(conf, typ.lastSon, a) else: szUnknownSize - of tyOpt: - case optKind(typ) - of oBool: result = computeSizeAux(lastSon(typ), a) + 1 - of oEnum: - if lastOrd(typ) + 1 < `shl`(BiggestInt(1), 32): result = 4 - else: result = 8 - of oNil: result = computeSizeAux(lastSon(typ), a) - of oPtr: result = ptrSize else: #internalError("computeSizeAux()") result = szUnknownSize typ.size = result typ.align = int16(a) -proc computeSize*(typ: PType): BiggestInt = +proc computeSize*(conf: ConfigRef; typ: PType): BiggestInt = var a: BiggestInt = 1 - result = computeSizeAux(typ, a) + result = computeSizeAux(conf, typ, a) proc getReturnType*(s: PSym): PType = # Obtains the return type of a iterator/proc/macro/template assert s.kind in skProcKinds result = s.typ.sons[0] -proc getSize*(typ: PType): BiggestInt = - result = computeSize(typ) - if result < 0: internalError("getSize: " & $typ.kind) +proc getSize*(conf: ConfigRef; typ: PType): BiggestInt = + result = computeSize(conf, typ) + if result < 0: internalError(conf, "getSize: " & $typ.kind) proc containsGenericTypeIter(t: PType, closure: RootRef): bool = case t.kind @@ -1458,8 +1475,7 @@ proc baseOfDistinct*(t: PType): PType = while it.kind in {tyPtr, tyRef}: parent = it it = it.lastSon - if it.kind == tyDistinct: - internalAssert parent != nil + if it.kind == tyDistinct and parent != nil: parent.sons[0] = it.sons[0] proc safeInheritanceDiff*(a, b: PType): int = @@ -1491,8 +1507,9 @@ type proc compatibleEffects*(formal, actual: PType): EffectsCompat = # for proc type compatibility checking: assert formal.kind == tyProc and actual.kind == tyProc - internalAssert formal.n.sons[0].kind == nkEffectList - internalAssert actual.n.sons[0].kind == nkEffectList + if formal.n.sons[0].kind != nkEffectList or + actual.n.sons[0].kind != nkEffectList: + return efTagsUnknown var spec = formal.n.sons[0] if spec.len != 0: @@ -1525,10 +1542,9 @@ proc isCompileTimeOnly*(t: PType): bool {.inline.} = proc containsCompileTimeOnly*(t: PType): bool = if isCompileTimeOnly(t): return true - if t.sons != nil: - for i in 0 ..< t.sonsLen: - if t.sons[i] != nil and isCompileTimeOnly(t.sons[i]): - return true + for i in 0 ..< t.sonsLen: + if t.sons[i] != nil and isCompileTimeOnly(t.sons[i]): + return true return false type @@ -1581,7 +1597,7 @@ proc isEmptyContainer*(t: PType): bool = of tyArray: result = t.sons[1].kind == tyEmpty of tySet, tySequence, tyOpenArray, tyVarargs: result = t.sons[0].kind == tyEmpty - of tyGenericInst, tyAlias: result = isEmptyContainer(t.lastSon) + of tyGenericInst, tyAlias, tySink: result = isEmptyContainer(t.lastSon) else: result = false proc takeType*(formal, arg: PType): PType = @@ -1617,14 +1633,14 @@ proc skipHiddenSubConv*(n: PNode): PNode = else: result = n -proc typeMismatch*(info: TLineInfo, formal, actual: PType) = +proc typeMismatch*(conf: ConfigRef; info: TLineInfo, formal, actual: PType) = if formal.kind != tyError and actual.kind != tyError: let named = typeToString(formal) let desc = typeToString(formal, preferDesc) let x = if named == desc: named else: named & " = " & desc - var msg = msgKindToString(errTypeMismatch) & + var msg = "type mismatch: got <" & typeToString(actual) & "> " & - msgKindToString(errButExpectedX) % [x] + "but expected '" & x & "'" if formal.kind == tyProc and actual.kind == tyProc: case compatibleEffects(formal, actual) @@ -1639,4 +1655,4 @@ proc typeMismatch*(info: TLineInfo, formal, actual: PType) = msg.add "\n.tag effect is 'any tag allowed'" of efLockLevelsDiffer: msg.add "\nlock levels differ" - localError(info, errGenerated, msg) + localError(conf, info, msg) diff --git a/compiler/typesrenderer.nim b/compiler/typesrenderer.nim index 4a9b8d1a9..0c4fe01e1 100644 --- a/compiler/typesrenderer.nim +++ b/compiler/typesrenderer.nim @@ -17,7 +17,6 @@ proc renderPlainSymbolName*(n: PNode): string = ## Use this on documentation name nodes to extract the *raw* symbol name, ## without decorations, parameters, or anything. That can be used as the base ## for the HTML hyperlinks. - result = "" case n.kind of nkPostfix, nkAccQuoted: result = renderPlainSymbolName(n[n.len-1]) @@ -28,8 +27,8 @@ proc renderPlainSymbolName*(n: PNode): string = of nkPragmaExpr: result = renderPlainSymbolName(n[0]) else: - internalError(n.info, "renderPlainSymbolName() with " & $n.kind) - assert(not result.isNil) + result = "" + #internalError(n.info, "renderPlainSymbolName() with " & $n.kind) proc renderType(n: PNode): string = ## Returns a string with the node type or the empty string. @@ -80,7 +79,6 @@ proc renderType(n: PNode): string = for i in 1 ..< len(n): result.add(renderType(n[i]) & ',') result[len(result)-1] = ']' else: result = "" - assert(not result.isNil) proc renderParamTypes(found: var seq[string], n: PNode) = @@ -105,7 +103,8 @@ proc renderParamTypes(found: var seq[string], n: PNode) = for i in 0 ..< typePos: found.add(typeStr) else: - internalError(n.info, "renderParamTypes(found,n) with " & $n.kind) + found.add($n) + #internalError(n.info, "renderParamTypes(found,n) with " & $n.kind) proc renderParamTypes*(n: PNode, sep = defaultParamSeparator): string = ## Returns the types contained in `n` joined by `sep`. diff --git a/compiler/vm.nim b/compiler/vm.nim index 0a706b0fc..e38642de8 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -19,7 +19,7 @@ import ast except getstr import strutils, astalgo, msgs, vmdef, vmgen, nimsets, types, passes, parser, vmdeps, idents, trees, renderer, options, transf, parseutils, - vmmarshal, gorgeimpl + vmmarshal, gorgeimpl, lineinfos, tables, btrees, macrocacheimpl from semfold import leValueConv, ordinalValToString from evaltempl import evalTemplate @@ -61,12 +61,12 @@ proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) = while x != nil: inc calls x = x.next - msgWriteln($calls & " calls omitted\n") + msgWriteln(c.config, $calls & " calls omitted\n") return stackTraceAux(c, x.next, x.comesFrom, recursionLimit-1) var info = c.debug[pc] # we now use the same format as in system/except.nim - var s = substr(toFilename(info), 0) + var s = substr(toFilename(c.config, info), 0) # this 'substr' prevents a strange corruption. XXX This needs to be # investigated eventually but first attempts to fix it broke everything # see the araq-wip-fixed-writebarrier branch. @@ -78,19 +78,21 @@ proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) = if x.prc != nil: for k in 1..max(1, 25-s.len): add(s, ' ') add(s, x.prc.name.s) - msgWriteln(s) + msgWriteln(c.config, s) proc stackTrace(c: PCtx, tos: PStackFrame, pc: int, - msg: TMsgKind, arg = "", n: PNode = nil) = - msgWriteln("stack trace: (most recent call last)") + msg: string, lineInfo: TLineInfo) = + msgWriteln(c.config, "stack trace: (most recent call last)") stackTraceAux(c, tos, pc) # XXX test if we want 'globalError' for every mode - let lineInfo = if n == nil: c.debug[pc] else: n.info - if c.mode == emRepl: globalError(lineInfo, msg, arg) - else: localError(lineInfo, msg, arg) + if c.mode == emRepl: globalError(c.config, lineInfo, msg) + else: localError(c.config, lineInfo, msg) + +proc stackTrace(c: PCtx, tos: PStackFrame, pc: int, msg: string) = + stackTrace(c, tos, pc, msg, c.debug[pc]) proc bailOut(c: PCtx; tos: PStackFrame) = - stackTrace(c, tos, c.exceptionInstr, errUnhandledExceptionX, + stackTrace(c, tos, c.exceptionInstr, "unhandled exception: " & c.currentExceptionA.sons[3].skipColon.strVal) when not defined(nimComputedGoto): @@ -204,16 +206,14 @@ proc asgnComplex(x: var TFullReg, y: TFullReg) = of rkRegisterAddr: x.regAddr = y.regAddr of rkNodeAddr: x.nodeAddr = y.nodeAddr -proc putIntoNode(n: var PNode; x: TFullReg) = +proc writeField(n: var PNode, x: TFullReg) = case x.kind of rkNone: discard of rkInt: n.intVal = x.intVal of rkFloat: n.floatVal = x.floatVal - of rkNode: - if nfIsRef in x.node.flags: n = x.node - else: n[] = x.node[] - of rkRegisterAddr: putIntoNode(n, x.regAddr[]) - of rkNodeAddr: n[] = x.nodeAddr[][] + of rkNode: n = copyValue(x.node) + of rkRegisterAddr: writeField(n, x.regAddr[]) + of rkNodeAddr: n = x.nodeAddr[] proc putIntoReg(dest: var TFullReg; n: PNode) = case n.kind @@ -244,7 +244,8 @@ template getstr(a: untyped): untyped = (if a.kind == rkNode: a.node.strVal else: $chr(int(a.intVal))) proc pushSafePoint(f: PStackFrame; pc: int) = - if f.safePoints.isNil: f.safePoints = @[] + when not defined(nimNoNilSeqs): + if f.safePoints.isNil: f.safePoints = @[] f.safePoints.add(pc) proc popSafePoint(f: PStackFrame) = @@ -257,7 +258,7 @@ proc cleanUpOnException(c: PCtx; tos: PStackFrame): let raisedType = c.currentExceptionA.typ.skipTypes(abstractPtrs) var f = tos while true: - while f.safePoints.isNil or f.safePoints.len == 0: + while f.safePoints.len == 0: f = f.next if f.isNil: return (-1, nil) var pc2 = f.safePoints[f.safePoints.high] @@ -272,7 +273,7 @@ proc cleanUpOnException(c: PCtx; tos: PStackFrame): abstractPtrs) else: nil #echo typeToString(exceptType), " ", typeToString(raisedType) - if exceptType.isNil or inheritanceDiff(exceptType, raisedType) <= 0: + if exceptType.isNil or inheritanceDiff(raisedType, exceptType) <= 0: # mark exception as handled but keep it in B for # the getCurrentException() builtin: c.currentExceptionB = c.currentExceptionA @@ -299,7 +300,6 @@ proc cleanUpOnException(c: PCtx; tos: PStackFrame): discard f.safePoints.pop proc cleanUpOnReturn(c: PCtx; f: PStackFrame): int = - if f.safePoints.isNil: return -1 for s in f.safePoints: var pc = s while c.code[pc].opcode == opcExcept: @@ -308,7 +308,7 @@ proc cleanUpOnReturn(c: PCtx; f: PStackFrame): int = return pc return -1 -proc opConv*(dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType): bool = +proc opConv(c: PCtx; dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType): bool = if desttyp.kind == tyString: if dest.kind != rkNode: myreset(dest) @@ -323,7 +323,7 @@ proc opConv*(dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType): bool = dest.node.strVal = if f.ast.isNil: f.name.s else: f.ast.strVal else: for i in 0..<n.len: - if n.sons[i].kind != nkSym: internalError("opConv for enum") + if n.sons[i].kind != nkSym: internalError(c.config, "opConv for enum") let f = n.sons[i].sym if f.position == x: dest.node.strVal = if f.ast.isNil: f.name.s else: f.ast.strVal @@ -353,7 +353,7 @@ proc opConv*(dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType): bool = of tyChar: dest.node.strVal = $chr(src.intVal) else: - internalError("cannot convert to string " & desttyp.typeToString) + internalError(c.config, "cannot convert to string " & desttyp.typeToString) else: case skipTypes(desttyp, abstractRange).kind of tyInt..tyInt64: @@ -364,7 +364,7 @@ proc opConv*(dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType): bool = dest.intVal = int(src.floatVal) else: dest.intVal = src.intVal - if dest.intVal < firstOrd(desttyp) or dest.intVal > lastOrd(desttyp): + if dest.intVal < firstOrd(c.config, desttyp) or dest.intVal > lastOrd(c.config, desttyp): return true of tyUInt..tyUInt64: if dest.kind != rkInt: @@ -402,9 +402,9 @@ template handleJmpBack() {.dirty.} = if allowInfiniteLoops in c.features: c.loopIterations = MaxLoopIterations else: - msgWriteln("stack trace: (most recent call last)") + msgWriteln(c.config, "stack trace: (most recent call last)") stackTraceAux(c, tos, pc) - globalError(c.debug[pc], errTooManyIterations) + globalError(c.config, c.debug[pc], errTooManyIterations) dec(c.loopIterations) proc recSetFlagIsRef(arg: PNode) = @@ -418,14 +418,14 @@ proc setLenSeq(c: PCtx; node: PNode; newLen: int; info: TLineInfo) = let typ = node.typ.skipTypes(abstractInst+{tyRange}-{tyTypeDesc}) let typeEntry = typ.sons[0].skipTypes(abstractInst+{tyRange}-{tyTypeDesc}) let typeKind = case typeEntry.kind - of tyUInt..tyUInt64: nkUIntLit - of tyRange, tyEnum, tyBool, tyChar, tyInt..tyInt64: nkIntLit - of tyFloat..tyFloat128: nkFloatLit - of tyString: nkStrLit - of tyObject: nkObjConstr - of tySequence: nkNilLit - of tyProc, tyTuple: nkPar - else: nkEmpty + of tyUInt..tyUInt64: nkUIntLit + of tyRange, tyEnum, tyBool, tyChar, tyInt..tyInt64: nkIntLit + of tyFloat..tyFloat128: nkFloatLit + of tyString: nkStrLit + of tyObject: nkObjConstr + of tySequence: nkNilLit + of tyProc, tyTuple: nkTupleConstr + else: nkEmpty let oldLen = node.len setLen(node.sons, newLen) @@ -434,6 +434,17 @@ proc setLenSeq(c: PCtx; node: PNode; newLen: int; info: TLineInfo) = for i in oldLen ..< newLen: node.sons[i] = newNodeI(typeKind, info) +const + errIndexOutOfBounds = "index out of bounds" + errNilAccess = "attempt to access a nil address" + errOverOrUnderflow = "over- or underflow" + errConstantDivisionByZero = "division by zero" + errIllegalConvFromXtoY = "illegal conversion from '$1' to '$2'" + errTooManyIterations = "interpretation requires too many iterations; " & + "if you are sure this is not a bug in your code edit " & + "compiler/vmdef.MaxLoopIterations and rebuild the compiler" + errFieldXNotFound = "node lacks field: " + proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = var pc = start var tos = tos @@ -447,7 +458,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = #if c.traceActive: when traceCode: echo "PC ", pc, " ", c.code[pc].opcode, " ra ", ra, " rb ", instr.regB, " rc ", instr.regC - # message(c.debug[pc], warnUser, "Trace") + # message(c.config, c.debug[pc], warnUser, "Trace") case instr.opcode of opcEof: return regs[ra] @@ -481,9 +492,6 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = asgnComplex(regs[ra], regs[instr.regB]) of opcAsgnRef: asgnRef(regs[ra], regs[instr.regB]) - of opcRegToNode: - decodeB(rkNode) - putIntoNode(regs[ra].node, regs[rb]) of opcNodeToReg: let ra = instr.regA let rb = instr.regB @@ -525,10 +533,10 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = decodeBC(rkInt) let idx = regs[rc].intVal.int let s = regs[rb].node.strVal - if s.isNil: - stackTrace(c, tos, pc, errNilAccess) - elif idx <=% s.len: + if idx <% s.len: regs[ra].intVal = s[idx].ord + elif idx == s.len and optLaxStrings in c.config.options: + regs[ra].intVal = 0 else: stackTrace(c, tos, pc, errIndexOutOfBounds) of opcWrArr: @@ -542,7 +550,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = else: stackTrace(c, tos, pc, errIndexOutOfBounds) elif idx <% arr.len: - putIntoNode(arr.sons[idx], regs[rc]) + writeField(arr.sons[idx], regs[rc]) else: stackTrace(c, tos, pc, errIndexOutOfBounds) of opcLdObj: @@ -562,9 +570,9 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = if dest.kind == nkNilLit: stackTrace(c, tos, pc, errNilAccess) elif dest.sons[shiftedRb].kind == nkExprColonExpr: - putIntoNode(dest.sons[shiftedRb].sons[1], regs[rc]) + writeField(dest.sons[shiftedRb].sons[1], regs[rc]) else: - putIntoNode(dest.sons[shiftedRb], regs[rc]) + writeField(dest.sons[shiftedRb], regs[rc]) of opcWrStrIdx: decodeBC(rkNode) let idx = regs[rb].intVal.int @@ -580,7 +588,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = if regs[rb].kind == rkNode: regs[ra].nodeAddr = addr(regs[rb].node) else: - stackTrace(c, tos, pc, errGenerated, "limited VM support for 'addr'") + stackTrace(c, tos, pc, "limited VM support for 'addr'") of opcLdDeref: # a = b[] let ra = instr.regA @@ -607,9 +615,24 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let ra = instr.regA let rc = instr.regC case regs[ra].kind - of rkNodeAddr: putIntoNode(regs[ra].nodeAddr[], regs[rc]) + of rkNodeAddr: + let n = regs[rc].regToNode + # `var object` parameters are sent as rkNodeAddr. When they are mutated + # vmgen generates opcWrDeref, which means that we must dereference + # twice. + # TODO: This should likely be handled differently in vmgen. + if (nfIsRef notin regs[ra].nodeAddr[].flags and + nfIsRef notin n.flags): + regs[ra].nodeAddr[][] = n[] + else: + regs[ra].nodeAddr[] = n of rkRegisterAddr: regs[ra].regAddr[] = regs[rc] - of rkNode: putIntoNode(regs[ra].node, regs[rc]) + of rkNode: + if regs[ra].node.kind == nkNilLit: + stackTrace(c, tos, pc, errNilAccess) + assert nfIsRef in regs[ra].node.flags + regs[ra].node[] = regs[rc].regToNode[] + regs[ra].node.flags.incl nfIsRef else: stackTrace(c, tos, pc, errNilAccess) of opcAddInt: decodeBC(rkInt) @@ -623,7 +646,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = stackTrace(c, tos, pc, errOverOrUnderflow) of opcAddImmInt: decodeBImm(rkInt) - #message(c.debug[pc], warnUser, "came here") + #message(c.config, c.debug[pc], warnUser, "came here") #debug regs[rb].node let bVal = regs[rb].intVal @@ -683,12 +706,12 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = decodeB(rkNode) var b = newNodeIT(nkCurly, regs[ra].node.info, regs[ra].node.typ) addSon(b, regs[rb].regToNode) - var r = diffSets(regs[ra].node, b) + var r = diffSets(c.config, regs[ra].node, b) discardSons(regs[ra].node) for i in countup(0, sonsLen(r) - 1): addSon(regs[ra].node, r.sons[i]) of opcCard: decodeB(rkInt) - regs[ra].intVal = nimsets.cardSet(regs[rb].node) + regs[ra].intVal = nimsets.cardSet(c.config, regs[rb].node) of opcMulInt: decodeBC(rkInt) let @@ -729,6 +752,9 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = of opcShlInt: decodeBC(rkInt) regs[ra].intVal = regs[rb].intVal shl regs[rc].intVal + of opcAshrInt: + decodeBC(rkInt) + regs[ra].intVal = ashr(regs[rb].intVal, regs[rc].intVal) of opcBitandInt: decodeBC(rkInt) regs[ra].intVal = regs[rb].intVal and regs[rc].intVal @@ -779,10 +805,22 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = regs[ra].intVal = ord(regs[rb].intVal <% regs[rc].intVal) of opcEqRef: decodeBC(rkInt) - regs[ra].intVal = ord((regs[rb].node.kind == nkNilLit and - regs[rc].node.kind == nkNilLit) or - regs[rb].node == regs[rc].node) - of opcEqNimrodNode: + if regs[rb].kind == rkNodeAddr: + if regs[rc].kind == rkNodeAddr: + regs[ra].intVal = ord(regs[rb].nodeAddr == regs[rc].nodeAddr) + else: + assert regs[rc].kind == rkNode + # we know these cannot be equal + regs[ra].intVal = ord(false) + elif regs[rc].kind == rkNodeAddr: + assert regs[rb].kind == rkNode + # we know these cannot be equal + regs[ra].intVal = ord(false) + else: + regs[ra].intVal = ord((regs[rb].node.kind == nkNilLit and + regs[rc].node.kind == nkNilLit) or + regs[rb].node == regs[rc].node) + of opcEqNimNode: decodeBC(rkInt) regs[ra].intVal = ord(exprStructuralEquivalent(regs[rb].node, regs[rc].node, @@ -824,35 +862,35 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = regs[ra].intVal = ord(regs[rb].node.strVal < regs[rc].node.strVal) of opcLeSet: decodeBC(rkInt) - regs[ra].intVal = ord(containsSets(regs[rb].node, regs[rc].node)) + regs[ra].intVal = ord(containsSets(c.config, regs[rb].node, regs[rc].node)) of opcEqSet: decodeBC(rkInt) - regs[ra].intVal = ord(equalSets(regs[rb].node, regs[rc].node)) + regs[ra].intVal = ord(equalSets(c.config, regs[rb].node, regs[rc].node)) of opcLtSet: decodeBC(rkInt) let a = regs[rb].node let b = regs[rc].node - regs[ra].intVal = ord(containsSets(a, b) and not equalSets(a, b)) + regs[ra].intVal = ord(containsSets(c.config, a, b) and not equalSets(c.config, a, b)) of opcMulSet: decodeBC(rkNode) createSet(regs[ra]) move(regs[ra].node.sons, - nimsets.intersectSets(regs[rb].node, regs[rc].node).sons) + nimsets.intersectSets(c.config, regs[rb].node, regs[rc].node).sons) of opcPlusSet: decodeBC(rkNode) createSet(regs[ra]) move(regs[ra].node.sons, - nimsets.unionSets(regs[rb].node, regs[rc].node).sons) + nimsets.unionSets(c.config, regs[rb].node, regs[rc].node).sons) of opcMinusSet: decodeBC(rkNode) createSet(regs[ra]) move(regs[ra].node.sons, - nimsets.diffSets(regs[rb].node, regs[rc].node).sons) + nimsets.diffSets(c.config, regs[rb].node, regs[rc].node).sons) of opcSymdiffSet: decodeBC(rkNode) createSet(regs[ra]) move(regs[ra].node.sons, - nimsets.symdiffSets(regs[rb].node, regs[rc].node).sons) + nimsets.symdiffSets(c.config, regs[rb].node, regs[rc].node).sons) of opcConcatStr: decodeBC(rkNode) createStr regs[ra] @@ -879,18 +917,28 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = if a.kind == nkSym: regs[ra].node = if a.sym.ast.isNil: newNode(nkNilLit) else: copyTree(a.sym.ast) + regs[ra].node.flags.incl nfIsRef + else: + stackTrace(c, tos, pc, "node is not a symbol") + of opcSymOwner: + decodeB(rkNode) + let a = regs[rb].node + if a.kind == nkSym: + regs[ra].node = if a.sym.owner.isNil: newNode(nkNilLit) + else: newSymNode(a.sym.skipGenericOwner) + regs[ra].node.flags.incl nfIsRef else: - stackTrace(c, tos, pc, errFieldXNotFound, "symbol") + stackTrace(c, tos, pc, "node is not a symbol") of opcEcho: let rb = instr.regB if rb == 1: - msgWriteln(regs[ra].node.strVal, {msgStdout}) + msgWriteln(c.config, regs[ra].node.strVal, {msgStdout}) else: var outp = "" for i in ra..ra+rb-1: #if regs[i].kind != rkNode: debug regs[i] outp.add(regs[i].node.strVal) - msgWriteln(outp, {msgStdout}) + msgWriteln(c.config, outp, {msgStdout}) of opcContainsSet: decodeBC(rkInt) regs[ra].intVal = ord(inSet(regs[rb].node, regs[rc].regToNode)) @@ -919,15 +967,15 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let rc = instr.regC if not (leValueConv(regs[rb].regToNode, regs[ra].regToNode) and leValueConv(regs[ra].regToNode, regs[rc].regToNode)): - stackTrace(c, tos, pc, errGenerated, - msgKindToString(errIllegalConvFromXtoY) % [ - $regs[ra].regToNode, "[" & $regs[rb].regToNode & ".." & $regs[rc].regToNode & "]"]) + stackTrace(c, tos, pc, + errIllegalConvFromXtoY % [ + $regs[ra].regToNode, "[" & $regs[rb].regToNode & ".." & $regs[rc].regToNode & "]"]) of opcIndCall, opcIndCallAsgn: # dest = call regStart, n; where regStart = fn, arg1, ... let rb = instr.regB let rc = instr.regC let bb = regs[rb].node - let isClosure = bb.kind == nkPar + let isClosure = bb.kind == nkTupleConstr let prc = if not isClosure: bb.sym else: bb.sons[0].sym if prc.offset < -1: # it's a callback: @@ -937,20 +985,20 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = currentLineInfo: c.debug[pc])) elif sfImportc in prc.flags: if allowFFI notin c.features: - globalError(c.debug[pc], errGenerated, "VM not allowed to do FFI") + globalError(c.config, c.debug[pc], "VM not allowed to do FFI") # we pass 'tos.slots' instead of 'regs' so that the compiler can keep # 'regs' in a register: when hasFFI: let prcValue = c.globals.sons[prc.position-1] if prcValue.kind == nkEmpty: - globalError(c.debug[pc], errGenerated, "canot run " & prc.name.s) + globalError(c.config, c.debug[pc], "cannot run " & prc.name.s) let newValue = callForeignFunction(prcValue, prc.typ, tos.slots, rb+1, rc-1, c.debug[pc]) if newValue.kind != nkEmpty: assert instr.opcode == opcIndCallAsgn putIntoReg(regs[ra], newValue) else: - globalError(c.debug[pc], errGenerated, "VM not built with FFI support") + globalError(c.config, c.debug[pc], "VM not built with FFI support") elif prc.kind != skTemplate: let newPc = compile(c, prc) # tricky: a recursion is also a jump back, so we use the same @@ -960,7 +1008,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = var newFrame = PStackFrame(prc: prc, comesFrom: pc, next: tos) newSeq(newFrame.slots, prc.offset+ord(isClosure)) if not isEmptyType(prc.typ.sons[0]) or prc.kind == skMacro: - putIntoReg(newFrame.slots[0], getNullValue(prc.typ.sons[0], prc.info)) + putIntoReg(newFrame.slots[0], getNullValue(prc.typ.sons[0], prc.info, c.config)) for i in 1 .. rc-1: newFrame.slots[i] = regs[rb+i] if isClosure: @@ -982,7 +1030,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let node = regs[rb+i].regToNode node.info = c.debug[pc] macroCall.add(node) - var a = evalTemplate(macroCall, prc, genSymOwner) + var a = evalTemplate(macroCall, prc, genSymOwner, c.config) if a.kind == nkStmtList and a.len == 1: a = a[0] a.recSetFlagIsRef ensureKind(rkNode) @@ -1067,7 +1115,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = of opcNew: ensureKind(rkNode) let typ = c.types[instr.regBx - wordExcess] - regs[ra].node = getNullValue(typ, c.debug[pc]) + regs[ra].node = getNullValue(typ, c.debug[pc], c.config) regs[ra].node.flags.incl nfIsRef of opcNewSeq: let typ = c.types[instr.regBx - wordExcess] @@ -1079,7 +1127,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = regs[ra].node.typ = typ newSeq(regs[ra].node.sons, count) for i in 0 ..< count: - regs[ra].node.sons[i] = getNullValue(typ.sons[0], c.debug[pc]) + regs[ra].node.sons[i] = getNullValue(typ.sons[0], c.debug[pc], c.config) of opcNewStr: decodeB(rkNode) regs[ra].node = newNodeI(nkStrLit, c.debug[pc]) @@ -1091,7 +1139,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = of opcLdNull: ensureKind(rkNode) let typ = c.types[instr.regBx - wordExcess] - regs[ra].node = getNullValue(typ, c.debug[pc]) + regs[ra].node = getNullValue(typ, c.debug[pc], c.config) # opcLdNull really is the gist of the VM's problems: should it load # a fresh null to regs[ra].node or to regs[ra].node[]? This really # depends on whether regs[ra] represents the variable itself or wether @@ -1138,7 +1186,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = regs[ra].node.strVal = renderTree(regs[rb].regToNode, {renderNoComments, renderDocComments}) of opcQuit: if c.mode in {emRepl, emStaticExpr, emStaticStmt}: - message(c.debug[pc], hintQuitCalled) + message(c.config, c.debug[pc], hintQuitCalled) msgQuit(int8(getOrdValue(regs[ra].regToNode))) else: return TFullReg(kind: rkNone) @@ -1164,25 +1212,46 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = if regs[ra].node.isNil: stackTrace(c, tos, pc, errNilAccess) else: c.setLenSeq(regs[ra].node, newLen, c.debug[pc]) of opcReset: - internalError(c.debug[pc], "too implement") + internalError(c.config, c.debug[pc], "too implement") of opcNarrowS: decodeB(rkInt) let min = -(1.BiggestInt shl (rb-1)) let max = (1.BiggestInt shl (rb-1))-1 if regs[ra].intVal < min or regs[ra].intVal > max: - stackTrace(c, tos, pc, errGenerated, - msgKindToString(errUnhandledExceptionX) % "value out of range") + stackTrace(c, tos, pc, "unhandled exception: value out of range") of opcNarrowU: decodeB(rkInt) regs[ra].intVal = regs[ra].intVal and ((1'i64 shl rb)-1) of opcIsNil: decodeB(rkInt) let node = regs[rb].node - regs[ra].intVal = ord(node.kind == nkNilLit or - (node.kind in {nkStrLit..nkTripleStrLit} and node.strVal.isNil)) + regs[ra].intVal = ord( + # Note that `nfIsRef` + `nkNilLit` represents an allocated + # reference with the value `nil`, so `isNil` should be false! + (node.kind == nkNilLit and nfIsRef notin node.flags) or + (not node.typ.isNil and node.typ.kind == tyProc and + node.typ.callConv == ccClosure and node.sons[0].kind == nkNilLit and + node.sons[1].kind == nkNilLit)) of opcNBindSym: + # cannot use this simple check + # if dynamicBindSym notin c.config.features: + + # bindSym with static input decodeBx(rkNode) regs[ra].node = copyTree(c.constants.sons[rbx]) + regs[ra].node.flags.incl nfIsRef + of opcNDynBindSym: + # experimental bindSym + let + rb = instr.regB + rc = instr.regC + idx = int(regs[rb+rc-1].intVal) + callback = c.callbacks[idx].value + args = VmArgs(ra: ra, rb: rb, rc: rc, slots: cast[pointer](regs), + currentException: c.currentExceptionB, + currentLineInfo: c.debug[pc]) + callback(args) + regs[ra].node.flags.incl nfIsRef of opcNChild: decodeBC(rkNode) let idx = regs[rc].intVal.int @@ -1205,7 +1274,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = if u.kind notin {nkEmpty..nkNilLit}: u.add(regs[rc].node) else: - stackTrace(c, tos, pc, errGenerated, "cannot add to node kind: " & $u.kind) + stackTrace(c, tos, pc, "cannot add to node kind: " & $u.kind) regs[ra].node = u of opcNAddMultiple: decodeBC(rkNode) @@ -1215,38 +1284,46 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = # XXX can be optimized: for i in 0..<x.len: u.add(x.sons[i]) else: - stackTrace(c, tos, pc, errGenerated, "cannot add to node kind: " & $u.kind) + stackTrace(c, tos, pc, "cannot add to node kind: " & $u.kind) regs[ra].node = u of opcNKind: decodeB(rkInt) regs[ra].intVal = ord(regs[rb].node.kind) c.comesFromHeuristic = regs[rb].node.info + of opcNSymKind: + decodeB(rkInt) + let a = regs[rb].node + if a.kind == nkSym: + regs[ra].intVal = ord(a.sym.kind) + else: + stackTrace(c, tos, pc, "node is not a symbol") + c.comesFromHeuristic = regs[rb].node.info of opcNIntVal: decodeB(rkInt) let a = regs[rb].node case a.kind of nkCharLit..nkUInt64Lit: regs[ra].intVal = a.intVal - else: stackTrace(c, tos, pc, errFieldXNotFound, "intVal") + else: stackTrace(c, tos, pc, errFieldXNotFound & "intVal") of opcNFloatVal: decodeB(rkFloat) let a = regs[rb].node case a.kind of nkFloatLit..nkFloat64Lit: regs[ra].floatVal = a.floatVal - else: stackTrace(c, tos, pc, errFieldXNotFound, "floatVal") + else: stackTrace(c, tos, pc, errFieldXNotFound & "floatVal") of opcNSymbol: decodeB(rkNode) let a = regs[rb].node if a.kind == nkSym: regs[ra].node = copyNode(a) else: - stackTrace(c, tos, pc, errFieldXNotFound, "symbol") + stackTrace(c, tos, pc, errFieldXNotFound & "symbol") of opcNIdent: decodeB(rkNode) let a = regs[rb].node if a.kind == nkIdent: regs[ra].node = copyNode(a) else: - stackTrace(c, tos, pc, errFieldXNotFound, "ident") + stackTrace(c, tos, pc, errFieldXNotFound & "ident") of opcNGetType: let rb = instr.regB let rc = instr.regC @@ -1255,84 +1332,106 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = # getType opcode: ensureKind(rkNode) if regs[rb].kind == rkNode and regs[rb].node.typ != nil: - regs[ra].node = opMapTypeToAst(regs[rb].node.typ, c.debug[pc]) + regs[ra].node = opMapTypeToAst(c.cache, regs[rb].node.typ, c.debug[pc]) + elif regs[rb].kind == rkNode and regs[rb].node.kind == nkSym and regs[rb].node.sym.typ != nil: + regs[ra].node = opMapTypeToAst(c.cache, regs[rb].node.sym.typ, c.debug[pc]) else: - stackTrace(c, tos, pc, errGenerated, "node has no type") + stackTrace(c, tos, pc, "node has no type") of 1: # typeKind opcode: ensureKind(rkInt) if regs[rb].kind == rkNode and regs[rb].node.typ != nil: regs[ra].intVal = ord(regs[rb].node.typ.kind) + elif regs[rb].kind == rkNode and regs[rb].node.kind == nkSym and regs[rb].node.sym.typ != nil: + regs[ra].intVal = ord(regs[rb].node.sym.typ.kind) #else: - # stackTrace(c, tos, pc, errGenerated, "node has no type") + # stackTrace(c, tos, pc, "node has no type") of 2: # getTypeInst opcode: ensureKind(rkNode) if regs[rb].kind == rkNode and regs[rb].node.typ != nil: - regs[ra].node = opMapTypeInstToAst(regs[rb].node.typ, c.debug[pc]) + regs[ra].node = opMapTypeInstToAst(c.cache, regs[rb].node.typ, c.debug[pc]) + elif regs[rb].kind == rkNode and regs[rb].node.kind == nkSym and regs[rb].node.sym.typ != nil: + regs[ra].node = opMapTypeInstToAst(c.cache, regs[rb].node.sym.typ, c.debug[pc]) else: - stackTrace(c, tos, pc, errGenerated, "node has no type") + stackTrace(c, tos, pc, "node has no type") else: # getTypeImpl opcode: ensureKind(rkNode) if regs[rb].kind == rkNode and regs[rb].node.typ != nil: - regs[ra].node = opMapTypeImplToAst(regs[rb].node.typ, c.debug[pc]) + regs[ra].node = opMapTypeImplToAst(c.cache, regs[rb].node.typ, c.debug[pc]) + elif regs[rb].kind == rkNode and regs[rb].node.kind == nkSym and regs[rb].node.sym.typ != nil: + regs[ra].node = opMapTypeImplToAst(c.cache, regs[rb].node.sym.typ, c.debug[pc]) else: - stackTrace(c, tos, pc, errGenerated, "node has no type") + stackTrace(c, tos, pc, "node has no type") of opcNStrVal: decodeB(rkNode) createStr regs[ra] let a = regs[rb].node - if a.kind in {nkStrLit..nkTripleStrLit}: regs[ra].node.strVal = a.strVal - elif a.kind == nkCommentStmt: regs[ra].node.strVal = a.comment - else: stackTrace(c, tos, pc, errFieldXNotFound, "strVal") + case a.kind + of {nkStrLit..nkTripleStrLit}: + regs[ra].node.strVal = a.strVal + of nkCommentStmt: + regs[ra].node.strVal = a.comment + of nkIdent: + regs[ra].node.strVal = a.ident.s + of nkSym: + regs[ra].node.strVal = a.sym.name.s + else: + stackTrace(c, tos, pc, errFieldXNotFound & "strVal") of opcSlurp: decodeB(rkNode) createStr regs[ra] regs[ra].node.strVal = opSlurp(regs[rb].node.strVal, c.debug[pc], - c.module) + c.module, c.config) of opcGorge: - decodeBC(rkNode) - inc pc - let rd = c.code[pc].regA - - createStr regs[ra] - regs[ra].node.strVal = opGorge(regs[rb].node.strVal, - regs[rc].node.strVal, regs[rd].node.strVal, - c.debug[pc])[0] - of opcNError: + when defined(nimcore): + decodeBC(rkNode) + inc pc + let rd = c.code[pc].regA + + createStr regs[ra] + regs[ra].node.strVal = opGorge(regs[rb].node.strVal, + regs[rc].node.strVal, regs[rd].node.strVal, + c.debug[pc], c.config)[0] + else: + globalError(c.config, c.debug[pc], "VM is not built with 'gorge' support") + of opcNError, opcNWarning, opcNHint: decodeB(rkNode) let a = regs[ra].node let b = regs[rb].node - stackTrace(c, tos, pc, errUser, a.strVal, if b.kind == nkNilLit: nil else: b) - of opcNWarning: - message(c.debug[pc], warnUser, regs[ra].node.strVal) - of opcNHint: - message(c.debug[pc], hintUser, regs[ra].node.strVal) + let info = if b.kind == nkNilLit: c.debug[pc] else: b.info + if instr.opcode == opcNError: + stackTrace(c, tos, pc, a.strVal, info) + elif instr.opcode == opcNWarning: + message(c.config, info, warnUser, a.strVal) + elif instr.opcode == opcNHint: + message(c.config, info, hintUser, a.strVal) of opcParseExprToAst: decodeB(rkNode) # c.debug[pc].line.int - countLines(regs[rb].strVal) ? var error: string - let ast = parseString(regs[rb].node.strVal, c.cache, c.debug[pc].toFullPath, - c.debug[pc].line.int, - proc (info: TLineInfo; msg: TMsgKind; arg: string) = - if error.isNil and msg <= msgs.errMax: - error = formatMsg(info, msg, arg)) - if not error.isNil: + let ast = parseString(regs[rb].node.strVal, c.cache, c.config, + toFullPath(c.config, c.debug[pc]), c.debug[pc].line.int, + proc (conf: ConfigRef; info: TLineInfo; msg: TMsgKind; arg: string) = + if error.len == 0 and msg <= errMax: + error = formatMsg(conf, info, msg, arg)) + if error.len > 0: c.errorFlag = error elif sonsLen(ast) != 1: - c.errorFlag = formatMsg(c.debug[pc], errExprExpected, "multiple statements") + c.errorFlag = formatMsg(c.config, c.debug[pc], errGenerated, + "expected expression, but got multiple statements") else: regs[ra].node = ast.sons[0] of opcParseStmtToAst: decodeB(rkNode) var error: string - let ast = parseString(regs[rb].node.strVal, c.cache, c.debug[pc].toFullPath, - c.debug[pc].line.int, - proc (info: TLineInfo; msg: TMsgKind; arg: string) = - if error.isNil and msg <= msgs.errMax: - error = formatMsg(info, msg, arg)) - if not error.isNil: + let ast = parseString(regs[rb].node.strVal, c.cache, c.config, + toFullPath(c.config, c.debug[pc]), c.debug[pc].line.int, + proc (conf: ConfigRef; info: TLineInfo; msg: TMsgKind; arg: string) = + if error.len == 0 and msg <= errMax: + error = formatMsg(conf, info, msg, arg)) + if error.len > 0: c.errorFlag = error else: regs[ra].node = ast @@ -1343,52 +1442,75 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = of opcCallSite: ensureKind(rkNode) if c.callsite != nil: regs[ra].node = c.callsite - else: stackTrace(c, tos, pc, errFieldXNotFound, "callsite") - of opcNGetFile: - decodeB(rkNode) - let n = regs[rb].node - regs[ra].node = newStrNode(nkStrLit, n.info.toFilename) - regs[ra].node.info = n.info - regs[ra].node.typ = n.typ - of opcNGetLine: - decodeB(rkNode) + else: stackTrace(c, tos, pc, errFieldXNotFound & "callsite") + of opcNGetLineInfo: + decodeBImm(rkNode) let n = regs[rb].node - regs[ra].node = newIntNode(nkIntLit, n.info.line) + case imm + of 0: # getFile + regs[ra].node = newStrNode(nkStrLit, toFullPath(c.config, n.info)) + of 1: # getLine + regs[ra].node = newIntNode(nkIntLit, n.info.line.int) + of 2: # getColumn + regs[ra].node = newIntNode(nkIntLit, n.info.col) + else: + internalAssert c.config, false regs[ra].node.info = n.info regs[ra].node.typ = n.typ - of opcNGetColumn: + of opcNSetLineInfo: decodeB(rkNode) - let n = regs[rb].node - regs[ra].node = newIntNode(nkIntLit, n.info.col) - regs[ra].node.info = n.info - regs[ra].node.typ = n.typ + regs[ra].node.info = regs[rb].node.info of opcEqIdent: decodeBC(rkInt) - if regs[rb].node.kind == nkIdent and regs[rc].node.kind == nkIdent: - regs[ra].intVal = ord(regs[rb].node.ident.id == regs[rc].node.ident.id) + # aliases for shorter and easier to understand code below + let aNode = regs[rb].node + let bNode = regs[rc].node + # these are cstring to prevent string copy, and cmpIgnoreStyle from + # takes cstring arguments + var aStrVal: cstring = nil + var bStrVal: cstring = nil + # extract strVal from argument ``a`` + case aNode.kind + of {nkStrLit..nkTripleStrLit}: + aStrVal = aNode.strVal.cstring + of nkIdent: + aStrVal = aNode.ident.s.cstring + of nkSym: + aStrVal = aNode.sym.name.s.cstring + of nkOpenSymChoice, nkClosedSymChoice: + aStrVal = aNode[0].sym.name.s.cstring else: - regs[ra].intVal = 0 + discard + # extract strVal from argument ``b`` + case bNode.kind + of {nkStrLit..nkTripleStrLit}: + bStrVal = bNode.strVal.cstring + of nkIdent: + bStrVal = bNode.ident.s.cstring + of nkSym: + bStrVal = bNode.sym.name.s.cstring + of nkOpenSymChoice, nkClosedSymChoice: + bStrVal = bNode[0].sym.name.s.cstring + else: + discard + # set result + regs[ra].intVal = + if aStrVal != nil and bStrVal != nil: + ord(idents.cmpIgnoreStyle(aStrVal,bStrVal,high(int)) == 0) + else: + 0 + of opcStrToIdent: decodeB(rkNode) if regs[rb].node.kind notin {nkStrLit..nkTripleStrLit}: - stackTrace(c, tos, pc, errFieldXNotFound, "strVal") + stackTrace(c, tos, pc, errFieldXNotFound & "strVal") else: regs[ra].node = newNodeI(nkIdent, c.debug[pc]) - regs[ra].node.ident = getIdent(regs[rb].node.strVal) - of opcIdentToStr: - decodeB(rkNode) - let a = regs[rb].node - createStr regs[ra] - regs[ra].node.info = c.debug[pc] - if a.kind == nkSym: - regs[ra].node.strVal = a.sym.name.s - elif a.kind == nkIdent: - regs[ra].node.strVal = a.ident.s - else: - stackTrace(c, tos, pc, errFieldXNotFound, "ident") + regs[ra].node.ident = getIdent(c.cache, regs[rb].node.strVal) + regs[ra].node.flags.incl nfIsRef of opcSetType: if regs[ra].kind != rkNode: - internalError(c.debug[pc], "cannot set type") + internalError(c.config, c.debug[pc], "cannot set type") regs[ra].node.typ = c.types[instr.regBx - wordExcess] of opcConv: let rb = instr.regB @@ -1397,9 +1519,9 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = inc pc let srctyp = c.types[c.code[pc].regBx - wordExcess] - if opConv(regs[ra], regs[rb], desttyp, srctyp): - stackTrace(c, tos, pc, errGenerated, - msgKindToString(errIllegalConvFromXtoY) % [ + if opConv(c, regs[ra], regs[rb], desttyp, srctyp): + stackTrace(c, tos, pc, + errIllegalConvFromXtoY % [ typeToString(srctyp), typeToString(desttyp)]) of opcCast: let rb = instr.regB @@ -1412,7 +1534,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let dest = fficast(regs[rb], desttyp) asgnRef(regs[ra], dest) else: - globalError(c.debug[pc], "cannot evaluate cast") + globalError(c.config, c.debug[pc], "cannot evaluate cast") of opcNSetIntVal: decodeB(rkNode) var dest = regs[ra].node @@ -1420,7 +1542,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = regs[rb].kind in {rkInt}: dest.intVal = regs[rb].intVal else: - stackTrace(c, tos, pc, errFieldXNotFound, "intVal") + stackTrace(c, tos, pc, errFieldXNotFound & "intVal") of opcNSetFloatVal: decodeB(rkNode) var dest = regs[ra].node @@ -1428,26 +1550,26 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = regs[rb].kind in {rkFloat}: dest.floatVal = regs[rb].floatVal else: - stackTrace(c, tos, pc, errFieldXNotFound, "floatVal") + stackTrace(c, tos, pc, errFieldXNotFound & "floatVal") of opcNSetSymbol: decodeB(rkNode) var dest = regs[ra].node if dest.kind == nkSym and regs[rb].node.kind == nkSym: dest.sym = regs[rb].node.sym else: - stackTrace(c, tos, pc, errFieldXNotFound, "symbol") + stackTrace(c, tos, pc, errFieldXNotFound & "symbol") of opcNSetIdent: decodeB(rkNode) var dest = regs[ra].node if dest.kind == nkIdent and regs[rb].node.kind == nkIdent: dest.ident = regs[rb].node.ident else: - stackTrace(c, tos, pc, errFieldXNotFound, "ident") + stackTrace(c, tos, pc, errFieldXNotFound & "ident") of opcNSetType: decodeB(rkNode) let b = regs[rb].node - internalAssert b.kind == nkSym and b.sym.kind == skType - internalAssert regs[ra].node != nil + internalAssert c.config, b.kind == nkSym and b.sym.kind == skType + internalAssert c.config, regs[ra].node != nil regs[ra].node.typ = b.sym.typ of opcNSetStrVal: decodeB(rkNode) @@ -1458,19 +1580,19 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = elif dest.kind == nkCommentStmt and regs[rb].kind in {rkNode}: dest.comment = regs[rb].node.strVal else: - stackTrace(c, tos, pc, errFieldXNotFound, "strVal") + stackTrace(c, tos, pc, errFieldXNotFound & "strVal") of opcNNewNimNode: decodeBC(rkNode) var k = regs[rb].intVal if k < 0 or k > ord(high(TNodeKind)): - internalError(c.debug[pc], + internalError(c.config, c.debug[pc], "request to create a NimNode of invalid kind") let cc = regs[rc].node let x = newNodeI(TNodeKind(int(k)), if cc.kind != nkNilLit: cc.info - elif c.comesFromHeuristic.line > -1: + elif c.comesFromHeuristic.line != 0'u16: c.comesFromHeuristic elif c.callsite != nil and c.callsite.safeLen > 1: c.callsite[1].info @@ -1478,7 +1600,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = c.debug[pc]) x.flags.incl nfIsRef # prevent crashes in the compiler resulting from wrong macros: - if x.kind == nkIdent: x.ident = getIdent"" + if x.kind == nkIdent: x.ident = c.cache.emptyIdent regs[ra].node = x of opcNCopyNimNode: decodeB(rkNode) @@ -1497,16 +1619,117 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let name = if regs[rc].node.strVal.len == 0: ":tmp" else: regs[rc].node.strVal if k < 0 or k > ord(high(TSymKind)): - internalError(c.debug[pc], "request to create symbol of invalid kind") - var sym = newSym(k.TSymKind, name.getIdent, c.module.owner, c.debug[pc]) + internalError(c.config, c.debug[pc], "request to create symbol of invalid kind") + var sym = newSym(k.TSymKind, getIdent(c.cache, name), c.module.owner, c.debug[pc]) incl(sym.flags, sfGenSym) regs[ra].node = newSymNode(sym) + regs[ra].node.flags.incl nfIsRef + of opcNccValue: + decodeB(rkInt) + let destKey = regs[rb].node.strVal + regs[ra].intVal = getOrDefault(c.graph.cacheCounters, destKey) + of opcNccInc: + let g = c.graph + let destKey = regs[ra].node.strVal + let by = regs[instr.regB].intVal + let v = getOrDefault(g.cacheCounters, destKey) + g.cacheCounters[destKey] = v+by + recordInc(c, c.debug[pc], destKey, by) + of opcNcsAdd: + let g = c.graph + let destKey = regs[ra].node.strVal + let val = regs[instr.regB].node + if not contains(g.cacheSeqs, destKey): + g.cacheSeqs[destKey] = newTree(nkStmtList, val) + # newNodeI(nkStmtList, c.debug[pc]) + else: + g.cacheSeqs[destKey].add val + recordAdd(c, c.debug[pc], destKey, val) + of opcNcsIncl: + let g = c.graph + let destKey = regs[ra].node.strVal + let val = regs[instr.regB].node + if not contains(g.cacheSeqs, destKey): + g.cacheSeqs[destKey] = newTree(nkStmtList, val) + else: + block search: + for existing in g.cacheSeqs[destKey]: + if exprStructuralEquivalent(existing, val, strictSymEquality=true): + break search + g.cacheSeqs[destKey].add val + recordIncl(c, c.debug[pc], destKey, val) + of opcNcsLen: + let g = c.graph + decodeB(rkInt) + let destKey = regs[rb].node.strVal + regs[ra].intVal = + if contains(g.cacheSeqs, destKey): g.cacheSeqs[destKey].len else: 0 + of opcNcsAt: + let g = c.graph + decodeBC(rkNode) + let idx = regs[rc].intVal + let destKey = regs[rb].node.strVal + if contains(g.cacheSeqs, destKey) and idx <% g.cacheSeqs[destKey].len: + regs[ra].node = g.cacheSeqs[destKey][idx.int] + else: + stackTrace(c, tos, pc, errIndexOutOfBounds) + of opcNctPut: + let g = c.graph + let destKey = regs[ra].node.strVal + let key = regs[instr.regB].node.strVal + let val = regs[instr.regC].node + if not contains(g.cacheTables, destKey): + g.cacheTables[destKey] = initBTree[string, PNode]() + if not contains(g.cacheTables[destKey], key): + g.cacheTables[destKey].add(key, val) + recordPut(c, c.debug[pc], destKey, key, val) + else: + stackTrace(c, tos, pc, "key already exists: " & key) + of opcNctLen: + let g = c.graph + decodeB(rkInt) + let destKey = regs[rb].node.strVal + regs[ra].intVal = + if contains(g.cacheTables, destKey): g.cacheTables[destKey].len else: 0 + of opcNctGet: + let g = c.graph + decodeBC(rkNode) + let destKey = regs[rb].node.strVal + let key = regs[rc].node.strVal + if contains(g.cacheTables, destKey): + if contains(g.cacheTables[destKey], key): + regs[ra].node = getOrDefault(g.cacheTables[destKey], key) + else: + stackTrace(c, tos, pc, "key does not exist: " & key) + else: + stackTrace(c, tos, pc, "key does not exist: " & destKey) + of opcNctHasNext: + let g = c.graph + decodeBC(rkInt) + let destKey = regs[rb].node.strVal + regs[ra].intVal = + if g.cacheTables.contains(destKey): + ord(btrees.hasNext(g.cacheTables[destKey], regs[rc].intVal.int)) + else: + 0 + of opcNctNext: + let g = c.graph + decodeBC(rkNode) + let destKey = regs[rb].node.strVal + let index = regs[rc].intVal + if contains(g.cacheTables, destKey): + let (k, v, nextIndex) = btrees.next(g.cacheTables[destKey], index.int) + regs[ra].node = newTree(nkTupleConstr, newStrNode(k, c.debug[pc]), v, + newIntNode(nkIntLit, nextIndex)) + else: + stackTrace(c, tos, pc, "key does not exist: " & destKey) + of opcTypeTrait: # XXX only supports 'name' for now; we can use regC to encode the # type trait operation decodeB(rkNode) var typ = regs[rb].node.typ - internalAssert typ != nil + internalAssert c.config, typ != nil while typ.kind == tyTypeDesc and typ.len > 0: typ = typ.sons[0] createStr regs[ra] regs[ra].node.strVal = typ.typeToString(preferExported) @@ -1515,14 +1738,15 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let rb = instr.regB inc pc let typ = c.types[c.code[pc].regBx - wordExcess] - putIntoReg(regs[ra], loadAny(regs[rb].node.strVal, typ)) + putIntoReg(regs[ra], loadAny(regs[rb].node.strVal, typ, c.cache, c.config)) of opcMarshalStore: decodeB(rkNode) inc pc let typ = c.types[c.code[pc].regBx - wordExcess] createStrKeepNode(regs[ra]) - if regs[ra].node.strVal.isNil: regs[ra].node.strVal = newStringOfCap(1000) - storeAny(regs[ra].node.strVal, typ, regs[rb].regToNode) + when not defined(nimNoNilSeqs): + if regs[ra].node.strVal.isNil: regs[ra].node.strVal = newStringOfCap(1000) + storeAny(regs[ra].node.strVal, typ, regs[rb].regToNode, c.config) of opcToNarrowInt: decodeBC(rkInt) let mask = (1'i64 shl rc) - 1 # 0xFF @@ -1544,7 +1768,7 @@ proc execute(c: PCtx, start: int): PNode = proc execProc*(c: PCtx; sym: PSym; args: openArray[PNode]): PNode = if sym.kind in routineKinds: if sym.typ.len-1 != args.len: - localError(sym.info, + localError(c.config, sym.info, "NimScript: expected $# arguments, but got $#" % [ $(sym.typ.len-1), $args.len]) else: @@ -1556,18 +1780,18 @@ proc execProc*(c: PCtx; sym: PSym; args: openArray[PNode]): PNode = # setup parameters: if not isEmptyType(sym.typ.sons[0]) or sym.kind == skMacro: - putIntoReg(tos.slots[0], getNullValue(sym.typ.sons[0], sym.info)) + putIntoReg(tos.slots[0], getNullValue(sym.typ.sons[0], sym.info, c.config)) # XXX We could perform some type checking here. for i in 1..<sym.typ.len: putIntoReg(tos.slots[i], args[i-1]) result = rawExecute(c, start, tos).regToNode else: - localError(sym.info, + localError(c.config, sym.info, "NimScript: attempt to call non-routine: " & sym.name.s) proc evalStmt*(c: PCtx, n: PNode) = - let n = transformExpr(c.module, n) + let n = transformExpr(c.graph, c.module, n) let start = genStmt(c, n) # execute new instructions; this redundant opcEof check saves us lots # of allocations in 'execute': @@ -1575,86 +1799,80 @@ proc evalStmt*(c: PCtx, n: PNode) = discard execute(c, start) proc evalExpr*(c: PCtx, n: PNode): PNode = - let n = transformExpr(c.module, n) + let n = transformExpr(c.graph, c.module, n) let start = genExpr(c, n) assert c.code[start].opcode != opcEof result = execute(c, start) proc getGlobalValue*(c: PCtx; s: PSym): PNode = - internalAssert s.kind in {skLet, skVar} and sfGlobal in s.flags + internalAssert c.config, s.kind in {skLet, skVar} and sfGlobal in s.flags result = c.globals.sons[s.position-1] include vmops -# for now we share the 'globals' environment. XXX Coming soon: An API for -# storing&loading the 'globals' environment to get what a component system -# requires. -var - globalCtx*: PCtx - -proc setupGlobalCtx(module: PSym; cache: IdentCache) = - if globalCtx.isNil: - globalCtx = newCtx(module, cache) - registerAdditionalOps(globalCtx) +proc setupGlobalCtx*(module: PSym; graph: ModuleGraph) = + if graph.vm.isNil: + graph.vm = newCtx(module, graph.cache, graph) + registerAdditionalOps(PCtx graph.vm) else: - refresh(globalCtx, module) + refresh(PCtx graph.vm, module) -proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = +proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = #var c = newEvalContext(module, emRepl) #c.features = {allowCast, allowFFI, allowInfiniteLoops} #pushStackFrame(c, newStackFrame()) # XXX produce a new 'globals' environment here: - setupGlobalCtx(module, cache) - result = globalCtx + setupGlobalCtx(module, graph) + result = PCtx graph.vm when hasFFI: - globalCtx.features = {allowFFI, allowCast} - -var oldErrorCount: int + PCtx(graph.vm).features = {allowFFI, allowCast} proc myProcess(c: PPassContext, n: PNode): PNode = + let c = PCtx(c) # don't eval errornous code: - if oldErrorCount == msgs.gErrorCounter: - evalStmt(PCtx(c), n) - result = emptyNode + if c.oldErrorCount == c.config.errorCounter: + evalStmt(c, n) + result = newNodeI(nkEmpty, n.info) else: result = n - oldErrorCount = msgs.gErrorCounter + c.oldErrorCount = c.config.errorCounter proc myClose(graph: ModuleGraph; c: PPassContext, n: PNode): PNode = myProcess(c, n) -const evalPass* = makePass(myOpen, nil, myProcess, myClose) +const evalPass* = makePass(myOpen, myProcess, myClose) -proc evalConstExprAux(module: PSym; cache: IdentCache; prc: PSym, n: PNode, +proc evalConstExprAux(module: PSym; + g: ModuleGraph; prc: PSym, n: PNode, mode: TEvalMode): PNode = - let n = transformExpr(module, n) - setupGlobalCtx(module, cache) - var c = globalCtx + let n = transformExpr(g, module, n) + setupGlobalCtx(module, g) + var c = PCtx g.vm let oldMode = c.mode defer: c.mode = oldMode c.mode = mode let start = genExpr(c, n, requiresValue = mode!=emStaticStmt) - if c.code[start].opcode == opcEof: return emptyNode + if c.code[start].opcode == opcEof: return newNodeI(nkEmpty, n.info) assert c.code[start].opcode != opcEof when debugEchoCode: c.echoCode start var tos = PStackFrame(prc: prc, comesFrom: 0, next: nil) newSeq(tos.slots, c.prc.maxSlots) #for i in 0 ..< c.prc.maxSlots: tos.slots[i] = newNode(nkEmpty) result = rawExecute(c, start, tos).regToNode - if result.info.line < 0: result.info = n.info + if result.info.col < 0: result.info = n.info -proc evalConstExpr*(module: PSym; cache: IdentCache, e: PNode): PNode = - result = evalConstExprAux(module, cache, nil, e, emConst) +proc evalConstExpr*(module: PSym; g: ModuleGraph; e: PNode): PNode = + result = evalConstExprAux(module, g, nil, e, emConst) -proc evalStaticExpr*(module: PSym; cache: IdentCache, e: PNode, prc: PSym): PNode = - result = evalConstExprAux(module, cache, prc, e, emStaticExpr) +proc evalStaticExpr*(module: PSym; g: ModuleGraph; e: PNode, prc: PSym): PNode = + result = evalConstExprAux(module, g, prc, e, emStaticExpr) -proc evalStaticStmt*(module: PSym; cache: IdentCache, e: PNode, prc: PSym) = - discard evalConstExprAux(module, cache, prc, e, emStaticStmt) +proc evalStaticStmt*(module: PSym; g: ModuleGraph; e: PNode, prc: PSym) = + discard evalConstExprAux(module, g, prc, e, emStaticStmt) -proc setupCompileTimeVar*(module: PSym; cache: IdentCache, n: PNode) = - discard evalConstExprAux(module, cache, nil, n, emStaticStmt) +proc setupCompileTimeVar*(module: PSym; g: ModuleGraph; n: PNode) = + discard evalConstExprAux(module, g, nil, n, emStaticStmt) proc setupMacroParam(x: PNode, typ: PType): TFullReg = case typ.kind @@ -1678,24 +1896,25 @@ iterator genericParamsInMacroCall*(macroSym: PSym, call: PNode): (PSym, PNode) = let posInCall = macroSym.typ.len + i yield (genericParam, call[posInCall]) -var evalMacroCounter: int +# to prevent endless recursion in macro instantiation +const evalMacroLimit = 1000 -proc evalMacroCall*(module: PSym; cache: IdentCache, n, nOrig: PNode, - sym: PSym): PNode = +proc evalMacroCall*(module: PSym; g: ModuleGraph; + n, nOrig: PNode, sym: PSym): PNode = # XXX globalError() is ugly here, but I don't know a better solution for now - inc(evalMacroCounter) - if evalMacroCounter > 100: - globalError(n.info, errTemplateInstantiationTooNested) + inc(g.config.evalMacroCounter) + if g.config.evalMacroCounter > evalMacroLimit: + globalError(g.config, n.info, "macro instantiation too nested") # immediate macros can bypass any type and arity checking so we check the # arity here too: if sym.typ.len > n.safeLen and sym.typ.len > 1: - globalError(n.info, "in call '$#' got $#, but expected $# argument(s)" % [ + globalError(g.config, n.info, "in call '$#' got $#, but expected $# argument(s)" % [ n.renderTree, $(n.safeLen-1), $(sym.typ.len-1)]) - setupGlobalCtx(module, cache) - var c = globalCtx - c.comesFromHeuristic.line = -1 + setupGlobalCtx(module, g) + var c = PCtx g.vm + c.comesFromHeuristic.line = 0'u16 c.callsite = nOrig let start = genProc(c, sym) @@ -1725,18 +1944,18 @@ proc evalMacroCall*(module: PSym; cache: IdentCache, n, nOrig: PNode, if idx < n.len: tos.slots[idx] = setupMacroParam(n.sons[idx], gp[i].sym.typ) else: - dec(evalMacroCounter) + dec(g.config.evalMacroCounter) c.callsite = nil - localError(n.info, "expected " & $gp.len & + localError(c.config, n.info, "expected " & $gp.len & " generic parameter(s)") elif gp[i].sym.typ.kind in {tyStatic, tyTypeDesc}: - dec(evalMacroCounter) + dec(g.config.evalMacroCounter) c.callsite = nil - globalError(n.info, "static[T] or typedesc nor supported for .immediate macros") + globalError(c.config, n.info, "static[T] or typedesc nor supported for .immediate macros") # temporary storage: #for i in L ..< maxSlots: tos.slots[i] = newNode(nkEmpty) result = rawExecute(c, start, tos).regToNode if result.info.line < 0: result.info = n.info - if cyclicTree(result): globalError(n.info, errCyclicTree) - dec(evalMacroCounter) + if cyclicTree(result): globalError(c.config, n.info, "macro produced a cyclic tree") + dec(g.config.evalMacroCounter) c.callsite = nil diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim index 5395d4bad..d642043dc 100644 --- a/compiler/vmdef.nim +++ b/compiler/vmdef.nim @@ -10,13 +10,14 @@ ## This module contains the type definitions for the new evaluation engine. ## An instruction is 1-3 int32s in memory, it is a register based VM. -import ast, passes, msgs, idents, intsets +import ast, passes, msgs, idents, intsets, options, modulegraphs, lineinfos, + tables, btrees const byteExcess* = 128 # we use excess-K for immediates wordExcess* = 32768 - MaxLoopIterations* = 1500_000 # max iterations of all loops + MaxLoopIterations* = 3_000_000 # max iterations of all loops type @@ -35,7 +36,6 @@ type opcAsgnFloat, opcAsgnRef, opcAsgnComplex, - opcRegToNode, opcNodeToReg, opcLdArr, # a = b[c] @@ -57,11 +57,12 @@ type opcLenStr, opcIncl, opcInclRange, opcExcl, opcCard, opcMulInt, opcDivInt, opcModInt, - opcAddFloat, opcSubFloat, opcMulFloat, opcDivFloat, opcShrInt, opcShlInt, + opcAddFloat, opcSubFloat, opcMulFloat, opcDivFloat, + opcShrInt, opcShlInt, opcAshrInt, opcBitandInt, opcBitorInt, opcBitxorInt, opcAddu, opcSubu, opcMulu, opcDivu, opcModu, opcEqInt, opcLeInt, opcLtInt, opcEqFloat, opcLeFloat, opcLtFloat, opcLeu, opcLtu, - opcEqRef, opcEqNimrodNode, opcSameNodeType, + opcEqRef, opcEqNimNode, opcSameNodeType, opcXor, opcNot, opcUnaryMinusInt, opcUnaryMinusFloat, opcBitnotInt, opcEqStr, opcLeStr, opcLtStr, opcEqSet, opcLeSet, opcLtSet, opcMulSet, opcPlusSet, opcMinusSet, opcSymdiffSet, opcConcatStr, @@ -79,6 +80,7 @@ type opcNAdd, opcNAddMultiple, opcNKind, + opcNSymKind, opcNIntVal, opcNFloatVal, opcNSymbol, @@ -90,6 +92,9 @@ type opcNSetFloatVal, opcNSetSymbol, opcNSetIdent, opcNSetType, opcNSetStrVal, opcNNewNimNode, opcNCopyNimNode, opcNCopyNimTree, opcNDel, opcGenSym, + opcNccValue, opcNccInc, opcNcsAdd, opcNcsIncl, opcNcsLen, opcNcsAt, + opcNctPut, opcNctLen, opcNctGet, opcNctHasNext, opcNctNext, + opcSlurp, opcGorge, opcParseExprToAst, @@ -98,10 +103,9 @@ type opcNError, opcNWarning, opcNHint, - opcNGetLine, opcNGetColumn, opcNGetFile, + opcNGetLineInfo, opcNSetLineInfo, opcEqIdent, opcStrToIdent, - opcIdentToStr, opcGetImpl, opcEcho, @@ -133,11 +137,12 @@ type opcLdGlobalAddr, # dest = addr(globals[Bx]) opcLdImmInt, # dest = immediate value - opcNBindSym, + opcNBindSym, opcNDynBindSym, opcSetType, # dest.typ = types[Bx] opcTypeTrait, opcMarshalLoad, opcMarshalStore, - opcToNarrowInt + opcToNarrowInt, + opcSymOwner TBlock* = object label*: PSym @@ -206,24 +211,28 @@ type callbacks*: seq[tuple[key: string, value: VmCallback]] errorFlag*: string cache*: IdentCache + config*: ConfigRef + graph*: ModuleGraph + oldErrorCount*: int TPosition* = distinct int PEvalContext* = PCtx -proc newCtx*(module: PSym; cache: IdentCache): PCtx = +proc newCtx*(module: PSym; cache: IdentCache; g: ModuleGraph): PCtx = PCtx(code: @[], debug: @[], globals: newNode(nkStmtListExpr), constants: newNode(nkStmtList), types: @[], prc: PProc(blocks: @[]), module: module, loopIterations: MaxLoopIterations, comesFromHeuristic: unknownLineInfo(), callbacks: @[], errorFlag: "", - cache: cache) + cache: cache, config: g.config, graph: g) proc refresh*(c: PCtx, module: PSym) = c.module = module c.prc = PProc(blocks: @[]) c.loopIterations = MaxLoopIterations -proc registerCallback*(c: PCtx; name: string; callback: VmCallback) = +proc registerCallback*(c: PCtx; name: string; callback: VmCallback): int {.discardable.} = + result = c.callbacks.len c.callbacks.add((name, callback)) const diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index bb6c47324..bf2418eaf 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -7,24 +7,24 @@ # distribution, for details about the copyright. # -import ast, types, msgs, os, streams, options, idents +import ast, types, msgs, os, streams, options, idents, lineinfos -proc opSlurp*(file: string, info: TLineInfo, module: PSym): string = +proc opSlurp*(file: string, info: TLineInfo, module: PSym; conf: ConfigRef): string = try: - var filename = parentDir(info.toFullPath) / file + var filename = parentDir(toFullPath(conf, info)) / file if not fileExists(filename): - filename = file.findFile + filename = findFile(conf, file) result = readFile(filename) # we produce a fake include statement for every slurped filename, so that # the module dependencies are accurate: appendToModule(module, newNode(nkIncludeStmt, info, @[ newStrNode(nkStrLit, filename)])) except IOError: - localError(info, errCannotOpenFile, file) + localError(conf, info, "cannot open file: " & file) result = "" -proc atomicTypeX(name: string; m: TMagic; t: PType; info: TLineInfo): PNode = - let sym = newSym(skType, getIdent(name), t.owner, info) +proc atomicTypeX(cache: IdentCache; name: string; m: TMagic; t: PType; info: TLineInfo): PNode = + let sym = newSym(skType, getIdent(cache, name), t.owner, info) sym.magic = m sym.typ = t result = newSymNode(sym) @@ -34,51 +34,51 @@ proc atomicTypeX(s: PSym; info: TLineInfo): PNode = result = newSymNode(s) result.info = info -proc mapTypeToAstX(t: PType; info: TLineInfo; +proc mapTypeToAstX(cache: IdentCache; t: PType; info: TLineInfo; inst=false; allowRecursionX=false): PNode -proc mapTypeToBracketX(name: string; m: TMagic; t: PType; info: TLineInfo; +proc mapTypeToBracketX(cache: IdentCache; name: string; m: TMagic; t: PType; info: TLineInfo; inst=false): PNode = result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) - result.add atomicTypeX(name, m, t, info) + result.add atomicTypeX(cache, name, m, t, info) for i in 0 ..< t.len: if t.sons[i] == nil: - let void = atomicTypeX("void", mVoid, t, info) + let void = atomicTypeX(cache, "void", mVoid, t, info) void.typ = newType(tyVoid, t.owner) result.add void else: - result.add mapTypeToAstX(t.sons[i], info, inst) + result.add mapTypeToAstX(cache, t.sons[i], info, inst) -proc objectNode(n: PNode): PNode = +proc objectNode(cache: IdentCache; n: PNode): PNode = if n.kind == nkSym: result = newNodeI(nkIdentDefs, n.info) result.add n # name - result.add mapTypeToAstX(n.sym.typ, n.info, true, false) # type - result.add ast.emptyNode # no assigned value + result.add mapTypeToAstX(cache, n.sym.typ, n.info, true, false) # type + result.add newNodeI(nkEmpty, n.info) # no assigned value else: result = copyNode(n) for i in 0 ..< n.safeLen: - result.add objectNode(n[i]) + result.add objectNode(cache, n[i]) -proc mapTypeToAstX(t: PType; info: TLineInfo; +proc mapTypeToAstX(cache: IdentCache; t: PType; info: TLineInfo; inst=false; allowRecursionX=false): PNode = var allowRecursion = allowRecursionX - template atomicType(name, m): untyped = atomicTypeX(name, m, t, info) + template atomicType(name, m): untyped = atomicTypeX(cache, name, m, t, info) template atomicType(s): untyped = atomicTypeX(s, info) - template mapTypeToAst(t,info): untyped = mapTypeToAstX(t, info, inst) - template mapTypeToAstR(t,info): untyped = mapTypeToAstX(t, info, inst, true) + template mapTypeToAst(t,info): untyped = mapTypeToAstX(cache, t, info, inst) + template mapTypeToAstR(t,info): untyped = mapTypeToAstX(cache, t, info, inst, true) template mapTypeToAst(t,i,info): untyped = - if i<t.len and t.sons[i]!=nil: mapTypeToAstX(t.sons[i], info, inst) - else: ast.emptyNode + if i<t.len and t.sons[i]!=nil: mapTypeToAstX(cache, t.sons[i], info, inst) + else: newNodeI(nkEmpty, info) template mapTypeToBracket(name, m, t, info): untyped = - mapTypeToBracketX(name, m, t, info, inst) + mapTypeToBracketX(cache, name, m, t, info, inst) template newNodeX(kind): untyped = newNodeIT(kind, if t.n.isNil: info else: t.n.info, t) template newIdentDefs(n,t): untyped = var id = newNodeX(nkIdentDefs) id.add n # name id.add mapTypeToAst(t, info) # type - id.add ast.emptyNode # no assigned value + id.add newNodeI(nkEmpty, info) # no assigned value id template newIdentDefs(s): untyped = newIdentDefs(s, s.typ) @@ -103,7 +103,7 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; result.add atomicType("array", mArray) if inst and t.sons[0].kind == tyRange: var rng = newNodeX(nkInfix) - rng.add newIdentNode(getIdent(".."), info) + rng.add newIdentNode(getIdent(cache, ".."), info) rng.add t.sons[0].n.sons[0].copyTree rng.add t.sons[0].n.sons[1].copyTree result.add rng @@ -132,14 +132,14 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; for i in 1 ..< t.len-1: result.add mapTypeToAst(t.sons[i], info) else: - result = mapTypeToAstX(t.lastSon, info, inst, allowRecursion) + result = mapTypeToAstX(cache, t.lastSon, info, inst, allowRecursion) of tyGenericBody: if inst: result = mapTypeToAstR(t.lastSon, info) else: result = mapTypeToAst(t.lastSon, info) of tyAlias: - result = mapTypeToAstX(t.lastSon, info, inst, allowRecursion) + result = mapTypeToAstX(cache, t.lastSon, info, inst, allowRecursion) of tyOrdinal: result = mapTypeToAst(t.lastSon, info) of tyDistinct: @@ -156,22 +156,22 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; of tyObject: if inst: result = newNodeX(nkObjectTy) - result.add ast.emptyNode # pragmas not reconstructed yet - if t.sons[0] == nil: result.add ast.emptyNode # handle parent object + result.add newNodeI(nkEmpty, info) # pragmas not reconstructed yet + if t.sons[0] == nil: result.add newNodeI(nkEmpty, info) # handle parent object else: var nn = newNodeX(nkOfInherit) nn.add mapTypeToAst(t.sons[0], info) result.add nn if t.n.len > 0: - result.add objectNode(t.n) + result.add objectNode(cache, t.n) else: - result.add ast.emptyNode + result.add newNodeI(nkEmpty, info) else: if allowRecursion or t.sym == nil: result = newNodeIT(nkObjectTy, if t.n.isNil: info else: t.n.info, t) - result.add ast.emptyNode + result.add newNodeI(nkEmpty, info) if t.sons[0] == nil: - result.add ast.emptyNode + result.add newNodeI(nkEmpty, info) else: result.add mapTypeToAst(t.sons[0], info) result.add copyTree(t.n) @@ -179,14 +179,14 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; result = atomicType(t.sym) of tyEnum: result = newNodeIT(nkEnumTy, if t.n.isNil: info else: t.n.info, t) - result.add ast.emptyNode # pragma node, currently always empty for enum + result.add newNodeI(nkEmpty, info) # pragma node, currently always empty for enum for c in t.n.sons: result.add copyTree(c) of tyTuple: if inst: # only named tuples have a node, unnamed tuples don't if t.n.isNil: - result = newNodeX(nkPar) + result = newNodeX(nkTupleConstr) for subType in t.sons: result.add mapTypeToAst(subType, info) else: @@ -208,7 +208,12 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; result.add mapTypeToAst(t.sons[0], info) else: result = mapTypeToBracket("ref", mRef, t, info) - of tyVar: result = mapTypeToBracket("var", mVar, t, info) + of tyVar: + if inst: + result = newNodeX(nkVarTy) + result.add mapTypeToAst(t.sons[0], info) + else: + result = mapTypeToBracket("var", mVar, t, info) of tyLent: result = mapTypeToBracket("lent", mBuiltinType, t, info) of tySink: result = mapTypeToBracket("sink", mBuiltinType, t, info) of tySequence: result = mapTypeToBracket("seq", mSeq, t, info) @@ -218,13 +223,14 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; result = newNodeX(nkProcTy) var fp = newNodeX(nkFormalParams) if t.sons[0] == nil: - fp.add ast.emptyNode + fp.add newNodeI(nkEmpty, info) else: fp.add mapTypeToAst(t.sons[0], t.n[0].info) for i in 1..<t.sons.len: fp.add newIdentDefs(t.n[i], t.sons[i]) result.add fp - result.add ast.emptyNode # pragmas aren't reconstructed yet + result.add if t.n[0].len > 0: t.n[0][pragmasEffects].copyTree + else: newNodeI(nkEmpty, info) else: result = mapTypeToBracket("proc", mNone, t, info) of tyOpenArray: result = mapTypeToBracket("openArray", mOpenArray, t, info) @@ -266,7 +272,7 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; of tyOr: result = mapTypeToBracket("or", mOr, t, info) of tyNot: result = mapTypeToBracket("not", mNot, t, info) of tyAnything: result = atomicType("anything", mNone) - of tyInferred: internalAssert false + of tyInferred: assert false of tyStatic, tyFromExpr: if inst: if t.n != nil: result = t.n.copyTree @@ -276,17 +282,17 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; result.add atomicType("static", mNone) if t.n != nil: result.add t.n.copyTree - of tyUnused, tyOptAsRef: internalError("mapTypeToAstX") + of tyUnused, tyOptAsRef: assert(false, "mapTypeToAstX") -proc opMapTypeToAst*(t: PType; info: TLineInfo): PNode = - result = mapTypeToAstX(t, info, false, true) +proc opMapTypeToAst*(cache: IdentCache; t: PType; info: TLineInfo): PNode = + result = mapTypeToAstX(cache, t, info, false, true) # the "Inst" version includes generic parameters in the resulting type tree # and also tries to look like the corresponding Nim type declaration -proc opMapTypeInstToAst*(t: PType; info: TLineInfo): PNode = - result = mapTypeToAstX(t, info, true, false) +proc opMapTypeInstToAst*(cache: IdentCache; t: PType; info: TLineInfo): PNode = + result = mapTypeToAstX(cache, t, info, true, false) # the "Impl" version includes generic parameters in the resulting type tree # and also tries to look like the corresponding Nim type implementation -proc opMapTypeImplToAst*(t: PType; info: TLineInfo): PNode = - result = mapTypeToAstX(t, info, true, true) +proc opMapTypeImplToAst*(cache: IdentCache; t: PType; info: TLineInfo): PNode = + result = mapTypeToAstX(cache, t, info, true, true) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index a22acdff0..e87347ec8 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -29,7 +29,7 @@ import strutils, ast, astalgo, types, msgs, renderer, vmdef, - trees, intsets, rodread, magicsys, options, lowerings + trees, intsets, magicsys, options, lowerings, lineinfos import platform from os import splitFile @@ -37,11 +37,13 @@ when hasFFI: import evalffi type - TGenFlag = enum gfAddrOf, gfFieldAccess + TGenFlag = enum + gfNode # Affects how variables are loaded - always loads as rkNode + gfNodeAddr # Affects how variables are loaded - always loads as rkNodeAddr TGenFlags = set[TGenFlag] -proc debugInfo(info: TLineInfo): string = - result = info.toFilename.splitFile.name & ":" & $info.line +proc debugInfo(c: PCtx; info: TLineInfo): string = + result = toFilename(c.config, info).splitFile.name & ":" & $info.line proc codeListing(c: PCtx, result: var string, start=0; last = -1) = # first iteration: compute all necessary labels: @@ -85,7 +87,7 @@ proc codeListing(c: PCtx, result: var string, start=0; last = -1) = else: result.addf("\t$#\tr$#, $#", ($opc).substr(3), x.regA, x.regBx-wordExcess) result.add("\t#") - result.add(debugInfo(c.debug[i])) + result.add(debugInfo(c, c.debug[i])) result.add("\n") inc i @@ -120,7 +122,7 @@ proc gABI(c: PCtx; n: PNode; opc: TOpcode; a, b: TRegister; imm: BiggestInt) = c.code.add(ins) c.debug.add(n.info) else: - localError(n.info, errGenerated, + localError(c.config, n.info, "VM: immediate value does not fit into an int8") proc gABx(c: PCtx; n: PNode; opc: TOpcode; a: TRegister = 0; bx: int) = @@ -137,7 +139,7 @@ proc gABx(c: PCtx; n: PNode; opc: TOpcode; a: TRegister = 0; bx: int) = c.code.add(ins) c.debug.add(n.info) else: - localError(n.info, errGenerated, + localError(c.config, n.info, "VM: immediate value does not fit into an int16") proc xjmp(c: PCtx; n: PNode; opc: TOpcode; a: TRegister = 0): TPosition = @@ -151,7 +153,7 @@ proc genLabel(c: PCtx): TPosition = proc jmpBack(c: PCtx, n: PNode, p = TPosition(0)) = let dist = p.int - c.code.len - internalAssert(-0x7fff < dist and dist < 0x7fff) + internalAssert(c.config, -0x7fff < dist and dist < 0x7fff) gABx(c, n, opcJmpBack, 0, dist) proc patch(c: PCtx, p: TPosition) = @@ -159,7 +161,7 @@ proc patch(c: PCtx, p: TPosition) = let p = p.int let diff = c.code.len - p #c.jumpTargets.incl(c.code.len) - internalAssert(-0x7fff < diff and diff < 0x7fff) + internalAssert(c.config, -0x7fff < diff and diff < 0x7fff) let oldInstr = c.code[p] # opcode and regA stay the same: c.code[p] = ((oldInstr.uint32 and 0xffff'u32).uint32 or @@ -201,7 +203,7 @@ proc getTemp(cc: PCtx; tt: PType): TRegister = c.slots[i] = (inUse: true, kind: k) return TRegister(i) if c.maxSlots >= high(TRegister): - globalError(cc.bestEffort, "VM problem: too many registers required") + globalError(cc.config, cc.bestEffort, "VM problem: too many registers required") result = TRegister(c.maxSlots) c.slots[c.maxSlots] = (inUse: true, kind: k) inc c.maxSlots @@ -223,7 +225,7 @@ proc getTempRange(cc: PCtx; n: int; kind: TSlotKind): TRegister = for k in result .. result+n-1: c.slots[k] = (inUse: true, kind: kind) return if c.maxSlots+n >= high(TRegister): - globalError(cc.bestEffort, "VM problem: too many registers required") + globalError(cc.config, cc.bestEffort, "VM problem: too many registers required") result = TRegister(c.maxSlots) inc c.maxSlots, n for k in result .. result+n-1: c.slots[k] = (inUse: true, kind: kind) @@ -251,7 +253,7 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) proc gen(c: PCtx; n: PNode; dest: TRegister; flags: TGenFlags = {}) = var d: TDest = dest gen(c, n, d, flags) - internalAssert d == dest + #internalAssert c.config, d == dest # issue #7407 proc gen(c: PCtx; n: PNode; flags: TGenFlags = {}) = var tmp: TDest = -1 @@ -261,7 +263,7 @@ proc gen(c: PCtx; n: PNode; flags: TGenFlags = {}) = proc genx(c: PCtx; n: PNode; flags: TGenFlags = {}): TRegister = var tmp: TDest = -1 gen(c, n, tmp, flags) - #internalAssert tmp >= 0 # 'nim check' does not like this internalAssert. + #internalAssert c.config, tmp >= 0 # 'nim check' does not like this internalAssert. if tmp >= 0: result = TRegister(tmp) @@ -320,7 +322,7 @@ proc genBreak(c: PCtx; n: PNode) = if c.prc.blocks[i].label == n.sons[0].sym: c.prc.blocks[i].fixups.add L1 return - globalError(n.info, errGenerated, "VM problem: cannot find 'break' target") + globalError(c.config, n.info, "VM problem: cannot find 'break' target") else: c.prc.blocks[c.prc.blocks.high].fixups.add L1 @@ -378,7 +380,7 @@ proc rawGenLiteral(c: PCtx; n: PNode): int = #assert(n.kind != nkCall) n.flags.incl nfAllConst c.constants.add n.canonValue - internalAssert result < 0x7fff + internalAssert c.config, result < 0x7fff proc sameConstant*(a, b: PNode): bool = result = false @@ -405,10 +407,10 @@ proc genLiteral(c: PCtx; n: PNode): int = if sameConstant(c.constants[i], n): return i result = rawGenLiteral(c, n) -proc unused(n: PNode; x: TDest) {.inline.} = +proc unused(c: PCtx; n: PNode; x: TDest) {.inline.} = if x >= 0: #debug(n) - globalError(n.info, "not unused") + globalError(c.config, n.info, "not unused") proc genCase(c: PCtx; n: PNode; dest: var TDest) = # if (!expr1) goto L1; @@ -424,7 +426,7 @@ proc genCase(c: PCtx; n: PNode; dest: var TDest) = if not isEmptyType(n.typ): if dest < 0: dest = getTemp(c, n.typ) else: - unused(n, dest) + unused(c, n, dest) var endings: seq[TPosition] = @[] withTemp(tmp, n.sons[0].typ): c.gen(n.sons[0], tmp) @@ -451,7 +453,7 @@ proc genType(c: PCtx; typ: PType): int = if sameType(t, typ): return i result = c.types.len c.types.add(typ) - internalAssert(result <= 0x7fff) + internalAssert(c.config, result <= 0x7fff) proc genTry(c: PCtx; n: PNode; dest: var TDest) = if dest < 0 and not isEmptyType(n.typ): dest = getTemp(c, n.typ) @@ -525,7 +527,7 @@ proc genCall(c: PCtx; n: PNode; dest: var TDest) = var r: TRegister = x+i c.gen(n.sons[i], r) if i >= fntyp.len: - internalAssert tfVarargs in fntyp.flags + internalAssert c.config, tfVarargs in fntyp.flags c.gABx(n, opcSetType, r, c.genType(n.sons[i].typ)) if dest < 0: c.gABC(n, opcIndCall, 0, x, n.len) @@ -540,17 +542,17 @@ proc needsAsgnPatch(n: PNode): bool = n.kind in {nkBracketExpr, nkDotExpr, nkCheckedFieldExpr, nkDerefExpr, nkHiddenDeref} or (n.kind == nkSym and n.sym.isGlobal) -proc genField(n: PNode): TRegister = +proc genField(c: PCtx; n: PNode): TRegister = if n.kind != nkSym or n.sym.kind != skField: - globalError(n.info, "no field symbol") + globalError(c.config, n.info, "no field symbol") let s = n.sym if s.position > high(result): - globalError(n.info, + globalError(c.config, n.info, "too large offset! cannot generate code for: " & s.name.s) result = s.position proc genIndex(c: PCtx; n: PNode; arr: PType): TRegister = - if arr.skipTypes(abstractInst).kind == tyArray and (let x = firstOrd(arr); + if arr.skipTypes(abstractInst).kind == tyArray and (let x = firstOrd(c.config, arr); x != 0): let tmp = c.genx(n) # freeing the temporary here means we can produce: regA = regA - Imm @@ -563,7 +565,7 @@ proc genIndex(c: PCtx; n: PNode; arr: PType): TRegister = proc genAsgnPatch(c: PCtx; le: PNode, value: TRegister) = case le.kind of nkBracketExpr: - let dest = c.genx(le.sons[0], {gfAddrOf, gfFieldAccess}) + let dest = c.genx(le.sons[0], {gfNode}) let idx = c.genIndex(le.sons[1], le.sons[0].typ) c.gABC(le, opcWrArr, dest, idx, value) c.freeTemp(dest) @@ -571,17 +573,17 @@ proc genAsgnPatch(c: PCtx; le: PNode, value: TRegister) = of nkDotExpr, nkCheckedFieldExpr: # XXX field checks here let left = if le.kind == nkDotExpr: le else: le.sons[0] - let dest = c.genx(left.sons[0], {gfAddrOf, gfFieldAccess}) - let idx = genField(left.sons[1]) + let dest = c.genx(left.sons[0], {gfNode}) + let idx = genField(c, left.sons[1]) c.gABC(left, opcWrObj, dest, idx, value) c.freeTemp(dest) of nkDerefExpr, nkHiddenDeref: - let dest = c.genx(le.sons[0], {gfAddrOf}) + let dest = c.genx(le.sons[0], {gfNode}) c.gABC(le, opcWrDeref, dest, 0, value) c.freeTemp(dest) of nkSym: if le.sym.isGlobal: - let dest = c.genx(le, {gfAddrOf}) + let dest = c.genx(le, {gfNodeAddr}) c.gABC(le, opcWrDeref, dest, 0, value) c.freeTemp(dest) else: @@ -767,19 +769,19 @@ proc genIntCast(c: PCtx; n: PNode; dest: var TDest) = var unsignedIntegers = {tyUInt8..tyUInt32, tyChar} let src = n.sons[1].typ.skipTypes(abstractRange)#.kind let dst = n.sons[0].typ.skipTypes(abstractRange)#.kind - let src_size = src.getSize + let src_size = getSize(c.config, src) - if platform.intSize < 8: + if c.config.target.intSize < 8: signedIntegers.incl(tyInt) unsignedIntegers.incl(tyUInt) - if src_size == dst.getSize and src.kind in allowedIntegers and + if src_size == getSize(c.config, dst) and src.kind in allowedIntegers and dst.kind in allowedIntegers: let tmp = c.genx(n.sons[1]) var tmp2 = c.getTemp(n.sons[1].typ) let tmp3 = c.getTemp(n.sons[1].typ) if dest < 0: dest = c.getTemp(n[0].typ) proc mkIntLit(ival: int): int = - result = genLiteral(c, newIntTypeNode(nkIntLit, ival, getSysType(tyInt))) + result = genLiteral(c, newIntTypeNode(nkIntLit, ival, getSysType(c.graph, n.info, tyInt))) if src.kind in unsignedIntegers and dst.kind in signedIntegers: # cast unsigned to signed integer of same size # signedVal = (unsignedVal xor offset) -% offset @@ -790,7 +792,7 @@ proc genIntCast(c: PCtx; n: PNode; dest: var TDest) = elif src.kind in signedIntegers and dst.kind in unsignedIntegers: # cast signed to unsigned integer of same size # unsignedVal = (offset +% signedVal +% 1) and offset - let offset = (1 shl (src_size * 8)) - 1 + let offset = (1 shl (src_size * 8)) - 1 c.gABx(n, opcLdConst, tmp2, mkIntLit(offset)) c.gABx(n, opcLdConst, dest, mkIntLit(offset+1)) c.gABC(n, opcAddu, tmp3, tmp, dest) @@ -802,7 +804,55 @@ proc genIntCast(c: PCtx; n: PNode; dest: var TDest) = c.freeTemp(tmp2) c.freeTemp(tmp3) else: - globalError(n.info, errGenerated, "VM is only allowed to 'cast' between integers of same size") + globalError(c.config, n.info, "VM is only allowed to 'cast' between integers of same size") + +proc genVoidABC(c: PCtx, n: PNode, dest: TDest, opcode: TOpcode) = + unused(c, n, dest) + var + tmp1 = c.genx(n[1]) + tmp2 = c.genx(n[2]) + tmp3 = c.genx(n[3]) + c.gABC(n, opcode, tmp1, tmp2, tmp3) + c.freeTemp(tmp1) + c.freeTemp(tmp2) + c.freeTemp(tmp3) + +proc genBindSym(c: PCtx; n: PNode; dest: var TDest) = + # nah, cannot use c.config.features because sempass context + # can have local experimental switch + # if dynamicBindSym notin c.config.features: + if n.len == 2: # hmm, reliable? + # bindSym with static input + if n[1].kind in {nkClosedSymChoice, nkOpenSymChoice, nkSym}: + let idx = c.genLiteral(n[1]) + if dest < 0: dest = c.getTemp(n.typ) + c.gABx(n, opcNBindSym, dest, idx) + else: + localError(c.config, n.info, "invalid bindSym usage") + else: + # experimental bindSym + if dest < 0: dest = c.getTemp(n.typ) + let x = c.getTempRange(n.len, slotTempUnknown) + + # callee symbol + var tmp0 = TDest(x) + c.genLit(n.sons[0], tmp0) + + # original parameters + for i in 1..<n.len-2: + var r = TRegister(x+i) + c.gen(n.sons[i], r) + + # info node + var tmp1 = TDest(x+n.len-2) + c.genLit(n.sons[^2], tmp1) + + # payload idx + var tmp2 = TDest(x+n.len-1) + c.genLit(n.sons[^1], tmp2) + + c.gABC(n, opcNDynBindSym, dest, x, n.len) + c.freeTempRange(x, n.len) proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = case m @@ -818,7 +868,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of mSucc, mAddI: c.genAddSubInt(n, dest, opcAddInt) of mInc, mDec: - unused(n, dest) + unused(c, n, dest) let opc = if m == mInc: opcAddInt else: opcSubInt let d = c.genx(n.sons[1]) if n.sons[2].isInt8Lit: @@ -832,10 +882,10 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = c.freeTemp(d) of mOrd, mChr, mArrToSeq: c.gen(n.sons[1], dest) of mNew, mNewFinalize: - unused(n, dest) + unused(c, n, dest) c.genNew(n) of mNewSeq: - unused(n, dest) + unused(c, n, dest) c.genNewSeq(n) of mNewSeqOfCap: c.genNewSeqOfCap(n, dest) of mNewString: @@ -844,7 +894,8 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of mNewStringOfCap: # we ignore the 'cap' argument and translate it as 'newString(0)'. # eval n.sons[1] for possible side effects: - var tmp = c.genx(n.sons[1]) + c.freeTemp(c.genx(n.sons[1])) + var tmp = c.getTemp(n.sons[1].typ) c.gABx(n, opcLdImmInt, tmp, 0) if dest < 0: dest = c.getTemp(n.typ) c.gABC(n, opcNewStr, dest, tmp) @@ -855,7 +906,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of mLengthStr, mXLenStr: genUnaryABI(c, n, dest, opcLenStr) of mIncl, mExcl: - unused(n, dest) + unused(c, n, dest) var d = c.genx(n.sons[1]) var tmp = c.genx(n.sons[2]) c.genSetType(n.sons[1], d) @@ -888,6 +939,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = c.freeTemp(tmp2) of mShlI: genBinaryABCnarrowU(c, n, dest, opcShlInt) + of mAshrI: genBinaryABCnarrow(c, n, dest, opcAshrInt) of mBitandI: genBinaryABCnarrowU(c, n, dest, opcBitandInt) of mBitorI: genBinaryABCnarrowU(c, n, dest, opcBitorInt) of mBitxorI: genBinaryABCnarrowU(c, n, dest, opcBitxorInt) @@ -952,20 +1004,20 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of mInSet: genBinarySet(c, n, dest, opcContainsSet) of mRepr: genUnaryABC(c, n, dest, opcRepr) of mExit: - unused(n, dest) + unused(c, n, dest) var tmp = c.genx(n.sons[1]) c.gABC(n, opcQuit, tmp) c.freeTemp(tmp) of mSetLengthStr, mSetLengthSeq: - unused(n, dest) + unused(c, n, dest) var d = c.genx(n.sons[1]) var tmp = c.genx(n.sons[2]) c.gABC(n, if m == mSetLengthStr: opcSetLenStr else: opcSetLenSeq, d, tmp) c.genAsgnPatch(n.sons[1], d) c.freeTemp(tmp) of mSwap: - unused(n, dest) - c.gen(lowerSwap(n, if c.prc == nil: c.module else: c.prc.sym)) + unused(c, n, dest) + c.gen(lowerSwap(c.graph, n, if c.prc == nil: c.module else: c.prc.sym)) of mIsNil: genUnaryABC(c, n, dest, opcIsNil) of mCopyStr: if dest < 0: dest = c.getTemp(n.typ) @@ -996,7 +1048,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = # skip 'nkHiddenAddr': let d2AsNode = n.sons[2].sons[0] if needsAsgnPatch(d2AsNode): - d2 = c.getTemp(getSysType(tyFloat)) + d2 = c.getTemp(getSysType(c.graph, n.info, tyFloat)) else: d2 = c.genx(d2AsNode) var @@ -1009,13 +1061,13 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = c.genAsgnPatch(d2AsNode, d2) c.freeTemp(d2) of mReset: - unused(n, dest) + unused(c, n, dest) var d = c.genx(n.sons[1]) c.gABC(n, opcReset, d) of mOf, mIs: if dest < 0: dest = c.getTemp(n.typ) var tmp = c.genx(n.sons[1]) - var idx = c.getTemp(getSysType(tyInt)) + var idx = c.getTemp(getSysType(c.graph, n.info, tyInt)) var typ = n.sons[2].typ if m == mOf: typ = typ.skipTypes(abstractPtrs-{tyTypeDesc}) c.gABx(n, opcLdImmInt, idx, c.genType(typ)) @@ -1023,7 +1075,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = c.freeTemp(tmp) c.freeTemp(idx) of mSizeOf: - globalError(n.info, errCannotInterpretNodeX, renderTree(n)) + globalError(c.config, n.info, "cannot run in the VM: " & renderTree(n)) of mHigh: if dest < 0: dest = c.getTemp(n.typ) let tmp = c.genx(n.sons[1]) @@ -1034,23 +1086,23 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = c.gABI(n, opcLenSeq, dest, tmp, 1) c.freeTemp(tmp) of mEcho: - unused(n, dest) + unused(c, n, dest) let n = n[1].skipConv let x = c.getTempRange(n.len, slotTempUnknown) - internalAssert n.kind == nkBracket + internalAssert c.config, n.kind == nkBracket for i in 0..<n.len: var r: TRegister = x+i c.gen(n.sons[i], r) c.gABC(n, opcEcho, x, n.len) c.freeTempRange(x, n.len) of mAppendStrCh: - unused(n, dest) + unused(c, n, dest) genBinaryStmtVar(c, n, opcAddStrCh) of mAppendStrStr: - unused(n, dest) + unused(c, n, dest) genBinaryStmtVar(c, n, opcAddStrStr) of mAppendSeqElem: - unused(n, dest) + unused(c, n, dest) genBinaryStmtVar(c, n, opcAddSeqElem) of mParseExprToAst: genUnaryABC(c, n, dest, opcParseExprToAst) @@ -1066,20 +1118,27 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of mStaticExec: genBinaryABCD(c, n, dest, opcGorge) of mNLen: genUnaryABI(c, n, dest, opcLenSeq, nimNodeFlag) of mGetImpl: genUnaryABC(c, n, dest, opcGetImpl) + of mSymOwner: genUnaryABC(c, n, dest, opcSymOwner) of mNChild: genBinaryABC(c, n, dest, opcNChild) - of mNSetChild, mNDel: - unused(n, dest) - var - tmp1 = c.genx(n.sons[1]) - tmp2 = c.genx(n.sons[2]) - tmp3 = c.genx(n.sons[3]) - c.gABC(n, if m == mNSetChild: opcNSetChild else: opcNDel, tmp1, tmp2, tmp3) - c.freeTemp(tmp1) - c.freeTemp(tmp2) - c.freeTemp(tmp3) + of mNSetChild: genVoidABC(c, n, dest, opcNSetChild) + of mNDel: genVoidABC(c, n, dest, opcNDel) of mNAdd: genBinaryABC(c, n, dest, opcNAdd) of mNAddMultiple: genBinaryABC(c, n, dest, opcNAddMultiple) of mNKind: genUnaryABC(c, n, dest, opcNKind) + of mNSymKind: genUnaryABC(c, n, dest, opcNSymKind) + + of mNccValue: genUnaryABC(c, n, dest, opcNccValue) + of mNccInc: genBinaryABC(c, n, dest, opcNccInc) + of mNcsAdd: genBinaryABC(c, n, dest, opcNcsAdd) + of mNcsIncl: genBinaryABC(c, n, dest, opcNcsIncl) + of mNcsLen: genUnaryABC(c, n, dest, opcNcsLen) + of mNcsAt: genBinaryABC(c, n, dest, opcNcsAt) + of mNctPut: genVoidABC(c, n, dest, opcNctPut) + of mNctLen: genUnaryABC(c, n, dest, opcNctLen) + of mNctGet: genBinaryABC(c, n, dest, opcNctGet) + of mNctHasNext: genBinaryABC(c, n, dest, opcNctHasNext) + of mNctNext: genBinaryABC(c, n, dest, opcNctNext) + of mNIntVal: genUnaryABC(c, n, dest, opcNIntVal) of mNFloatVal: genUnaryABC(c, n, dest, opcNFloatVal) of mNSymbol: genUnaryABC(c, n, dest, opcNSymbol) @@ -1097,61 +1156,54 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = #genUnaryABC(c, n, dest, opcNGetType) of mNStrVal: genUnaryABC(c, n, dest, opcNStrVal) of mNSetIntVal: - unused(n, dest) + unused(c, n, dest) genBinaryStmt(c, n, opcNSetIntVal) of mNSetFloatVal: - unused(n, dest) + unused(c, n, dest) genBinaryStmt(c, n, opcNSetFloatVal) of mNSetSymbol: - unused(n, dest) + unused(c, n, dest) genBinaryStmt(c, n, opcNSetSymbol) of mNSetIdent: - unused(n, dest) + unused(c, n, dest) genBinaryStmt(c, n, opcNSetIdent) of mNSetType: - unused(n, dest) + unused(c, n, dest) genBinaryStmt(c, n, opcNSetType) of mNSetStrVal: - unused(n, dest) + unused(c, n, dest) genBinaryStmt(c, n, opcNSetStrVal) of mNNewNimNode: genBinaryABC(c, n, dest, opcNNewNimNode) of mNCopyNimNode: genUnaryABC(c, n, dest, opcNCopyNimNode) of mNCopyNimTree: genUnaryABC(c, n, dest, opcNCopyNimTree) - of mNBindSym: - if n[1].kind in {nkClosedSymChoice, nkOpenSymChoice, nkSym}: - let idx = c.genLiteral(n[1]) - if dest < 0: dest = c.getTemp(n.typ) - c.gABx(n, opcNBindSym, dest, idx) - else: - localError(n.info, "invalid bindSym usage") + of mNBindSym: genBindSym(c, n, dest) of mStrToIdent: genUnaryABC(c, n, dest, opcStrToIdent) - of mIdentToStr: genUnaryABC(c, n, dest, opcIdentToStr) of mEqIdent: genBinaryABC(c, n, dest, opcEqIdent) - of mEqNimrodNode: genBinaryABC(c, n, dest, opcEqNimrodNode) + of mEqNimrodNode: genBinaryABC(c, n, dest, opcEqNimNode) of mSameNodeType: genBinaryABC(c, n, dest, opcSameNodeType) of mNLineInfo: case n[0].sym.name.s - of "getFile": - genUnaryABC(c, n, dest, opcNGetFile) - of "getLine": - genUnaryABC(c, n, dest, opcNGetLine) - of "getColumn": - genUnaryABC(c, n, dest, opcNGetColumn) - else: - internalAssert false + of "getFile": genUnaryABI(c, n, dest, opcNGetLineInfo, 0) + of "getLine": genUnaryABI(c, n, dest, opcNGetLineInfo, 1) + of "getColumn": genUnaryABI(c, n, dest, opcNGetLineInfo, 2) + of "copyLineInfo": + internalAssert c.config, n.len == 3 + unused(c, n, dest) + genBinaryStmt(c, n, opcNSetLineInfo) + else: internalAssert c.config, false of mNHint: - unused(n, dest) - genUnaryStmt(c, n, opcNHint) + unused(c, n, dest) + genBinaryStmt(c, n, opcNHint) of mNWarning: - unused(n, dest) - genUnaryStmt(c, n, opcNWarning) + unused(c, n, dest) + genBinaryStmt(c, n, opcNWarning) of mNError: if n.len <= 1: # query error condition: c.gABC(n, opcQueryErrorFlag, dest) else: # setter - unused(n, dest) + unused(c, n, dest) genBinaryStmt(c, n, opcNError) of mNCallSite: if dest < 0: dest = c.getTemp(n.typ) @@ -1162,7 +1214,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = c.genCall(n, dest) of mExpandToAst: if n.len != 2: - globalError(n.info, errGenerated, "expandToAst requires 1 argument") + globalError(c.config, n.info, "expandToAst requires 1 argument") let arg = n.sons[1] if arg.kind in nkCallKinds: #if arg[0].kind != nkSym or arg[0].sym.kind notin {skTemplate, skMacro}: @@ -1172,12 +1224,12 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = # do not call clearDest(n, dest) here as getAst has a meta-type as such # produces a value else: - globalError(n.info, "expandToAst requires a call expression") + globalError(c.config, n.info, "expandToAst requires a call expression") of mRunnableExamples: discard "just ignore any call to runnableExamples" else: # mGCref, mGCunref, - globalError(n.info, "cannot generate code for: " & $m) + globalError(c.config, n.info, "cannot generate code for: " & $m) proc genMarshalLoad(c: PCtx, n: PNode, dest: var TDest) = ## Signature: proc to*[T](data: string): T @@ -1235,41 +1287,21 @@ proc canElimAddr(n: PNode): PNode = # addr ( deref ( x )) --> x result = n.sons[0].sons[0] -proc genAddrDeref(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode; - flags: TGenFlags) = - # a nop for certain types - let isAddr = opc in {opcAddrNode, opcAddrReg} - if isAddr and (let m = canElimAddr(n); m != nil): +proc genAddr(c: PCtx, n: PNode, dest: var TDest, flags: TGenFlags) = + if (let m = canElimAddr(n); m != nil): gen(c, m, dest, flags) return - let af = if n[0].kind in {nkBracketExpr, nkDotExpr, nkCheckedFieldExpr}: {gfAddrOf, gfFieldAccess} - else: {gfAddrOf} - let newflags = if isAddr: flags+af else: flags - # consider: - # proc foo(f: var ref int) = - # f = new(int) - # proc blah() = - # var x: ref int - # foo x - # - # The type of 'f' is 'var ref int' and of 'x' is 'ref int'. Hence for - # nkAddr we must not use 'unneededIndirection', but for deref we use it. - if not isAddr and unneededIndirection(n.sons[0]): - gen(c, n.sons[0], dest, newflags) - if gfAddrOf notin flags and fitsRegister(n.typ): - c.gABC(n, opcNodeToReg, dest, dest) - elif isAddr and isGlobal(n.sons[0]): + let af = if n[0].kind in {nkBracketExpr, nkDotExpr, nkCheckedFieldExpr}: {gfNode} + else: {gfNodeAddr} + let newflags = flags-{gfNode, gfNodeAddr}+af + + if isGlobal(n.sons[0]): gen(c, n.sons[0], dest, flags+af) else: let tmp = c.genx(n.sons[0], newflags) if dest < 0: dest = c.getTemp(n.typ) - if not isAddr: - gABC(c, n, opc, dest, tmp) - assert n.typ != nil - if gfAddrOf notin flags and fitsRegister(n.typ): - c.gABC(n, opcNodeToReg, dest, dest) - elif c.prc.slots[tmp].kind >= slotTempUnknown: + if c.prc.slots[tmp].kind >= slotTempUnknown: gABC(c, n, opcAddrNode, dest, tmp) # hack ahead; in order to fix bug #1781 we mark the temporary as # permanent, so that it's not used for anything else: @@ -1280,6 +1312,19 @@ proc genAddrDeref(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode; gABC(c, n, opcAddrReg, dest, tmp) c.freeTemp(tmp) +proc genDeref(c: PCtx, n: PNode, dest: var TDest, flags: TGenFlags) = + if unneededIndirection(n.sons[0]): + gen(c, n.sons[0], dest, flags) + if {gfNodeAddr, gfNode} * flags == {} and fitsRegister(n.typ): + c.gABC(n, opcNodeToReg, dest, dest) + else: + let tmp = c.genx(n.sons[0], flags) + if dest < 0: dest = c.getTemp(n.typ) + gABC(c, n, opcLdDeref, dest, tmp) + assert n.typ != nil + if {gfNodeAddr, gfNode} * flags == {} and fitsRegister(n.typ): + c.gABC(n, opcNodeToReg, dest, dest) + proc whichAsgnOpc(n: PNode): TOpcode = case n.typ.skipTypes(abstractRange-{tyTypeDesc}).kind of tyBool, tyChar, tyEnum, tyOrdinal, tyInt..tyInt64, tyUInt..tyUInt64: @@ -1288,7 +1333,7 @@ proc whichAsgnOpc(n: PNode): TOpcode = opcAsgnStr of tyFloat..tyFloat128: opcAsgnFloat - of tyRef, tyNil, tyVar, tyLent: + of tyRef, tyNil, tyVar, tyLent, tyPtr: opcAsgnRef else: opcAsgnComplex @@ -1306,14 +1351,14 @@ proc setSlot(c: PCtx; v: PSym) = if v.position == 0: if c.prc.maxSlots == 0: c.prc.maxSlots = 1 if c.prc.maxSlots >= high(TRegister): - globalError(v.info, "cannot generate code; too many registers required") + globalError(c.config, v.info, "cannot generate code; too many registers required") v.position = c.prc.maxSlots c.prc.slots[v.position] = (inUse: true, kind: if v.kind == skLet: slotFixedLet else: slotFixedVar) inc c.prc.maxSlots -proc cannotEval(n: PNode) {.noinline.} = - globalError(n.info, errGenerated, "cannot evaluate at compile time: " & +proc cannotEval(c: PCtx; n: PNode) {.noinline.} = + globalError(c.config, n.info, "cannot evaluate at compile time: " & n.renderTree) proc isOwnedBy(a, b: PSym): bool = @@ -1333,10 +1378,10 @@ proc checkCanEval(c: PCtx; n: PNode) = if {sfCompileTime, sfGlobal} <= s.flags: return if s.kind in {skVar, skTemp, skLet, skParam, skResult} and not s.isOwnedBy(c.prc.sym) and s.owner != c.module and c.mode != emRepl: - cannotEval(n) + cannotEval(c, n) elif s.kind in {skProc, skFunc, skConverter, skMethod, skIterator} and sfForward in s.flags: - cannotEval(n) + cannotEval(c, n) proc isTemp(c: PCtx; dest: TDest): bool = result = dest >= 0 and c.prc.slots[dest].kind >= slotTempUnknown @@ -1365,7 +1410,7 @@ proc preventFalseAlias(c: PCtx; n: PNode; opc: TOpcode; proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) = case le.kind of nkBracketExpr: - let dest = c.genx(le.sons[0], {gfAddrOf, gfFieldAccess}) + let dest = c.genx(le.sons[0], {gfNode}) let idx = c.genIndex(le.sons[1], le.sons[0].typ) let tmp = c.genx(ri) if le.sons[0].typ.skipTypes(abstractVarRange-{tyTypeDesc}).kind in { @@ -1377,13 +1422,13 @@ proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) = of nkDotExpr, nkCheckedFieldExpr: # XXX field checks here let left = if le.kind == nkDotExpr: le else: le.sons[0] - let dest = c.genx(left.sons[0], {gfAddrOf, gfFieldAccess}) - let idx = genField(left.sons[1]) + let dest = c.genx(left.sons[0], {gfNode}) + let idx = genField(c, left.sons[1]) let tmp = c.genx(ri) c.preventFalseAlias(left, opcWrObj, dest, idx, tmp) c.freeTemp(tmp) of nkDerefExpr, nkHiddenDeref: - let dest = c.genx(le.sons[0], {gfAddrOf}) + let dest = c.genx(le.sons[0], {gfNode}) let tmp = c.genx(ri) c.preventFalseAlias(le, opcWrDeref, dest, 0, tmp) c.freeTemp(tmp) @@ -1392,13 +1437,13 @@ proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) = checkCanEval(c, le) if s.isGlobal: withTemp(tmp, le.typ): - c.gen(le, tmp, {gfAddrOf}) + c.gen(le, tmp, {gfNodeAddr}) let val = c.genx(ri) c.preventFalseAlias(le, opcWrDeref, tmp, 0, val) c.freeTemp(val) else: if s.kind == skForVar: c.setSlot s - internalAssert s.position > 0 or (s.position == 0 and + internalAssert c.config, s.position > 0 or (s.position == 0 and s.kind in {skParam,skResult}) var dest: TRegister = s.position + ord(s.kind == skParam) assert le.typ != nil @@ -1410,7 +1455,7 @@ proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) = else: gen(c, ri, dest) else: - let dest = c.genx(le, {gfAddrOf}) + let dest = c.genx(le, {gfNodeAddr}) genAsgn(c, dest, ri, requiresCopy) proc genTypeLit(c: PCtx; t: PType; dest: var TDest) = @@ -1424,15 +1469,15 @@ proc importcSym(c: PCtx; info: TLineInfo; s: PSym) = c.globals.add(importcSymbol(s)) s.position = c.globals.len else: - localError(info, errGenerated, "VM is not allowed to 'importc'") + localError(c.config, info, "VM is not allowed to 'importc'") else: - localError(info, errGenerated, + localError(c.config, info, "cannot 'importc' variable at compile time") -proc getNullValue*(typ: PType, info: TLineInfo): PNode +proc getNullValue*(typ: PType, info: TLineInfo; conf: ConfigRef): PNode proc genGlobalInit(c: PCtx; n: PNode; s: PSym) = - c.globals.add(getNullValue(s.typ, n.info)) + c.globals.add(getNullValue(s.typ, n.info, c.config)) s.position = c.globals.len # This is rather hard to support, due to the laziness of the VM code # generator. See tests/compile/tmacro2 for why this is necessary: @@ -1446,24 +1491,26 @@ proc genGlobalInit(c: PCtx; n: PNode; s: PSym) = c.freeTemp(tmp) proc genRdVar(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = + # gfNodeAddr and gfNode are mutually exclusive + assert card(flags * {gfNodeAddr, gfNode}) < 2 let s = n.sym if s.isGlobal: if sfCompileTime in s.flags or c.mode == emRepl: discard elif s.position == 0: - cannotEval(n) + cannotEval(c, n) if s.position == 0: if sfImportc in s.flags: c.importcSym(n.info, s) else: genGlobalInit(c, n, s) if dest < 0: dest = c.getTemp(n.typ) assert s.typ != nil - if gfAddrOf notin flags and fitsRegister(s.typ): + if gfNodeAddr in flags: + c.gABx(n, opcLdGlobalAddr, dest, s.position) + elif fitsRegister(s.typ) and gfNode notin flags: var cc = c.getTemp(n.typ) c.gABx(n, opcLdGlobal, cc, s.position) c.gABC(n, opcNodeToReg, dest, cc) c.freeTemp(cc) - elif {gfAddrOf, gfFieldAccess} * flags == {gfAddrOf}: - c.gABx(n, opcLdGlobalAddr, dest, s.position) else: c.gABx(n, opcLdGlobal, dest, s.position) else: @@ -1472,16 +1519,17 @@ proc genRdVar(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = s.kind in {skParam,skResult}): if dest < 0: dest = s.position + ord(s.kind == skParam) - internalAssert(c.prc.slots[dest].kind < slotSomeTemp) + internalAssert(c.config, c.prc.slots[dest].kind < slotSomeTemp) else: # we need to generate an assignment: genAsgn(c, dest, n, c.prc.slots[dest].kind >= slotSomeTemp) else: # see tests/t99bott for an example that triggers it: - cannotEval(n) + cannotEval(c, n) template needsRegLoad(): untyped = - gfAddrOf notin flags and fitsRegister(n.typ.skipTypes({tyVar, tyLent})) + {gfNode, gfNodeAddr} * flags == {} and + fitsRegister(n.typ.skipTypes({tyVar, tyLent})) proc genArrAccess2(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode; flags: TGenFlags) = @@ -1502,7 +1550,7 @@ proc genArrAccess2(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode; proc genObjAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = let a = c.genx(n.sons[0], flags) - let b = genField(n.sons[1]) + let b = genField(c, n.sons[1]) if dest < 0: dest = c.getTemp(n.typ) if needsRegLoad(): var cc = c.getTemp(n.typ) @@ -1526,24 +1574,23 @@ proc genArrAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = else: genArrAccess2(c, n, dest, opcLdArr, flags) -proc getNullValueAux(obj: PNode, result: PNode) = +proc getNullValueAux(obj: PNode, result: PNode; conf: ConfigRef) = case obj.kind of nkRecList: - for i in countup(0, sonsLen(obj) - 1): getNullValueAux(obj.sons[i], result) + for i in countup(0, sonsLen(obj) - 1): getNullValueAux(obj.sons[i], result, conf) of nkRecCase: - getNullValueAux(obj.sons[0], result) + getNullValueAux(obj.sons[0], result, conf) for i in countup(1, sonsLen(obj) - 1): - getNullValueAux(lastSon(obj.sons[i]), result) + getNullValueAux(lastSon(obj.sons[i]), result, conf) of nkSym: let field = newNodeI(nkExprColonExpr, result.info) field.add(obj) - field.add(getNullValue(obj.sym.typ, result.info)) + field.add(getNullValue(obj.sym.typ, result.info, conf)) addSon(result, field) - else: globalError(result.info, "cannot create null element for: " & $obj) + else: globalError(conf, result.info, "cannot create null element for: " & $obj) -proc getNullValue(typ: PType, info: TLineInfo): PNode = +proc getNullValue(typ: PType, info: TLineInfo; conf: ConfigRef): PNode = var t = skipTypes(typ, abstractRange-{tyTypeDesc}) - result = emptyNode case t.kind of tyBool, tyEnum, tyChar, tyInt..tyInt64: result = newNodeIT(nkIntLit, info, t) @@ -1553,14 +1600,15 @@ proc getNullValue(typ: PType, info: TLineInfo): PNode = result = newNodeIT(nkFloatLit, info, t) of tyCString, tyString: result = newNodeIT(nkStrLit, info, t) - of tyVar, tyLent, tyPointer, tyPtr, tySequence, tyExpr, + result.strVal = "" + of tyVar, tyLent, tyPointer, tyPtr, tyExpr, tyStmt, tyTypeDesc, tyStatic, tyRef, tyNil: result = newNodeIT(nkNilLit, info, t) of tyProc: if t.callConv != ccClosure: result = newNodeIT(nkNilLit, info, t) else: - result = newNodeIT(nkPar, info, t) + result = newNodeIT(nkTupleConstr, info, t) result.add(newNodeIT(nkNilLit, info, t)) result.add(newNodeIT(nkNilLit, info, t)) of tyObject: @@ -1569,23 +1617,26 @@ proc getNullValue(typ: PType, info: TLineInfo): PNode = # initialize inherited fields: var base = t.sons[0] while base != nil: - getNullValueAux(skipTypes(base, skipPtrs).n, result) + getNullValueAux(skipTypes(base, skipPtrs).n, result, conf) base = base.sons[0] - getNullValueAux(t.n, result) + getNullValueAux(t.n, result, conf) of tyArray: result = newNodeIT(nkBracket, info, t) - for i in countup(0, int(lengthOrd(t)) - 1): - addSon(result, getNullValue(elemType(t), info)) + for i in countup(0, int(lengthOrd(conf, t)) - 1): + addSon(result, getNullValue(elemType(t), info, conf)) of tyTuple: - result = newNodeIT(nkPar, info, t) + result = newNodeIT(nkTupleConstr, info, t) for i in countup(0, sonsLen(t) - 1): - addSon(result, getNullValue(t.sons[i], info)) + addSon(result, getNullValue(t.sons[i], info, conf)) of tySet: result = newNodeIT(nkCurly, info, t) of tyOpt: result = newNodeIT(nkNilLit, info, t) + of tySequence: + result = newNodeIT(nkBracket, info, t) else: - globalError(info, "cannot create null element for: " & $t.kind) + globalError(conf, info, "cannot create null element for: " & $t.kind) + result = newNodeI(nkEmpty, info) proc ldNullOpcode(t: PType): TOpcode = assert t != nil @@ -1599,7 +1650,7 @@ proc genVarSection(c: PCtx; n: PNode) = for i in 0 .. a.len-3: if not a[i].sym.isGlobal: setSlot(c, a[i].sym) checkCanEval(c, a[i]) - c.gen(lowerTupleUnpacking(a, c.getOwner)) + c.gen(lowerTupleUnpacking(c.graph, a, c.getOwner)) elif a.sons[0].kind == nkSym: let s = a.sons[0].sym checkCanEval(c, a.sons[0]) @@ -1607,14 +1658,14 @@ proc genVarSection(c: PCtx; n: PNode) = if s.position == 0: if sfImportc in s.flags: c.importcSym(a.info, s) else: - let sa = getNullValue(s.typ, a.info) + let sa = getNullValue(s.typ, a.info, c.config) #if s.ast.isNil: getNullValue(s.typ, a.info) #else: canonValue(s.ast) assert sa.kind != nkCall c.globals.add(sa) s.position = c.globals.len if a.sons[2].kind != nkEmpty: - let tmp = c.genx(a.sons[0], {gfAddrOf}) + let tmp = c.genx(a.sons[0], {gfNodeAddr}) let val = c.genx(a.sons[2]) c.genAdditionalCopy(a.sons[2], opcWrDeref, tmp, 0, val) c.freeTemp(val) @@ -1649,7 +1700,7 @@ proc genArrayConstr(c: PCtx, n: PNode, dest: var TDest) = if dest < 0: dest = c.getTemp(n.typ) c.gABx(n, opcLdNull, dest, c.genType(n.typ)) - let intType = getSysType(tyInt) + let intType = getSysType(c.graph, n.info, tyInt) let seqType = n.typ.skipTypes(abstractVar-{tyTypeDesc}) if seqType.kind == tySequence: var tmp = c.getTemp(intType) @@ -1693,13 +1744,13 @@ proc genObjConstr(c: PCtx, n: PNode, dest: var TDest) = for i in 1..<n.len: let it = n.sons[i] if it.kind == nkExprColonExpr and it.sons[0].kind == nkSym: - let idx = genField(it.sons[0]) + let idx = genField(c, it.sons[0]) let tmp = c.genx(it.sons[1]) c.preventFalseAlias(it.sons[1], whichAsgnOpc(it.sons[1], opcWrObj), dest, idx, tmp) c.freeTemp(tmp) else: - globalError(n.info, "invalid object constructor") + globalError(c.config, n.info, "invalid object constructor") proc genTupleConstr(c: PCtx, n: PNode, dest: var TDest) = if dest < 0: dest = c.getTemp(n.typ) @@ -1708,7 +1759,7 @@ proc genTupleConstr(c: PCtx, n: PNode, dest: var TDest) = for i in 0..<n.len: let it = n.sons[i] if it.kind == nkExprColonExpr: - let idx = genField(it.sons[0]) + let idx = genField(c, it.sons[0]) let tmp = c.genx(it.sons[1]) c.preventFalseAlias(it.sons[1], whichAsgnOpc(it.sons[1], opcWrObj), dest, idx, tmp) @@ -1768,6 +1819,9 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = let constVal = if s.ast != nil: s.ast else: s.typ.n gen(c, constVal, dest) of skEnumField: + # we never reach this case - as of the time of this comment, + # skEnumField is folded to an int in semfold.nim, but this code + # remains for robustness if dest < 0: dest = c.getTemp(n.typ) if s.position >= low(int16) and s.position <= high(int16): c.gABx(n, opcLdImmInt, dest, s.position) @@ -1780,15 +1834,17 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = if c.prc.sym != nil and c.prc.sym.kind == skMacro: genRdVar(c, n, dest, flags) else: - globalError(n.info, errGenerated, "cannot generate code for: " & s.name.s) + globalError(c.config, n.info, "cannot generate code for: " & s.name.s) else: - globalError(n.info, errGenerated, "cannot generate code for: " & s.name.s) + globalError(c.config, n.info, "cannot generate code for: " & s.name.s) of nkCallKinds: if n.sons[0].kind == nkSym: let s = n.sons[0].sym if s.magic != mNone: genMagic(c, n, dest, s.magic) elif matches(s, "stdlib", "marshal", "to"): + # XXX marshal load&store should not be opcodes, but use the + # general callback mechanisms. genMarshalLoad(c, n, dest) elif matches(s, "stdlib", "marshal", "$$"): genMarshalStore(c, n, dest) @@ -1806,37 +1862,36 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = genLit(c, n, dest) of nkUIntLit..pred(nkNilLit): genLit(c, n, dest) of nkNilLit: - if not n.typ.isEmptyType: genLit(c, getNullValue(n.typ, n.info), dest) - else: unused(n, dest) + if not n.typ.isEmptyType: genLit(c, getNullValue(n.typ, n.info, c.config), dest) + else: unused(c, n, dest) of nkAsgn, nkFastAsgn: - unused(n, dest) + unused(c, n, dest) genAsgn(c, n.sons[0], n.sons[1], n.kind == nkAsgn) of nkDotExpr: genObjAccess(c, n, dest, flags) of nkCheckedFieldExpr: genCheckedObjAccess(c, n, dest, flags) of nkBracketExpr: genArrAccess(c, n, dest, flags) - of nkDerefExpr, nkHiddenDeref: genAddrDeref(c, n, dest, opcLdDeref, flags) - of nkAddr, nkHiddenAddr: genAddrDeref(c, n, dest, opcAddrNode, flags) + of nkDerefExpr, nkHiddenDeref: genDeref(c, n, dest, flags) + of nkAddr, nkHiddenAddr: genAddr(c, n, dest, flags) of nkIfStmt, nkIfExpr: genIf(c, n, dest) of nkWhenStmt: # This is "when nimvm" node. Chose the first branch. gen(c, n.sons[0].sons[1], dest) of nkCaseStmt: genCase(c, n, dest) of nkWhileStmt: - unused(n, dest) + unused(c, n, dest) genWhile(c, n) of nkBlockExpr, nkBlockStmt: genBlock(c, n, dest) of nkReturnStmt: - unused(n, dest) + unused(c, n, dest) genReturn(c, n) of nkRaiseStmt: - unused(n, dest) genRaise(c, n) of nkBreakStmt: - unused(n, dest) + unused(c, n, dest) genBreak(c, n) of nkTryStmt: genTry(c, n, dest) of nkStmtList: - #unused(n, dest) + #unused(c, n, dest) # XXX Fix this bug properly, lexim triggers it for x in n: gen(c, x) of nkStmtListExpr: @@ -1846,17 +1901,17 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = of nkPragmaBlock: gen(c, n.lastSon, dest, flags) of nkDiscardStmt: - unused(n, dest) + unused(c, n, dest) gen(c, n.sons[0]) of nkHiddenStdConv, nkHiddenSubConv, nkConv: genConv(c, n, n.sons[1], dest) of nkObjDownConv: genConv(c, n, n.sons[0], dest) of nkVarSection, nkLetSection: - unused(n, dest) + unused(c, n, dest) genVarSection(c, n) of declarativeDefs, nkMacroDef: - unused(n, dest) + unused(c, n, dest) of nkLambdaKinds: #let s = n.sons[namePos].sym #discard genProc(c, s) @@ -1875,14 +1930,14 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = else: dest = tmp0 of nkEmpty, nkCommentStmt, nkTypeSection, nkConstSection, nkPragma, - nkTemplateDef, nkIncludeStmt, nkImportStmt, nkFromStmt: - unused(n, dest) + nkTemplateDef, nkIncludeStmt, nkImportStmt, nkFromStmt, nkExportStmt: + unused(c, n, dest) of nkStringToCString, nkCStringToString: gen(c, n.sons[0], dest) of nkBracket: genArrayConstr(c, n, dest) of nkCurly: genSetConstr(c, n, dest) of nkObjConstr: genObjConstr(c, n, dest) - of nkPar, nkClosure: genTupleConstr(c, n, dest) + of nkPar, nkClosure, nkTupleConstr: genTupleConstr(c, n, dest) of nkCast: if allowCast in c.features: genConv(c, n, n.sons[1], dest, opcCast) @@ -1893,7 +1948,7 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = of nkComesFrom: discard "XXX to implement for better stack traces" else: - globalError(n.info, errGenerated, "cannot generate VM code for " & $n) + globalError(c.config, n.info, "cannot generate VM code for " & $n) proc removeLastEof(c: PCtx) = let last = c.code.len-1 @@ -1910,7 +1965,7 @@ proc genStmt*(c: PCtx; n: PNode): int = c.gen(n, d) c.gABC(n, opcEof) if d >= 0: - globalError(n.info, errGenerated, "VM problem: dest register is set") + globalError(c.config, n.info, "VM problem: dest register is set") proc genExpr*(c: PCtx; n: PNode, requiresValue = true): int = c.removeLastEof @@ -1919,7 +1974,7 @@ proc genExpr*(c: PCtx; n: PNode, requiresValue = true): int = c.gen(n, d) if d < 0: if requiresValue: - globalError(n.info, errGenerated, "VM problem: dest register is not set") + globalError(c.config, n.info, "VM problem: dest register is not set") d = 0 c.gABC(n, opcEof, d) @@ -1934,7 +1989,7 @@ proc genParams(c: PCtx; params: PNode) = c.prc.maxSlots = max(params.len, 1) proc finalJumpTarget(c: PCtx; pc, diff: int) = - internalAssert(-0x7fff < diff and diff < 0x7fff) + internalAssert(c.config, -0x7fff < diff and diff < 0x7fff) let oldInstr = c.code[pc] # opcode and regA stay the same: c.code[pc] = ((oldInstr.uint32 and 0xffff'u32).uint32 or @@ -2003,7 +2058,7 @@ proc genProc(c: PCtx; s: PSym): int = #c.removeLastEof result = c.code.len+1 # skip the jump instruction if x.kind == nkEmpty: - x = newTree(nkBracket, newIntNode(nkIntLit, result), ast.emptyNode) + x = newTree(nkBracket, newIntNode(nkIntLit, result), x) else: x.sons[0] = newIntNode(nkIntLit, result) s.ast.sons[miscPos] = x diff --git a/compiler/vmmarshal.nim b/compiler/vmmarshal.nim index 5f725994e..149d2e08f 100644 --- a/compiler/vmmarshal.nim +++ b/compiler/vmmarshal.nim @@ -9,7 +9,8 @@ ## Implements marshaling for the VM. -import streams, json, intsets, tables, ast, astalgo, idents, types, msgs +import streams, json, intsets, tables, ast, astalgo, idents, types, msgs, + options, lineinfos proc ptrToInt(x: PNode): int {.inline.} = result = cast[int](x) # don't skip alignment @@ -28,37 +29,38 @@ proc getField(n: PNode; position: int): PSym = of nkOfBranch, nkElse: result = getField(lastSon(n.sons[i]), position) if result != nil: return - else: internalError(n.info, "getField(record case branch)") + else: discard of nkSym: if n.sym.position == position: result = n.sym else: discard -proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet) +proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet; conf: ConfigRef) -proc storeObj(s: var string; typ: PType; x: PNode; stored: var IntSet) = - internalAssert x.kind == nkObjConstr +proc storeObj(s: var string; typ: PType; x: PNode; stored: var IntSet; conf: ConfigRef) = + assert x.kind == nkObjConstr let start = 1 for i in countup(start, sonsLen(x) - 1): if i > start: s.add(", ") var it = x.sons[i] if it.kind == nkExprColonExpr: - internalAssert it.sons[0].kind == nkSym - let field = it.sons[0].sym - s.add(escapeJson(field.name.s)) - s.add(": ") - storeAny(s, field.typ, it.sons[1], stored) + if it.sons[0].kind == nkSym: + let field = it.sons[0].sym + s.add(escapeJson(field.name.s)) + s.add(": ") + storeAny(s, field.typ, it.sons[1], stored, conf) elif typ.n != nil: let field = getField(typ.n, i) s.add(escapeJson(field.name.s)) s.add(": ") - storeAny(s, field.typ, it, stored) + storeAny(s, field.typ, it, stored, conf) proc skipColon*(n: PNode): PNode = result = n if n.kind == nkExprColonExpr: result = n.sons[1] -proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet) = +proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet; + conf: ConfigRef) = case t.kind of tyNone: assert false of tyBool: s.add($(a.intVal != 0)) @@ -74,7 +76,7 @@ proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet) = s.add("[") for i in 0 .. a.len-1: if i > 0: s.add(", ") - storeAny(s, t.elemType, a[i], stored) + storeAny(s, t.elemType, a[i], stored, conf) s.add("]") of tyTuple: s.add("{") @@ -82,11 +84,11 @@ proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet) = if i > 0: s.add(", ") s.add("\"Field" & $i) s.add("\": ") - storeAny(s, t.sons[i], a[i].skipColon, stored) + storeAny(s, t.sons[i], a[i].skipColon, stored, conf) s.add("}") of tyObject: s.add("{") - storeObj(s, t, a, stored) + storeObj(s, t, a, stored, conf) s.add("}") of tySet: s.add("[") @@ -94,15 +96,16 @@ proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet) = if i > 0: s.add(", ") if a[i].kind == nkRange: var x = copyNode(a[i][0]) - storeAny(s, t.lastSon, x, stored) + storeAny(s, t.lastSon, x, stored, conf) while x.intVal+1 <= a[i][1].intVal: s.add(", ") - storeAny(s, t.lastSon, x, stored) + storeAny(s, t.lastSon, x, stored, conf) inc x.intVal else: - storeAny(s, t.lastSon, a[i], stored) + storeAny(s, t.lastSon, a[i], stored, conf) s.add("]") - of tyRange, tyGenericInst, tyAlias, tySink: storeAny(s, t.lastSon, a, stored) + of tyRange, tyGenericInst, tyAlias, tySink: + storeAny(s, t.lastSon, a, stored, conf) of tyEnum: # we need a slow linear search because of enums with holes: for e in items(t.n): @@ -121,22 +124,24 @@ proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet) = s.add("[") s.add($x.ptrToInt) s.add(", ") - storeAny(s, t.lastSon, a, stored) + storeAny(s, t.lastSon, a, stored, conf) s.add("]") of tyString, tyCString: - if a.kind == nkNilLit or a.strVal.isNil: s.add("null") + if a.kind == nkNilLit: s.add("null") else: s.add(escapeJson(a.strVal)) of tyInt..tyInt64, tyUInt..tyUInt64: s.add($a.intVal) of tyFloat..tyFloat128: s.add($a.floatVal) else: - internalError a.info, "cannot marshal at compile-time " & t.typeToString + internalError conf, a.info, "cannot marshal at compile-time " & t.typeToString -proc storeAny*(s: var string; t: PType; a: PNode) = +proc storeAny*(s: var string; t: PType; a: PNode; conf: ConfigRef) = var stored = initIntSet() - storeAny(s, t, a, stored) + storeAny(s, t, a, stored, conf) proc loadAny(p: var JsonParser, t: PType, - tab: var Table[BiggestInt, PNode]): PNode = + tab: var Table[BiggestInt, PNode]; + cache: IdentCache; + conf: ConfigRef): PNode = case t.kind of tyNone: assert false of tyBool: @@ -170,7 +175,7 @@ proc loadAny(p: var JsonParser, t: PType, next(p) result = newNode(nkBracket) while p.kind != jsonArrayEnd and p.kind != jsonEof: - result.add loadAny(p, t.elemType, tab) + result.add loadAny(p, t.elemType, tab, cache, conf) if p.kind == jsonArrayEnd: next(p) else: raiseParseErr(p, "']' end of array expected") of tySequence: @@ -182,7 +187,7 @@ proc loadAny(p: var JsonParser, t: PType, next(p) result = newNode(nkBracket) while p.kind != jsonArrayEnd and p.kind != jsonEof: - result.add loadAny(p, t.elemType, tab) + result.add loadAny(p, t.elemType, tab, cache, conf) if p.kind == jsonArrayEnd: next(p) else: raiseParseErr(p, "") else: @@ -190,7 +195,7 @@ proc loadAny(p: var JsonParser, t: PType, of tyTuple: if p.kind != jsonObjectStart: raiseParseErr(p, "'{' expected for an object") next(p) - result = newNode(nkPar) + result = newNode(nkTupleConstr) var i = 0 while p.kind != jsonObjectEnd and p.kind != jsonEof: if p.kind != jsonString: @@ -198,7 +203,7 @@ proc loadAny(p: var JsonParser, t: PType, next(p) if i >= t.len: raiseParseErr(p, "too many fields to tuple type " & typeToString(t)) - result.add loadAny(p, t.sons[i], tab) + result.add loadAny(p, t.sons[i], tab, cache, conf) inc i if p.kind == jsonObjectEnd: next(p) else: raiseParseErr(p, "'}' end of object expected") @@ -210,7 +215,7 @@ proc loadAny(p: var JsonParser, t: PType, while p.kind != jsonObjectEnd and p.kind != jsonEof: if p.kind != jsonString: raiseParseErr(p, "string expected for a field name") - let ident = getIdent(p.str) + let ident = getIdent(cache, p.str) let field = lookupInRecord(t.n, ident) if field.isNil: raiseParseErr(p, "unknown field for object of type " & typeToString(t)) @@ -220,7 +225,7 @@ proc loadAny(p: var JsonParser, t: PType, setLen(result.sons, pos + 1) let fieldNode = newNode(nkExprColonExpr) fieldNode.addSon(newSymNode(newSym(skField, ident, nil, unknownLineInfo()))) - fieldNode.addSon(loadAny(p, field.typ, tab)) + fieldNode.addSon(loadAny(p, field.typ, tab, cache, conf)) result.sons[pos] = fieldNode if p.kind == jsonObjectEnd: next(p) else: raiseParseErr(p, "'}' end of object expected") @@ -229,7 +234,7 @@ proc loadAny(p: var JsonParser, t: PType, next(p) result = newNode(nkCurly) while p.kind != jsonArrayEnd and p.kind != jsonEof: - result.add loadAny(p, t.lastSon, tab) + result.add loadAny(p, t.lastSon, tab, cache, conf) next(p) if p.kind == jsonArrayEnd: next(p) else: raiseParseErr(p, "']' end of array expected") @@ -248,7 +253,7 @@ proc loadAny(p: var JsonParser, t: PType, if p.kind == jsonInt: let idx = p.getInt next(p) - result = loadAny(p, t.lastSon, tab) + result = loadAny(p, t.lastSon, tab, cache, conf) tab[idx] = result else: raiseParseErr(p, "index for ref type expected") if p.kind == jsonArrayEnd: next(p) @@ -275,14 +280,15 @@ proc loadAny(p: var JsonParser, t: PType, next(p) return raiseParseErr(p, "float expected") - of tyRange, tyGenericInst, tyAlias, tySink: result = loadAny(p, t.lastSon, tab) + of tyRange, tyGenericInst, tyAlias, tySink: + result = loadAny(p, t.lastSon, tab, cache, conf) else: - internalError "cannot marshal at compile-time " & t.typeToString + internalError conf, "cannot marshal at compile-time " & t.typeToString -proc loadAny*(s: string; t: PType): PNode = +proc loadAny*(s: string; t: PType; cache: IdentCache; conf: ConfigRef): PNode = var tab = initTable[BiggestInt, PNode]() var p: JsonParser open(p, newStringStream(s), "unknown file") next(p) - result = loadAny(p, t, tab) + result = loadAny(p, t, tab, cache, conf) close(p) diff --git a/compiler/vmops.nim b/compiler/vmops.nim index 2a00f207a..a7d47d7a3 100644 --- a/compiler/vmops.nim +++ b/compiler/vmops.nim @@ -13,7 +13,7 @@ from math import sqrt, ln, log10, log2, exp, round, arccos, arcsin, arctan, arctan2, cos, cosh, hypot, sinh, sin, tan, tanh, pow, trunc, floor, ceil, fmod -from os import getEnv, existsEnv, dirExists, fileExists, walkDir +from os import getEnv, existsEnv, dirExists, fileExists, putEnv, walkDir template mathop(op) {.dirty.} = registerCallback(c, "stdlib.math." & astToStr(op), `op Wrapper`) @@ -27,6 +27,9 @@ template ospathsop(op) {.dirty.} = template systemop(op) {.dirty.} = registerCallback(c, "stdlib.system." & astToStr(op), `op Wrapper`) +template macrosop(op) {.dirty.} = + registerCallback(c, "stdlib.macros." & astToStr(op), `op Wrapper`) + template wrap1f_math(op) {.dirty.} = proc `op Wrapper`(a: VmArgs) {.nimcall.} = setResult(a, op(getFloat(a, 0))) @@ -37,30 +40,30 @@ template wrap2f_math(op) {.dirty.} = setResult(a, op(getFloat(a, 0), getFloat(a, 1))) mathop op -template wrap1s_os(op) {.dirty.} = +template wrap0(op, modop) {.dirty.} = proc `op Wrapper`(a: VmArgs) {.nimcall.} = - setResult(a, op(getString(a, 0))) - osop op + setResult(a, op()) + modop op -template wrap1s_ospaths(op) {.dirty.} = +template wrap1s(op, modop) {.dirty.} = proc `op Wrapper`(a: VmArgs) {.nimcall.} = setResult(a, op(getString(a, 0))) - ospathsop op + modop op -template wrap2s_ospaths(op) {.dirty.} = +template wrap2s(op, modop) {.dirty.} = proc `op Wrapper`(a: VmArgs) {.nimcall.} = setResult(a, op(getString(a, 0), getString(a, 1))) - ospathsop op + modop op -template wrap1s_system(op) {.dirty.} = +template wrap1svoid(op, modop) {.dirty.} = proc `op Wrapper`(a: VmArgs) {.nimcall.} = - setResult(a, op(getString(a, 0))) - systemop op + op(getString(a, 0)) + modop op -template wrap2svoid_system(op) {.dirty.} = +template wrap2svoid(op, modop) {.dirty.} = proc `op Wrapper`(a: VmArgs) {.nimcall.} = op(getString(a, 0), getString(a, 1)) - systemop op + modop op proc getCurrentExceptionMsgWrapper(a: VmArgs) {.nimcall.} = setResult(a, if a.currentException.isNil: "" @@ -69,15 +72,18 @@ proc getCurrentExceptionMsgWrapper(a: VmArgs) {.nimcall.} = proc staticWalkDirImpl(path: string, relative: bool): PNode = result = newNode(nkBracket) for k, f in walkDir(path, relative): - result.add newTree(nkPar, newIntNode(nkIntLit, k.ord), + result.add newTree(nkTupleConstr, newIntNode(nkIntLit, k.ord), newStrNode(nkStrLit, f)) -proc gorgeExWrapper(a: VmArgs) {.nimcall.} = - let (s, e) = opGorge(getString(a, 0), getString(a, 1), getString(a, 2), - a.currentLineInfo) - setResult a, newTree(nkPar, newStrNode(nkStrLit, s), newIntNode(nkIntLit, e)) - proc registerAdditionalOps*(c: PCtx) = + proc gorgeExWrapper(a: VmArgs) = + let (s, e) = opGorge(getString(a, 0), getString(a, 1), getString(a, 2), + a.currentLineInfo, c.config) + setResult a, newTree(nkTupleConstr, newStrNode(nkStrLit, s), newIntNode(nkIntLit, e)) + + proc getProjectPathWrapper(a: VmArgs) = + setResult a, c.config.projectPath + wrap1f_math(sqrt) wrap1f_math(ln) wrap1f_math(log10) @@ -101,13 +107,16 @@ proc registerAdditionalOps*(c: PCtx) = wrap1f_math(ceil) wrap2f_math(fmod) - wrap2s_ospaths(getEnv) - wrap1s_ospaths(existsEnv) - wrap1s_os(dirExists) - wrap1s_os(fileExists) - wrap2svoid_system(writeFile) - wrap1s_system(readFile) - systemop getCurrentExceptionMsg - registerCallback c, "stdlib.*.staticWalkDir", proc (a: VmArgs) {.nimcall.} = - setResult(a, staticWalkDirImpl(getString(a, 0), getBool(a, 1))) - systemop gorgeEx + when defined(nimcore): + wrap2s(getEnv, ospathsop) + wrap1s(existsEnv, ospathsop) + wrap2svoid(putEnv, ospathsop) + wrap1s(dirExists, osop) + wrap1s(fileExists, osop) + wrap2svoid(writeFile, systemop) + wrap1s(readFile, systemop) + systemop getCurrentExceptionMsg + registerCallback c, "stdlib.*.staticWalkDir", proc (a: VmArgs) {.nimcall.} = + setResult(a, staticWalkDirImpl(getString(a, 0), getBool(a, 1))) + systemop gorgeEx + macrosop getProjectPath diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim index e458cad03..91b527e02 100644 --- a/compiler/wordrecg.nim +++ b/compiler/wordrecg.nim @@ -52,10 +52,11 @@ type wNimcall, wStdcall, wCdecl, wSafecall, wSyscall, wInline, wNoInline, wFastcall, wClosure, wNoconv, wOn, wOff, wChecks, wRangechecks, wBoundchecks, wOverflowchecks, wNilchecks, - wFloatchecks, wNanChecks, wInfChecks, + wFloatchecks, wNanChecks, wInfChecks, wMoveChecks, wAssertions, wPatterns, wWarnings, wHints, wOptimization, wRaises, wWrites, wReads, wSize, wEffects, wTags, - wDeadCodeElim, wSafecode, wPackage, wNoForward, wReorder, wNoRewrite, + wDeadCodeElimUnused, # deprecated, dead code elim always happens + wSafecode, wPackage, wNoForward, wReorder, wNoRewrite, wPragma, wCompileTime, wNoInit, wPassc, wPassl, wBorrow, wDiscardable, @@ -139,11 +140,12 @@ const "cdecl", "safecall", "syscall", "inline", "noinline", "fastcall", "closure", "noconv", "on", "off", "checks", "rangechecks", "boundchecks", "overflowchecks", "nilchecks", - "floatchecks", "nanchecks", "infchecks", + "floatchecks", "nanchecks", "infchecks", "movechecks", "assertions", "patterns", "warnings", "hints", "optimization", "raises", "writes", "reads", "size", "effects", "tags", - "deadcodeelim", "safecode", "package", "noforward", "reorder", "norewrite", + "deadcodeelim", # deprecated, dead code elim always happens + "safecode", "package", "noforward", "reorder", "norewrite", "pragma", "compiletime", "noinit", "passc", "passl", "borrow", "discardable", "fieldchecks", diff --git a/compiler/writetracking.nim b/compiler/writetracking.nim index 577db613d..1ea1deb2d 100644 --- a/compiler/writetracking.nim +++ b/compiler/writetracking.nim @@ -15,7 +15,8 @@ ## * Computing an aliasing relation based on the assignments. This relation ## is then used to compute the 'writes' and 'escapes' effects. -import intsets, idents, ast, astalgo, trees, renderer, msgs, types +import intsets, idents, ast, astalgo, trees, renderer, msgs, types, options, + lineinfos const debug = false @@ -120,7 +121,7 @@ proc returnsNewExpr*(n: PNode): NewLocation = nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkOfBranch, nkElifBranch, nkElse, nkExceptBranch, nkFinally, nkCast: result = returnsNewExpr(n.lastSon) - of nkCurly, nkBracket, nkPar, nkObjConstr, nkClosure, + of nkCurly, nkBracket, nkPar, nkTupleConstr, nkObjConstr, nkClosure, nkIfExpr, nkIfStmt, nkWhenStmt, nkCaseStmt, nkTryStmt: result = newLit for i in ord(n.kind == nkObjConstr) ..< n.len: @@ -179,8 +180,8 @@ proc deps(w: var W; n: PNode) = for child in n: let last = lastSon(child) if last.kind == nkEmpty: continue - if child.kind == nkVarTuple and last.kind == nkPar: - internalAssert child.len-2 == last.len + if child.kind == nkVarTuple and last.kind in {nkPar, nkTupleConstr}: + if child.len-2 != last.len: return for i in 0 .. child.len-3: deps(w, child.sons[i], last.sons[i], {}) else: @@ -220,7 +221,7 @@ proc possibleAliases(w: var W; result: var seq[ptr TSym]) = # x = f(..., y, ....) for i in 0 ..< a.srcNoTc: addNoDup a.src[i] -proc markWriteOrEscape(w: var W) = +proc markWriteOrEscape(w: var W; conf: ConfigRef) = ## Both 'writes' and 'escapes' effects ultimately only care ## about *parameters*. ## However, due to aliasing, even locals that might not look as parameters @@ -249,7 +250,7 @@ proc markWriteOrEscape(w: var W) = if p.kind == skParam and p.owner == w.owner: incl(p.flags, sfWrittenTo) if w.owner.kind == skFunc and p.typ.kind != tyVar: - localError(a.info, "write access to non-var parameter: " & p.name.s) + localError(conf, a.info, "write access to non-var parameter: " & p.name.s) if {rootIsResultOrParam, rootIsHeapAccess, markAsEscaping}*a.destInfo != {}: var destIsParam = false @@ -263,14 +264,14 @@ proc markWriteOrEscape(w: var W) = if p.kind == skParam and p.owner == w.owner: incl(p.flags, sfEscapes) -proc trackWrites*(owner: PSym; body: PNode) = +proc trackWrites*(owner: PSym; body: PNode; conf: ConfigRef) = var w: W w.owner = owner w.assignments = @[] # Phase 1: Collect and preprocess any assignments in the proc body: deps(w, body) # Phase 2: Compute the 'writes' and 'escapes' effects: - markWriteOrEscape(w) + markWriteOrEscape(w, conf) if w.returnsNew != asgnOther and not isEmptyType(owner.typ.sons[0]) and containsGarbageCollectedRef(owner.typ.sons[0]): incl(owner.typ.flags, tfReturnsNew) diff --git a/config/nim.cfg b/config/nim.cfg index a2a774b23..9626a3197 100644 --- a/config/nim.cfg +++ b/config/nim.cfg @@ -82,19 +82,21 @@ path="$lib/pure" clang.cpp.options.linker = "-ldl" tcc.options.linker = "-ldl" @end - @if bsd or haiku: + @if bsd: # BSD got posix_spawn only recently, so we deactivate it for osproc: define:useFork # at least NetBSD has problems with thread local storage: tlsEmulation:on @end @if haiku: - # -fopenmp - gcc.options.linker = "-lroot -lnetwork" - gcc.cpp.options.linker = "-lroot -lnetwork" - clang.options.linker = "-lroot -lnetwork" - clang.cpp.options.linker = "-lroot -lnetwork" - tcc.options.linker = "-lroot -lnetwork" + # Haiku currently have problems with TLS + # https://dev.haiku-os.org/ticket/14342 + tlsEmulation:on + gcc.options.linker = "-Wl,--as-needed -lnetwork" + gcc.cpp.options.linker = "-Wl,--as-needed -lnetwork" + clang.options.linker = "-Wl,--as-needed -lnetwork" + clang.cpp.options.linker = "-Wl,--as-needed -lnetwork" + tcc.options.linker = "-Wl,--as-needed -lnetwork" @end @end @@ -109,6 +111,14 @@ path="$lib/pure" @end @end +@if nintendoswitch: + cc = "switch_gcc" + switch_gcc.options.linker = "-g -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE" + switch_gcc.cpp.options.linker = "-g -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE" + switch_gcc.options.always = "-g -Wall -O2 -ffunction-sections -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE -D__SWITCH__" + switch_gcc.cpp.options.always = "-g -Wall -O2 -ffunction-sections -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE -D__SWITCH__ -fno-rtti -fno-exceptions -std=gnu++11" +@end + # Configuration for the Intel C/C++ compiler: @if windows: icl.options.speed = "/Ox /arch:SSE2" @@ -118,7 +128,7 @@ path="$lib/pure" # Configuration for the GNU C/C++ compiler: @if windows: #gcc.path = r"$nim\dist\mingw\bin" - @if gcc: + @if gcc or tcc: tlsEmulation:on @end @end @@ -243,3 +253,16 @@ vcc.cpp.options.size = "/O1" # Configuration for the Tiny C Compiler: tcc.options.always = "-w" + +# Configuration for the Genode toolchain +@if genode: + gcc.path = "/usr/local/genode-gcc/bin" + gcc.cpp.options.always = "-D__GENODE__ -fno-stack-protector" + @if i386 or amd64: + gcc.exe = "genode-x86-gcc" + gcc.cpp.exe = "genode-x86-g++" + @elif arm: + gcc.exe = "genode-arm-gcc" + gcc.cpp.exe = "genode-arm-g++" + @end +@end diff --git a/config/nimdoc.cfg b/config/nimdoc.cfg index 2800bc581..96e91283a 100644 --- a/config/nimdoc.cfg +++ b/config/nimdoc.cfg @@ -55,6 +55,8 @@ doc.item.toc = """ # HTML rendered for doc.item's seeSrc variable. Note that this will render to # the empty string if you don't pass anything through --docSeeSrcURL. Available # substitutaion variables here are: +# * $commit: branch/commit to use in source link. +# * $devel: branch to use in edit link. # * $path: relative path to the file being processed. # * $line: line of the item in the original source file. # * $url: whatever you did pass through the --docSeeSrcUrl switch (which also @@ -62,7 +64,7 @@ doc.item.toc = """ doc.item.seesrc = """ <a href="${url}/tree/${commit}/${path}#L${line}" class="link-seesrc" target="_blank">Source</a> -<a href="${url}/edit/devel/${path}#L${line}" class="link-seesrc" target="_blank" >Edit</a> +<a href="${url}/edit/${devel}/${path}#L${line}" class="link-seesrc" target="_blank" >Edit</a> """ doc.toc = """ @@ -109,7 +111,7 @@ doc.body_toc_group = """ </li> </ul> </div> - <div id="searchInput"> + <div id="searchInputDiv"> Search: <input type="text" id="searchInput" onkeyup="search()" /> </div> @@ -139,7 +141,7 @@ doc.body_toc_group = """ <ul class="simple"> </ul> </div> - <div id="searchInput"> + <div id="searchInputDiv"> Search: <input type="text" id="searchInput" onkeyup="search()" /> </div> @@ -229,7 +231,7 @@ body { line-height: 20px; color: #444; letter-spacing: 0.15px; - background-color: rgba(252, 248, 244, 0.45); } + background-color: #FDFBFA; } /* Skeleton grid */ .container { @@ -364,10 +366,10 @@ dd > pre { table-layout: fixed; } /* Nim search input */ -div#searchInput { +div#searchInputDiv { margin-bottom: 8px; } -div#searchInput input#searchInput { +div#searchInputDiv input#searchInput { width: 10em; } div.search-groupby { @@ -1315,7 +1317,7 @@ dt pre > span.Identifier ~ span.Identifier, dt pre > span.Operator ~ span.Identi color: inherit; font-weight: inherit; } -dt pre > span.Operator ~ span.Identifier, dt pre > span.Operator ~ span.Operator { +dt pre > span.Operator ~ span.Identifier { color: inherit; font-weight: inherit; } @@ -1330,15 +1332,6 @@ dt pre > span.Operator ~ span.Identifier, dt pre > span.Operator ~ span.Operator background-repeat: no-repeat; background-image: url("data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAUAAAAF////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAIAAABbAAAAlQAAAKIAAACbAAAAmwAAAKIAAACVAAAAWwAAAAL///8A////AP///wD///8A////AAAAABQAAADAAAAAYwAAAA3///8A////AP///wD///8AAAAADQAAAGMAAADAAAAAFP///wD///8A////AP///wAAAACdAAAAOv///wD///8A////AP///wD///8A////AP///wD///8AAAAAOgAAAJ3///8A////AP///wAAAAAnAAAAcP///wAAAAAoAAAASv///wD///8A////AP///wAAAABKAAAAKP///wAAAABwAAAAJ////wD///8AAAAAgQAAABwAAACIAAAAkAAAAJMAAACtAAAAFQAAABUAAACtAAAAkwAAAJAAAACIAAAAHAAAAIH///8A////AAAAAKQAAACrAAAAaP///wD///8AAAAARQAAANIAAADSAAAARf///wD///8AAAAAaAAAAKsAAACk////AAAAADMAAACcAAAAnQAAABj///8A////AP///wAAAAAYAAAAGP///wD///8A////AAAAABgAAACdAAAAnAAAADMAAAB1AAAAwwAAAP8AAADpAAAAsQAAAE4AAAAb////AP///wAAAAAbAAAATgAAALEAAADpAAAA/wAAAMMAAAB1AAAAtwAAAOkAAAD/AAAA/wAAAP8AAADvAAAA3gAAAN4AAADeAAAA3gAAAO8AAAD/AAAA/wAAAP8AAADpAAAAtwAAAGUAAAA/AAAA3wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAADfAAAAPwAAAGX///8A////AAAAAEgAAADtAAAAvwAAAL0AAADGAAAA7wAAAO8AAADGAAAAvQAAAL8AAADtAAAASP///wD///8A////AP///wD///8AAAAAO////wD///8A////AAAAAIcAAACH////AP///wD///8AAAAAO////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A//8AAP//AAD4HwAA7/cAAN/7AAD//wAAoYUAAJ55AACf+QAAh+EAAAAAAADAAwAA4AcAAP5/AAD//wAA//8AAA=="); margin-bottom: -5px; } -div.pragma { - display: none; -} -span.pragmabegin { - cursor: pointer; -} -span.pragmaend { - cursor: pointer; -} div.search_results { background-color: antiquewhite; @@ -1351,32 +1344,47 @@ div#global-links ul { margin-left: 0; list-style-type: none; } + +span.pragmadots { + /* Position: relative frees us up to make the dots + look really nice without fucking up the layout and + causing bulging in the parent container */ + position: relative; + /* 1px down looks slightly nicer */ + top: 1px; + + padding: 2px; + background-color: #D3D3D3; + border-radius: 4px; + margin: 0 2px; + cursor: pointer; + + /* For some reason on Chrome, making the font size + smaller than 1em is causing the parent container to + bulge slightly. So, we're stuck with inheriting 1em, + which is sad, because 0.8em looks better... */ +} +span.pragmadots:hover { + background-color: #DBDBDB; +} +span.pragmawrap { + display: none; +} + </style> <script type="text/javascript" src="../dochack.js"></script> <script type="text/javascript"> -function togglepragma(d) { - if (d.style.display != 'inline') - d.style.display = 'inline'; - else - d.style.display = 'none'; -} - function main() { - var elements = document.getElementsByClassName("pragmabegin"); - for (var i = 0; i < elements.length; ++i) { - var e = elements[i]; - e.onclick = function(event) { - togglepragma(event.target.nextSibling); - }; - } - var elements = document.getElementsByClassName("pragmaend"); - for (var i = 0; i < elements.length; ++i) { - var e = elements[i]; - e.onclick = function(event) { - togglepragma(event.target.previousSibling); - }; + var pragmaDots = document.getElementsByClassName("pragmadots"); + for (var i = 0; i < pragmaDots.length; i++) { + pragmaDots[i].onclick = function(event) { + // Hide tease + event.target.parentNode.style.display = "none"; + // Show actual + event.target.parentNode.nextElementSibling.style.display = "inline"; + } } } </script> diff --git a/copying.txt b/copying.txt index 98b3e568f..d2bf9a7a7 100644 --- a/copying.txt +++ b/copying.txt @@ -1,7 +1,7 @@ ===================================================== Nim -- a Compiler for Nim. https://nim-lang.org/ -Copyright (C) 2006-2017 Andreas Rumpf. All rights reserved. +Copyright (C) 2006-2018 Andreas Rumpf. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/doc/advopt.txt b/doc/advopt.txt index c8ff0845a..150025509 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -7,7 +7,6 @@ Advanced commands: //rst2html convert a reStructuredText file to HTML //rst2tex convert a reStructuredText file to TeX //jsondoc extract the documentation to a json file - //jsondoc2 extract the documentation to a json file (uses doc2) //ctags create a tags file //buildIndex build an index for the whole documentation //run run the project (with Tiny C backend; buggy!) @@ -32,11 +31,12 @@ Advanced options: --nimcache:PATH set the path used for generated files --header:FILE the compiler should produce a .h file (FILE is optional) - -c, --compileOnly compile only; do not assemble or link - --noLinking compile but do not link + -c, --compileOnly compile Nim files only; do not assemble or link + --noLinking compile Nim and generated files but do not link --noMain do not generate a main procedure --genScript generate a compile script (in the 'nimcache' - subdirectory named 'compile_$project$scriptext') + subdirectory named 'compile_$$project$$scriptext'), + implies --compileOnly --genDeps generate a '.deps' file containing the dependencies --os:SYMBOL set the target operating system (cross-compilation) --cpu:SYMBOL set the target processor (cross-compilation) @@ -61,9 +61,15 @@ Advanced options: --implicitStatic:on|off turn implicit compile time evaluation on|off --patterns:on|off turn pattern matching on|off --memTracker:on|off turn memory tracker on|off + --hotCodeReloading:on|off + turn support for hot code reloading on|off --excessiveStackTrace:on|off stack traces use full file paths --oldNewlines:on|off turn on|off the old behaviour of "\n" + --laxStrings:on|off when turned on, accessing the zero terminator in + strings is allowed; only for backwards compatibility + --nilseqs:on|off allow 'nil' for strings/seqs for + backwards compatibility --skipCfg do not read the general configuration file --skipUserCfg do not read the user's configuration file --skipParentCfg do not read the parent dirs' configuration files @@ -75,6 +81,7 @@ Advanced options: --NimblePath:PATH add a path for Nimble support --noNimblePath deactivate the Nimble path --noCppExceptions use default exception handling with C++ backend + --cppCompileToNamespace use namespace "Nim" for the generated C++ code --excludePath:PATH exclude a path from the list of search paths --dynlibOverride:SYMBOL marks SYMBOL so that dynlib:SYMBOL has no effect and can be statically linked instead; @@ -86,5 +93,6 @@ Advanced options: --parallelBuild:0|1|... perform a parallel build value = number of processors (0 for auto-detect) --verbosity:0|1|2|3 set Nim's verbosity level (1 is default) - --experimental enable experimental language features + --experimental:$1 + enable experimental language feature -v, --version show detailed version information diff --git a/doc/astspec.txt b/doc/astspec.txt index 6d755c2e2..830dc7da9 100644 --- a/doc/astspec.txt +++ b/doc/astspec.txt @@ -28,12 +28,8 @@ contains: intVal: BiggestInt ## the int literal of nnkFloatLit..nnkFloat64Lit: floatVal: BiggestFloat ## the float literal - of nnkStrLit..nnkTripleStrLit: + of nnkStrLit..nnkTripleStrLit, nnkCommentStmt, nnkIdent, nnkSym: strVal: string ## the string literal - of nnkIdent: - ident: NimIdent ## the identifier - of nnkSym: - symbol: NimSym ## the symbol (after symbol lookup phase) else: sons: seq[NimNode] ## the node's sons (or children) @@ -74,8 +70,8 @@ Nim expression Corresponding AST ``"""abc"""`` ``nnkTripleStrLit(strVal = "abc")`` ``' '`` ``nnkCharLit(intVal = 32)`` ``nil`` ``nnkNilLit()`` -``myIdentifier`` ``nnkIdent(ident = !"myIdentifier")`` -``myIdentifier`` after lookup pass: ``nnkSym(symbol = ...)`` +``myIdentifier`` ``nnkIdent(strVal = "myIdentifier")`` +``myIdentifier`` after lookup pass: ``nnkSym(strVal = "myIdentifier", ...)`` ----------------- --------------------------------------------- Identifiers are ``nnkIdent`` nodes. After the name lookup pass these nodes @@ -97,7 +93,7 @@ AST: .. code-block:: nim nnkCommand( - nnkIdent(!"echo"), + nnkIdent("echo"), nnkStrLit("abc"), nnkStrLit("xyz") ) @@ -115,7 +111,7 @@ AST: .. code-block:: nim nnkCall( - nnkIdent(!"echo"), + nnkIdent("echo"), nnkStrLit("abc"), nnkStrLit("xyz") ) @@ -133,7 +129,7 @@ AST: .. code-block:: nim nnkInfix( - nnkIdent(!"&"), + nnkIdent("&"), nnkStrLit("abc"), nnkStrLit("xyz") ) @@ -150,10 +146,10 @@ AST: .. code-block:: nim nnkInfix( - nnkIdent(!"+"), + nnkIdent("+"), nnkIntLit(5), nnkInfix( - nnkIdent(!"*"), + nnkIdent("*"), nnkIntLit(3), nnkIntLit(4) ) @@ -174,7 +170,7 @@ AST: .. code-block:: nim nnkCall( nnkAccQuoted( - nnkIdent(!"+") + nnkIdent("+") ), nnkIntLit(3), nnkIntLit(4) @@ -192,7 +188,7 @@ AST: .. code-block:: nim nnkPrefix( - nnkIdent(!"?"), + nnkIdent("?"), nnkStrLit("abc") ) @@ -212,8 +208,8 @@ AST: .. code-block:: nim nnkPostfix( - nnkIdent(!"*"), - nnkIdent(!"identifier") + nnkIdent("*"), + nnkIdent("identifier") ) @@ -229,10 +225,10 @@ AST: .. code-block:: nim nnkCall( - nnkIdent(!"writeLine"), + nnkIdent("writeLine"), nnkExprEqExpr( - nnkIdent(!"file"), - nnkIdent(!"stdout") + nnkIdent("file"), + nnkIdent("stdout") ), nnkStrLit("hallo") ) @@ -253,7 +249,7 @@ AST: .. code-block:: nim nnkCallStrLit( - nnkIdent(!"echo"), + nnkIdent("echo"), nnkRStrLit("hello") ) @@ -268,7 +264,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkDerefExpr(nnkIdent(!"x")) + nnkDerefExpr(nnkIdent("x")) Addr operator @@ -282,7 +278,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkAddr(nnkIdent(!"x")) + nnkAddr(nnkIdent("x")) Cast operator @@ -296,7 +292,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkCast(nnkIdent(!"T"), nnkIdent(!"x")) + nnkCast(nnkIdent("T"), nnkIdent("x")) Object access operator ``.`` @@ -310,7 +306,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkDotExpr(nnkIdent(!"x"), nnkIdent(!"y")) + nnkDotExpr(nnkIdent("x"), nnkIdent("y")) If you use Nim's flexible calling syntax (as in ``x.len()``), the result is the same as above but wrapped in an ``nnkCall``. @@ -327,7 +323,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkBracketExpr(nnkIdent(!"x"), nnkIdent(!"y")) + nnkBracketExpr(nnkIdent("x"), nnkIdent("y")) Parentheses @@ -373,8 +369,8 @@ AST: .. code-block:: nim nnkTableConstr( - nnkExprColonExpr(nnkIdent(!"a"), nnkIntLit(3)), - nnkExprColonExpr(nnkIdent(!"b"), nnkIntLit(5)) + nnkExprColonExpr(nnkIdent("a"), nnkIntLit(3)), + nnkExprColonExpr(nnkIdent("b"), nnkIntLit(5)) ) @@ -410,7 +406,7 @@ AST: .. code-block:: nim nnkInfix( - nnkIdent(!".."), + nnkIdent(".."), nnkIntLit(1), nnkIntLit(3) ) @@ -461,7 +457,7 @@ Documentation Comments ---------------------- Double-hash (``##``) comments in the code actually have their own format, -using ``strVal`` to get and set the comment text. Single-hash (``#``) +using ``strVal`` to get and set the comment text. Single-hash (``#``) comments are ignored. Concrete syntax: @@ -497,7 +493,7 @@ AST: .. code-block:: nim nnkPragma( nnkExprColonExpr( - nnkIdent(!"emit"), + nnkIdent("emit"), nnkStrLit("#include <stdio.h>") # the "argument" ) ) @@ -515,10 +511,10 @@ AST: .. code-block:: nim nnkPragma( nnkExprColonExpr( - nnkIdent(!"pragma"), # this is always first when declaring a new pragma - nnkIdent(!"cdeclRename") # the name of the pragma + nnkIdent("pragma"), # this is always first when declaring a new pragma + nnkIdent("cdeclRename") # the name of the pragma ), - nnkIdent(!"cdecl") + nnkIdent("cdecl") ) Statements @@ -570,7 +566,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkAsgn(nnkIdent(!"x"), nnkIntLit(42)) + nnkAsgn(nnkIdent("x"), nnkIntLit(42)) This is not the syntax for assignment when combined with ``var``, ``let``, or ``const``. @@ -736,7 +732,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkBreakStmt(nnkIdent(!"otherLocation")) + nnkBreakStmt(nnkIdent("otherLocation")) If ``break`` is used without a jump-to location, ``nnkEmpty`` replaces ``nnkIdent``. @@ -751,7 +747,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkBlockStmt(nnkIdent(!"name"), nnkStmtList(...)) + nnkBlockStmt(nnkIdent("name"), nnkStmtList(...)) A ``block`` doesn't need an name, in which case ``nnkEmpty`` is used. @@ -787,7 +783,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkImportStmt(nnkIdent(!"math")) + nnkImportStmt(nnkIdent("math")) With ``except``, we get ``nnkImportExceptStmt``. @@ -799,7 +795,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkImportExceptStmt(nnkIdent(!"math"),nnkIdent(!"pow")) + nnkImportExceptStmt(nnkIdent("math"),nnkIdent("pow")) Note that ``import math as m`` does not use a different node; rather, we use ``nnkImportStmt`` with ``as`` as an infix operator. @@ -814,9 +810,9 @@ AST: .. code-block:: nim nnkImportStmt( nnkInfix( - nnkIdent(!"as"), - nnkIdent(!"strutils"), - nnkIdent(!"su") + nnkIdent("as"), + nnkIdent("strutils"), + nnkIdent("su") ) ) @@ -833,7 +829,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkFromStmt(nnkIdent(!"math"), nnkIdent(!"pow")) + nnkFromStmt(nnkIdent("math"), nnkIdent("pow")) Using ``from math as m import pow`` works identically to the ``as`` modifier with the ``import`` statement, but wrapped in ``nnkFromStmt``. @@ -852,7 +848,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkExportStmt(nnkIdent(!"unsigned")) + nnkExportStmt(nnkIdent("unsigned")) Similar to the ``import`` statement, the AST is different for ``export ... except``. @@ -865,7 +861,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkExportExceptStmt(nnkIdent(!"math"),nnkIdent(!"pow")) + nnkExportExceptStmt(nnkIdent("math"),nnkIdent("pow")) Include statement ----------------- @@ -880,7 +876,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkIncludeStmt(nnkIdent(!"blocks")) + nnkIncludeStmt(nnkIdent("blocks")) Var section ----------- @@ -895,7 +891,7 @@ AST: .. code-block:: nim nnkVarSection( nnkIdentDefs( - nnkIdent(!"a"), + nnkIdent("a"), nnkEmpty(), # or nnkIdent(...) if the variable declares the type nnkIntLit(3), ) @@ -925,7 +921,7 @@ AST: .. code-block:: nim nnkLetSection( nnkIdentDefs( - nnkIdent(!"a"), + nnkIdent("a"), nnkEmpty(), # or nnkIdent(...) for the type nnkIntLit(3), ) @@ -944,7 +940,7 @@ AST: .. code-block:: nim nnkConstSection( nnkConstDef( # not nnkConstDefs! - nnkIdent(!"a"), + nnkIdent("a"), nnkEmpty(), # or nnkIdent(...) if the variable declares the type nnkIntLit(3), # required in a const declaration! ) @@ -966,9 +962,9 @@ AST: .. code-block:: nim nnkTypeSection( nnkTypeDef( - nnkIdent(!"A"), + nnkIdent("A"), nnkEmpty(), - nnkIdent(!"int") + nnkIdent("int") ) ) @@ -985,10 +981,10 @@ AST: .. code-block:: nim # ... nnkTypeDef( - nnkIdent(!"MyInt"), + nnkIdent("MyInt"), nnkEmpty(), nnkDistinctTy( - nnkIdent(!"int") + nnkIdent("int") ) ) @@ -1004,10 +1000,10 @@ AST: .. code-block:: nim nnkTypeSection( nnkTypeDef( - nnkIdent(!"A"), + nnkIdent("A"), nnkGenericParams( nnkIdentDefs( - nnkIdent(!"T"), + nnkIdent("T"), nnkEmpty(), # if the type is declared with options, like # ``[T: SomeInteger]``, they are given here nnkEmpty(), @@ -1031,12 +1027,12 @@ AST: .. code-block:: nim # ... nnkTypeDef( - nnkIdent(!"IO"), + nnkIdent("IO"), nnkEmpty(), nnkObjectTy( nnkEmpty(), # no pragmas here nnkOfInherit( - nnkIdent(!"RootObj") # inherits from RootObj + nnkIdent("RootObj") # inherits from RootObj ) nnkEmpty() ) @@ -1062,43 +1058,43 @@ AST: # ... nnkObjectTy( nnkPragma( - nnkIdent(!"inheritable") + nnkIdent("inheritable") ), nnkEmpty(), nnkRecList( # list of object parameters nnkIdentDefs( - nnkIdent(!"name"), - nnkIdent(!"string"), + nnkIdent("name"), + nnkIdent("string"), nnkEmpty() ), nnkRecCase( # case statement within object (not nnkCaseStmt) nnkIdentDefs( - nnkIdent(!"isFat"), - nnkIdent(!"bool"), + nnkIdent("isFat"), + nnkIdent("bool"), nnkEmpty() ), nnkOfBranch( - nnkIdent(!"true"), + nnkIdent("true"), nnkRecList( # again, a list of object parameters nnkIdentDefs( - nnkIdent(!"m"), + nnkIdent("m"), nnkBracketExpr( - nnkIdent(!"array"), + nnkIdent("array"), nnkIntLit(100000), - nnkIdent(!"T") + nnkIdent("T") ), nnkEmpty() ) ), nnkOfBranch( - nnkIdent(!"false"), + nnkIdent("false"), nnkRecList( nnkIdentDefs( - nnkIdent(!"m"), + nnkIdent("m"), nnkBracketExpr( - nnkIdent(!"array"), + nnkIdent("array"), nnkIntLit(10), - nnkIdent(!"T") + nnkIdent("T") ), nnkEmpty() ) @@ -1123,7 +1119,7 @@ AST: # ... nnkEnumTy( nnkEmpty(), - nnkIdent(!"First") # you need at least one nnkIdent or the compiler complains + nnkIdent("First") # you need at least one nnkIdent or the compiler complains ) The usage of ``concept`` (experimental) is similar to objects. @@ -1158,9 +1154,9 @@ AST: .. code-block:: nim # ... within nnkGenericParams nnkIdentDefs( - nnkIdent(!"T"), + nnkIdent("T"), nnkStaticTy( - nnkIdent(!"int") + nnkIdent("int") ), nnkEmpty() ) @@ -1180,7 +1176,7 @@ Nim type Corresponding AST ``distinct`` ``nnkDistinctTy`` ``enum`` ``nnkEnumTy`` ``concept`` ``nnkTypeClassTy``\* -``array`` ``nnkBracketExpr(nnkIdent(!"array"),...``\* +``array`` ``nnkBracketExpr(nnkIdent("array"),...``\* ``proc`` ``nnkProcTy`` ``iterator`` ``nnkIteratorTy`` ``object`` ``nnkObjectTy`` @@ -1200,7 +1196,7 @@ AST: .. code-block:: nim # ... nnkTypeDef( - nnkIdent(!"MyProc"), + nnkIdent("MyProc"), nnkGenericParams( # here, not with the proc # ... ) @@ -1225,7 +1221,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkMixinStmt(nnkIdent(!"x")) + nnkMixinStmt(nnkIdent("x")) Bind statement -------------- @@ -1238,7 +1234,7 @@ Concrete syntax: AST: .. code-block:: nim - nnkBindStmt(nnkIdent(!"x")) + nnkBindStmt(nnkIdent("x")) Procedure declaration --------------------- @@ -1255,29 +1251,29 @@ AST: .. code-block:: nim nnkProcDef( - nnkPostfix(nnkIdent(!"*"), nnkIdent(!"hello")), # the exported proc name + nnkPostfix(nnkIdent("*"), nnkIdent("hello")), # the exported proc name nnkEmpty(), # patterns for term rewriting in templates and macros (not procs) nnkGenericParams( # generic type parameters, like with type declaration nnkIdentDefs( - nnkIdent(!"T"), nnkIdent(!"SomeInteger") + nnkIdent("T"), nnkIdent("SomeInteger") ) ), nnkFormalParams( - nnkIdent(!"int"), # the first FormalParam is the return type. nnkEmpty() if there is none + nnkIdent("int"), # the first FormalParam is the return type. nnkEmpty() if there is none nnkIdentDefs( - nnkIdent(!"x"), - nnkIdent(!"int"), # type type (required for procs, not for templates) + nnkIdent("x"), + nnkIdent("int"), # type type (required for procs, not for templates) nnkIntLit(3) # a default value ), nnkIdentDefs( - nnkIdent(!"y"), - nnkIdent(!"float32"), + nnkIdent("y"), + nnkIdent("float32"), nnkEmpty() ) - nnkPragma(nnkIdent(!"inline")), - nnkEmpty(), # reserved slot for future use - nnkStmtList(nnkDiscardStmt(nnkEmpty())) # the meat of the proc - ) + ), + nnkPragma(nnkIdent("inline")), + nnkEmpty(), # reserved slot for future use + nnkStmtList(nnkDiscardStmt(nnkEmpty())) # the meat of the proc ) There is another consideration. Nim has flexible type identification for @@ -1296,9 +1292,9 @@ AST: nnkFormalParams( nnkEmpty(), # no return here nnkIdentDefs( - nnkIdent(!"a"), # the first parameter - nnkIdent(!"b"), # directly to the second parameter - nnkIdent(!"int"), # their shared type identifier + nnkIdent("a"), # the first parameter + nnkIdent("b"), # directly to the second parameter + nnkIdent("int"), # their shared type identifier nnkEmpty(), # default value would go here ) ), @@ -1318,7 +1314,7 @@ AST: # ... nnkFormalParams( nnkVarTy( - nnkIdent(!"int") + nnkIdent("int") ) ) @@ -1337,7 +1333,7 @@ AST: .. code-block:: nim nnkIteratorDef( - nnkIdent(!"nonsense"), + nnkIdent("nonsense"), nnkEmpty(), ... ) @@ -1356,7 +1352,7 @@ AST: .. code-block:: nim nnkConverterDef( - nnkIdent(!"toBool"), + nnkIdent("toBool"), # ... ) @@ -1378,7 +1374,7 @@ AST: .. code-block:: nim nnkTemplateDef( - nnkIdent(!"optOpt"), + nnkIdent("optOpt"), nnkStmtList( # instead of nnkEmpty() expr1 ), diff --git a/doc/backends.txt b/doc/backends.txt index b7f5308ab..13ef7bf4d 100644 --- a/doc/backends.txt +++ b/doc/backends.txt @@ -65,7 +65,6 @@ The JavaScript target --------------------- Nim can also generate `JavaScript`:idx: code through the ``js`` command. -However, the JavaScript code generator is experimental! Nim targets JavaScript 1.5 which is supported by any widely used browser. Since JavaScript does not have a portable means to include another module, @@ -77,7 +76,7 @@ available. This includes: * manual memory management (``alloc``, etc.) * casting and other unsafe operations (``cast`` operator, ``zeroMem``, etc.) * file management -* most modules of the Standard library +* most modules of the standard library * proper 64 bit integer arithmetic * unsigned integer arithmetic @@ -87,9 +86,8 @@ However, the modules `strutils <strutils.html>`_, `math <math.html>`_, and To compile a Nim module into a ``.js`` file use the ``js`` command; the default is a ``.js`` file that is supposed to be referenced in an ``.html`` -file. However, you can also run the code with `nodejs`:idx:, a `software -platform for easily building fast, scalable network applications -<http://nodejs.org>`_:: +file. However, you can also run the code with `nodejs`:idx: +(`<http://nodejs.org>`_):: nim js -d:nodejs -r examples/hallo.nim @@ -330,8 +328,9 @@ Nimcache naming logic The `nimcache`:idx: directory is generated during compilation and will hold either temporary or final files depending on your backend target. The default -name for the directory is ``nimcache`` but you can use the ``--nimcache`` -`compiler switch <nimc.html#command-line-switches>`_ to change it. +name for the directory depends on the used backend and on your OS but you can +use the ``--nimcache`` `compiler switch <nimc.html#command-line-switches>`_ to +change it. Nimcache and C like targets ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/basicopt.txt b/doc/basicopt.txt index 4db2d5af7..a9166d36c 100644 --- a/doc/basicopt.txt +++ b/doc/basicopt.txt @@ -12,7 +12,8 @@ Options: -p, --path:PATH add path to search paths -d, --define:SYMBOL(:VAL) define a conditional symbol - (Optionally: Define the value for that symbol) + (Optionally: Define the value for that symbol, + see: "compile time define pragmas") -u, --undef:SYMBOL undefine a conditional symbol -f, --forceBuild force rebuilding of all modules --stackTrace:on|off turn stack tracing on|off @@ -29,14 +30,13 @@ Options: --nanChecks:on|off turn NaN checks on|off --infChecks:on|off turn Inf checks on|off --nilChecks:on|off turn nil checks on|off - --deadCodeElim:on|off whole program dead code elimination on|off --opt:none|speed|size optimize not at all or for speed|size Note: use -d:release for a release build! --debugger:native|endb use native debugger (gdb) | ENDB (experimental) --app:console|gui|lib|staticlib generate a console app|GUI app|DLL|static library -r, --run run the compiled program with given arguments - --advanced show advanced command line switches + --fullhelp show all command line switches -h, --help show this help Note, single letter options that take an argument require a colon. E.g. -p:PATH. diff --git a/doc/docgen.rst b/doc/docgen.rst index 40c464ebd..fa23bdc79 100644 --- a/doc/docgen.rst +++ b/doc/docgen.rst @@ -84,52 +84,58 @@ Document Types HTML ---- -Generation of HTML documents is done via both the ``doc`` and ``doc2`` -commands. These command take either a single .nim file, outputting a single -.html file with the same base filename, or multiple .nim files, outputting -multiple .html files and, optionally, an index file. +Generation of HTML documents is done via the ``doc`` command. This command +takes either a single .nim file, outputting a single .html file with the same +base filename, or multiple .nim files, outputting multiple .html files and, +optionally, an index file. The ``doc`` command:: nim doc sample Partial Output:: ... - proc helloWorld*(times: int) - ... - -Output can be viewed in full here: `docgen_sample.html <docgen_sample.html>`_. -The next command, called ``doc2``, is very similar to the ``doc`` command, but -will be run after the compiler performs semantic checking on the input nim -module(s), which allows it to process macros. - -The ``doc2`` command:: - nim doc2 sample - -Partial Output:: - ... proc helloWorld(times: int) {.raises: [], tags: [].} ... -The full output can be seen here: `docgen_sample2.html <docgen_sample2.html>`_. -As you can see, the tool has extracted additional information provided to it by -the compiler beyond what the ``doc`` command provides, such as pragmas attached -implicitly by the compiler. This type of information is not available from -looking at the AST (Abstract Syntax Tree) prior to semantic checking, as the -``doc`` command does. +The full output can be seen here: `docgen_sample.html <docgen_sample.html>`_. +It runs after semantic checking, and includes pragmas attached implicitly by the +compiler. JSON ---- Generation of JSON documents is done via the ``jsondoc`` command. This command -takes in a .nim file, and outputs a .json file with the same base filename. -Note that this tool is built off of the ``doc`` command, and therefore is -performed before semantic checking. +takes in a .nim file, and outputs a .json file with the same base filename. Note +that this tool is built off of the ``doc`` command (previously ``doc2``), and +contains the same information. The ``jsondoc`` command:: nim jsondoc sample Output:: + { + "orig": "docgen_sample.nim", + "nimble": "", + "entries": [ + { + "name": "helloWorld", + "type": "skProc", + "line": 5, + "col": 0, + "description": "Takes an integer and outputs as many "hello world!"s", + "code": "proc helloWorld(times: int) {.raises: [], tags: [].}" + } + ] + } + +Similarly to the old ``doc`` command the old ``jsondoc`` command has been +renamed ``jsondoc0``. + +The ``jsondoc0`` command:: + nim jsondoc0 sample + +Output:: [ { "comment": "This module is a sample." @@ -142,6 +148,8 @@ Output:: } ] +Note that the ``jsondoc`` command outputs it's JSON without pretty-printing it, +while ``jsondoc0`` outputs pretty-printed JSON. Related Options =============== @@ -280,9 +288,9 @@ symbols in the `system module <system.html>`_. `#len,seq[T] <system.html#len,seq[T]>`_ * ``iterator pairs[T](a: seq[T]): tuple[key: int, val: T] {.inline.}`` **=>** `#pairs.i,seq[T] <system.html#pairs.i,seq[T]>`_ -* ``template newException[](exceptn: typedesc; message: string): expr`` **=>** - `#newException.t,typedesc,string - <system.html#newException.t,typedesc,string>`_ +* ``template newException[](exceptn: type; message: string): expr`` **=>** + `#newException.t,type,string + <system.html#newException.t,type,string>`_ Index (idx) file format diff --git a/doc/endb.rst b/doc/endb.rst index 6757d98e3..90bb964ba 100644 --- a/doc/endb.rst +++ b/doc/endb.rst @@ -128,7 +128,7 @@ The ``watchpoint`` pragma is syntactically a statement. It can be used to mark a location as a watchpoint: .. code-block:: Nim - var a: array [0..20, int] + var a: array[0..20, int] {.watchpoint: a[3].} for i in 0 .. 20: a[i] = i diff --git a/doc/exception_hierarchy_fragment.txt b/doc/exception_hierarchy_fragment.txt deleted file mode 100644 index a02d9ccef..000000000 --- a/doc/exception_hierarchy_fragment.txt +++ /dev/null @@ -1,28 +0,0 @@ -* `Exception <system.html#Exception>`_ - * `AccessViolationError <system.html#AccessViolationError>`_ - * `ArithmeticError <system.html#ArithmeticError>`_ - * `DivByZeroError <system.html#DivByZeroError>`_ - * `OverflowError <system.html#OverflowError>`_ - * `AssertionError <system.html#AssertionError>`_ - * `DeadThreadError <system.html#DeadThreadError>`_ - * `FloatingPointError <system.html#FloatingPointError>`_ - * `FloatDivByZeroError <system.html#FloatDivByZeroError>`_ - * `FloatInexactError <system.html#FloatInexactError>`_ - * `FloatInvalidOpError <system.html#FloatInvalidOpError>`_ - * `FloatOverflowError <system.html#FloatOverflowError>`_ - * `FloatUnderflowError <system.html#FloatUnderflowError>`_ - * `FieldError <system.html#FieldError>`_ - * `IndexError <system.html#IndexError>`_ - * `ObjectAssignmentError <system.html#ObjectAssignmentError>`_ - * `ObjectConversionError <system.html#ObjectConversionError>`_ - * `ValueError <system.html#ValueError>`_ - * `KeyError <system.html#KeyError>`_ - * `ReraiseError <system.html#ReraiseError>`_ - * `RangeError <system.html#RangeError>`_ - * `OutOfMemoryError <system.html#OutOfMemoryError>`_ - * `ResourceExhaustedError <system.html#ResourceExhaustedError>`_ - * `StackOverflowError <system.html#StackOverflowError>`_ - * `SystemError <system.html#SystemError>`_ - * `IOError <system.html#IOError>`_ - * `OSError <system.html#OSError>`_ - * `LibraryError <system.html#LibraryError>`_ diff --git a/doc/grammar.txt b/doc/grammar.txt index eae3694a0..e06ebd5d9 100644 --- a/doc/grammar.txt +++ b/doc/grammar.txt @@ -1,3 +1,4 @@ +# This file is generated by compiler/parser.nim. module = stmt ^* (';' / IND{=}) comma = ',' COMMENT? semicolon = ';' COMMENT? @@ -25,9 +26,10 @@ symbol = '`' (KEYW|IDENT|literal|(operator|'('|')'|'['|']'|'{'|'}'|'=')+)+ '`' | IDENT | KEYW exprColonEqExpr = expr (':'|'=' expr)? exprList = expr ^+ comma -dotExpr = expr '.' optInd symbol -qualifiedIdent = symbol ('.' optInd symbol)? exprColonEqExprList = exprColonEqExpr (comma exprColonEqExpr)* (comma)? +dotExpr = expr '.' optInd (symbol | '[:' exprList ']') +explicitGenericInstantiation = '[:' exprList ']' ( '(' exprColonEqExpr ')' )? +qualifiedIdent = symbol ('.' optInd symbol)? setOrTableConstr = '{' ((exprColonEqExpr comma)* | ':' ) '}' castExpr = 'cast' '[' optInd typeDesc optPar ']' '(' optInd expr optPar ')' parKeyw = 'discard' | 'include' | 'if' | 'while' | 'case' | 'try' @@ -155,7 +157,7 @@ routine = optInd identVis pattern? genericParamList? paramListColon pragma? ('=' COMMENT? stmt)? indAndComment commentStmt = COMMENT section(p) = COMMENT? p / (IND{>} (p / COMMENT)^+IND{=} DED) -constant = identWithPragma (colon typedesc)? '=' optInd expr indAndComment +constant = identWithPragma (colon typeDesc)? '=' optInd expr indAndComment enum = 'enum' optInd (symbol optInd ('=' optInd expr COMMENT?)? comma?)+ objectWhen = 'when' expr colcom objectPart COMMENT? ('elif' expr colcom objectPart COMMENT?)* diff --git a/doc/intern.txt b/doc/intern.txt index dadb0eb05..b253cac42 100644 --- a/doc/intern.txt +++ b/doc/intern.txt @@ -38,10 +38,6 @@ Path Purpose Bootstrapping the compiler ========================== -As of version 0.8.5 the compiler is maintained in Nim. (The first versions -have been implemented in Object Pascal.) The Python-based build system has -been rewritten in Nim too. - Compiling the compiler is a simple matter of running:: nim c koch.nim @@ -202,16 +198,131 @@ Compilation cache ================= The implementation of the compilation cache is tricky: There are lots -of issues to be solved for the front- and backend. In the following -sections *global* means *shared between modules* or *property of the whole -program*. +of issues to be solved for the front- and backend. + + +General approach: AST replay +---------------------------- + +We store a module's AST of a successful semantic check in a SQLite +database. There are plenty of features that require a sub sequence +to be re-applied, for example: + +.. code-block:: nim + {.compile: "foo.c".} # even if the module is loaded from the DB, + # "foo.c" needs to be compiled/linked. + +The solution is to **re-play** the module's top level statements. +This solves the problem without having to special case the logic +that fills the internal seqs which are affected by the pragmas. + +In fact, this decribes how the AST should be stored in the database, +as a "shallow" tree. Let's assume we compile module ``m`` with the +following contents: + +.. code-block:: nim + import strutils + + var x*: int = 90 + {.compile: "foo.c".} + proc p = echo "p" + proc q = echo "q" + static: + echo "static" + +Conceptually this is the AST we store for the module: + +.. code-block:: nim + import strutils + + var x* + {.compile: "foo.c".} + proc p + proc q + static: + echo "static" + +The symbol's ``ast`` field is loaded lazily, on demand. This is where most +savings come from, only the shallow outer AST is reconstructed immediately. + +It is also important that the replay involves the ``import`` statement so +that the dependencies are resolved properly. + + +Shared global compiletime state +------------------------------- + +Nim allows ``.global, compiletime`` variables that can be filled by macro +invokations across different modules. This feature breaks modularity in a +severe way. Plenty of different solutions have been proposed: + +- Restrict the types of global compiletime variables to ``Set[T]`` or + similar unordered, only-growable collections so that we can track + the module's write effects to these variables and reapply the changes + in a different order. +- In every module compilation, reset the variable to its default value. +- Provide a restrictive API that can load/save the compiletime state to + a file. + +(These solutions are not mutually exclusive.) + +Since we adopt the "replay the top level statements" idea, the natural +solution to this problem is to emit pseudo top level statements that +reflect the mutations done to the global variable. However, this is +MUCH harder than it sounds, for example ``squeaknim`` uses this +snippet: + +.. code-block:: nim + apicall.add(") module: '" & dllName & "'>\C" & + "\t^self externalCallFailed\C!\C\C") + stCode.add(st & "\C\t\"Generated by NimSqueak\"\C\t" & apicall) + +We can "replay" ``stCode.add`` only if the values of ``st`` +and ``apicall`` are known. And even then a hash table's ``add`` with its +hashing mechanism is too hard to replay. + +In practice, things are worse still, consider ``someGlobal[i][j].add arg``. +We only know the root is ``someGlobal`` but the concrete path to the data +is unknown as is the value that is added. We could compute a "diff" between +the global states and use that to compute a symbol patchset, but this is +quite some work, expensive to do at runtime (it would need to run after +every module has been compiled) and also would break for hash tables. + +We need an API that hides the complex aliasing problems by not relying +on Nim's global variables. The obvious solution is to use string keys +instead of global variables: + +.. code-block:: nim + + proc cachePut*(key: string; value: string) + proc cacheGet*(key: string): string + +However, the values being strings/json is quite problematic: Many +lookup tables that are built at compiletime embed *proc vars* and +types which have no obvious string representation... Seems like +AST diffing is still the best idea as it will not require to use +an alien API and works with some existing Nimble packages, at least. + +On the other hand, in Nim's future I would like to replace the VM +by native code. A diff algorithm wouldn't work for that. +Instead the native code would work with an API like ``put``, ``get``: + +.. code-block:: nim + + proc cachePut*(key: string; value: NimNode) + proc cacheGet*(key: string): NimNode + +The API should embrace the AST diffing notion: See the +module ``macrocache`` for the final details. -Frontend issues ---------------- Methods and type converters -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +--------------------------- + +In the following +sections *global* means *shared between modules* or *property of the whole +program*. Nim contains language features that are *global*. The best example for that are multi methods: Introducing a new method with the same name and some @@ -238,20 +349,17 @@ If in the above example module ``B`` is re-compiled, but ``A`` is not then ``B`` needs to be aware of ``toBool`` even though ``toBool`` is not referenced in ``B`` *explicitly*. -Both the multi method and the type converter problems are solved by storing -them in special sections in the ROD file that are loaded *unconditionally* -when the ROD file is read. +Both the multi method and the type converter problems are solved by the +AST replay implementation. + Generics ~~~~~~~~ -If we generate an instance of a generic, we'd like to re-use that -instance if possible across module boundaries. However, this is not -possible if the compilation cache is enabled. So we give up then and use -the caching of generics only per module, not per project. This means that -``--symbolFiles:on`` hurts a bit for efficiency. A better solution would -be to persist the instantiations in a global cache per project. This might be -implemented in later versions. +We cache generic instantiations and need to ensure this caching works +well with the incremental compilation feature. Since the cache is +attached to the ``PSym`` datastructure, it should work without any +special logic. Backend issues @@ -259,13 +367,10 @@ Backend issues - Init procs must not be "forgotten" to be called. - Files must not be "forgotten" to be linked. -- Anything that is contained in ``nim__dat.c`` is shared between modules - implicitly. - Method dispatchers are global. - DLL loading via ``dlsym`` is global. - Emulated thread vars are global. - However the biggest problem is that dead code elimination breaks modularity! To see why, consider this scenario: The module ``G`` (for example the huge Gtk2 module...) is compiled with dead code elimination turned on. So none @@ -274,25 +379,21 @@ of ``G``'s procs is generated at all. Then module ``B`` is compiled that requires ``G.P1``. Ok, no problem, ``G.P1`` is loaded from the symbol file and ``G.c`` now contains ``G.P1``. -Then module ``A`` (that depends onto ``B`` and ``G``) is compiled and ``B`` +Then module ``A`` (that depends on ``B`` and ``G``) is compiled and ``B`` and ``G`` are left unchanged. ``A`` requires ``G.P2``. So now ``G.c`` MUST contain both ``P1`` and ``P2``, but we haven't even loaded ``P1`` from the symbol file, nor do we want to because we then quickly -would restore large parts of the whole program. But we also don't want to -store ``P1`` in ``B.c`` because that would mean to store every symbol where -it is referred from which ultimately means the main module and putting -everything in a single C file. +would restore large parts of the whole program. -There is however another solution: The old file ``G.c`` containing ``P1`` is -**merged** with the new file ``G.c`` containing ``P2``. This is the solution -that is implemented in the C code generator (have a look at the ``ccgmerge`` -module). The merging may lead to *cruft* (aka dead code) in generated C code -which can only be removed by recompiling a project with the compilation cache -turned off. Nevertheless the merge solution is way superior to the -cheap solution "turn off dead code elimination if the compilation cache is -turned on". +Solution +~~~~~~~~ +The backend must have some logic so that if the currently processed module +is from the compilation cache, the ``ast`` field is not accessed. Instead +the generated C(++) for the symbol's body needs to be cached too and +inserted back into the produced C file. This approach seems to deal with +all the outlined problems above. Debugging Nim's memory management @@ -317,7 +418,7 @@ Introduction I use the term *cell* here to refer to everything that is traced (sequences, refs, strings). -This section describes how the new GC works. +This section describes how the GC works. The basic algorithm is *Deferrent Reference Counting* with cycle detection. References on the stack are not counted for better performance and easier C diff --git a/doc/koch.rst b/doc/koch.rst index ff62b8186..35cf9d8b6 100644 --- a/doc/koch.rst +++ b/doc/koch.rst @@ -35,32 +35,8 @@ options: By default a debug version is created, passing this option will force a release build, which is much faster and should be preferred unless you are debugging the compiler. --d:useGnuReadline - Includes the `rdstdin module <rdstdin.html>`_ for `interactive - mode <nimc.html#nim-interactive-mode>`_ (aka ``nim i``). - This is not needed on Windows. On other platforms this may - incorporate the GNU readline library. --d:nativeStacktrace - Use native stack traces (only for Mac OS X or Linux). --d:avoidTimeMachine - Only for Mac OS X, activating this switch will force excluding - the generated ``nimcache`` directories from Time Machine backups. - By default ``nimcache`` directories will be included in backups, - and just for the Nim compiler itself it means backing up 20MB - of generated files each time you update the compiler. Using this - option will make the compiler invoke the `tmutil - <https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man8/tmutil.8.html>`_ - command on all ``nimcache`` directories, setting their backup - exclusion bit. - - You can use the following command to locate all ``nimcache`` - directories and check their backup exclusion bit:: - - $ find . -type d -name nimcache -exec tmutil isexcluded \{\} \; ---gc:refc|v2|markAndSweep|boehm|go|none - Selects which garbage collection strategy to use for the compiler - and generated code. See the `Nim's Garbage Collector <gc.html>`_ - documentation for more information. +-d:useLinenoise + Use the linenoise library for interactive mode (not needed on Windows). After compilation is finished you will hopefully end up with the nim compiler in the ``bin`` directory. You can add Nim's ``bin`` directory to @@ -104,8 +80,3 @@ be rerun in serial fashion so that meaninful error output can be gathered for inspection. The ``--parallelBuild:n`` switch or configuration option can be used to force a specific number of parallel jobs or run everything serially from the start (``n == 1``). - -zip command ------------ - -The `zip`:idx: command builds the installation ZIP package. diff --git a/doc/lib.rst b/doc/lib.rst index 2e963451d..f0569070d 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -216,12 +216,6 @@ Math libraries Floating-point environment. Handling of floating-point rounding and exceptions (overflow, zero-devide, etc.). -* `basic2d <basic2d.html>`_ - Basic 2d support with vectors, points, matrices and some basic utilities. - -* `basic3d <basic3d.html>`_ - Basic 3d support with vectors, points, matrices and some basic utilities. - * `mersenne <mersenne.html>`_ Mersenne twister random number generator. @@ -418,9 +412,8 @@ Miscellaneous * `options <options.html>`_ Types which encapsulate an optional value. -* `future <future.html>`_ - This module implements new experimental features. Currently the syntax - sugar for anonymous procedures. +* `sugar <sugar.html>`_ + This module implements nice syntactic sugar based on Nim's macro system. * `coro <coro.html>`_ This module implements experimental coroutines in Nim. @@ -443,6 +436,10 @@ Modules for JS backend * `asyncjs <asyncjs.html>`_ Types and macros for writing asynchronous procedures in JavaScript. +* `jscore <jscore.html>`_ + Wrapper of core JavaScript functions. For most purposes you should be using + the ``math``, ``json``, and ``times`` stdlib modules instead of this module. + Deprecated modules ------------------ @@ -568,29 +565,4 @@ Nimble is a package manager for the Nim programming language. For instructions on how to install Nimble packages see `its README <https://github.com/nim-lang/nimble#readme>`_. -Official packages ------------------ - -These packages are officially supported and will therefore be continually -maintained to ensure that they work with the latest versions of the Nim -compiler. - -.. raw:: html - - <div id="officialPkgList"><b>If you are reading this you are missing - nimblepkglist.js or have javascript disabled in your browser.</b></div> - -Unofficial packages -------------------- - -These packages have been developed by independent Nim developers and as -such may not always be up to date with the latest developments in the -Nim programming language. - -.. raw:: html - - <div id="unofficialPkgList"><b>If you are reading this you are missing - nimblepkglist.js or have javascript disabled in your browser.</b></div> - - <script type="text/javascript" src="nimblepkglist.js"></script> - <script type="text/javascript" src="https://irclogs.nim-lang.org/packages?callback=gotPackageList" async></script> +To see a list of Nimble's packages, check out `https://nimble.directory/ <https://nimble.directory/>`_ or the `packages repos <https://github.com/nim-lang/packages>`_ on GitHub. diff --git a/doc/manual.rst b/doc/manual.rst index 2dbbf0447..6dc6794f1 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -13,27 +13,8382 @@ Nim Manual pretty much constant for a given task. -- Ran -.. include:: manual/about.txt -.. include:: manual/definitions.txt -.. include:: manual/lexing.txt -.. include:: manual/syntax.txt -.. include:: manual/types.txt -.. include:: manual/type_rel.txt -.. include:: manual/stmts.txt -.. include:: manual/procs.txt -.. include:: manual/type_sections.txt -.. include:: manual/exceptions.txt -.. include:: manual/effects.txt -.. include:: manual/generics.txt -.. include:: manual/templates.txt -.. include:: manual/typedesc.txt -.. include:: manual/special_ops.txt -.. include:: manual/type_bound_ops.txt -.. include:: manual/trmacros.txt -.. include:: manual/modules.txt -.. include:: manual/compiler_msgs.txt -.. include:: manual/pragmas.txt -.. include:: manual/ffi.txt -.. include:: manual/threads.txt -.. include:: manual/locking.txt -.. include:: manual/taint.txt +About this document +=================== + +**Note**: This document is a draft! Several of Nim's features may need more +precise wording. This manual is constantly evolving into a proper specification. + +This document describes the lexis, the syntax, and the semantics of Nim. + +The language constructs are explained using an extended BNF, in which ``(a)*`` +means 0 or more ``a``'s, ``a+`` means 1 or more ``a``'s, and ``(a)?`` means an +optional *a*. Parentheses may be used to group elements. + +``&`` is the lookahead operator; ``&a`` means that an ``a`` is expected but +not consumed. It will be consumed in the following rule. + +The ``|``, ``/`` symbols are used to mark alternatives and have the lowest +precedence. ``/`` is the ordered choice that requires the parser to try the +alternatives in the given order. ``/`` is often used to ensure the grammar +is not ambiguous. + +Non-terminals start with a lowercase letter, abstract terminal symbols are in +UPPERCASE. Verbatim terminal symbols (including keywords) are quoted +with ``'``. An example:: + + ifStmt = 'if' expr ':' stmts ('elif' expr ':' stmts)* ('else' stmts)? + +The binary ``^*`` operator is used as a shorthand for 0 or more occurrences +separated by its second argument; likewise ``^+`` means 1 or more +occurrences: ``a ^+ b`` is short for ``a (b a)*`` +and ``a ^* b`` is short for ``(a (b a)*)?``. Example:: + + arrayConstructor = '[' expr ^* ',' ']' + +Other parts of Nim - like scoping rules or runtime semantics are only +described in the, more easily comprehensible, informal manner for now. + + + + +Definitions +=========== + +A Nim program specifies a computation that acts on a memory consisting of +components called `locations`:idx:. A variable is basically a name for a +location. Each variable and location is of a certain `type`:idx:. The +variable's type is called `static type`:idx:, the location's type is called +`dynamic type`:idx:. If the static type is not the same as the dynamic type, +it is a super-type or subtype of the dynamic type. + +An `identifier`:idx: is a symbol declared as a name for a variable, type, +procedure, etc. The region of the program over which a declaration applies is +called the `scope`:idx: of the declaration. Scopes can be nested. The meaning +of an identifier is determined by the smallest enclosing scope in which the +identifier is declared unless overloading resolution rules suggest otherwise. + +An expression specifies a computation that produces a value or location. +Expressions that produce locations are called `l-values`:idx:. An l-value +can denote either a location or the value the location contains, depending on +the context. Expressions whose values can be determined statically are called +`constant expressions`:idx:; they are never l-values. + +A `static error`:idx: is an error that the implementation detects before +program execution. Unless explicitly classified, an error is a static error. + +A `checked runtime error`:idx: is an error that the implementation detects +and reports at runtime. The method for reporting such errors is via *raising +exceptions* or *dying with a fatal error*. However, the implementation +provides a means to disable these runtime checks. See the section pragmas_ +for details. + +Whether a checked runtime error results in an exception or in a fatal error at +runtime is implementation specific. Thus the following program is always +invalid: + +.. code-block:: nim + var a: array[0..1, char] + let i = 5 + try: + a[i] = 'N' + except IndexError: + echo "invalid index" + +An `unchecked runtime error`:idx: is an error that is not guaranteed to be +detected, and can cause the subsequent behavior of the computation to +be arbitrary. Unchecked runtime errors cannot occur if only `safe`:idx: +language features are used. + + + +Lexical Analysis +================ + +Encoding +-------- + +All Nim source files are in the UTF-8 encoding (or its ASCII subset). Other +encodings are not supported. Any of the standard platform line termination +sequences can be used - the Unix form using ASCII LF (linefeed), the Windows +form using the ASCII sequence CR LF (return followed by linefeed), or the old +Macintosh form using the ASCII CR (return) character. All of these forms can be +used equally, regardless of platform. + + +Indentation +----------- + +Nim's standard grammar describes an `indentation sensitive`:idx: language. +This means that all the control structures are recognized by indentation. +Indentation consists only of spaces; tabulators are not allowed. + +The indentation handling is implemented as follows: The lexer annotates the +following token with the preceding number of spaces; indentation is not +a separate token. This trick allows parsing of Nim with only 1 token of +lookahead. + +The parser uses a stack of indentation levels: the stack consists of integers +counting the spaces. The indentation information is queried at strategic +places in the parser but ignored otherwise: The pseudo terminal ``IND{>}`` +denotes an indentation that consists of more spaces than the entry at the top +of the stack; ``IND{=}`` an indentation that has the same number of spaces. ``DED`` +is another pseudo terminal that describes the *action* of popping a value +from the stack, ``IND{>}`` then implies to push onto the stack. + +With this notation we can now easily define the core of the grammar: A block of +statements (simplified example):: + + ifStmt = 'if' expr ':' stmt + (IND{=} 'elif' expr ':' stmt)* + (IND{=} 'else' ':' stmt)? + + simpleStmt = ifStmt / ... + + stmt = IND{>} stmt ^+ IND{=} DED # list of statements + / simpleStmt # or a simple statement + + + +Comments +-------- + +Comments start anywhere outside a string or character literal with the +hash character ``#``. +Comments consist of a concatenation of `comment pieces`:idx:. A comment piece +starts with ``#`` and runs until the end of the line. The end of line characters +belong to the piece. If the next line only consists of a comment piece with +no other tokens between it and the preceding one, it does not start a new +comment: + + +.. code-block:: nim + i = 0 # This is a single comment over multiple lines. + # The scanner merges these two pieces. + # The comment continues here. + + +`Documentation comments`:idx: are comments that start with two ``##``. +Documentation comments are tokens; they are only allowed at certain places in +the input file as they belong to the syntax tree! + + +Multiline comments +------------------ + +Starting with version 0.13.0 of the language Nim supports multiline comments. +They look like: + +.. code-block:: nim + #[Comment here. + Multiple lines + are not a problem.]# + +Multiline comments support nesting: + +.. code-block:: nim + #[ #[ Multiline comment in already + commented out code. ]# + proc p[T](x: T) = discard + ]# + +Multiline documentation comments also exist and support nesting too: + +.. code-block:: nim + proc foo = + ##[Long documentation comment + here. + ]## + + +Identifiers & Keywords +---------------------- + +Identifiers in Nim can be any string of letters, digits +and underscores, beginning with a letter. Two immediate following +underscores ``__`` are not allowed:: + + letter ::= 'A'..'Z' | 'a'..'z' | '\x80'..'\xff' + digit ::= '0'..'9' + IDENTIFIER ::= letter ( ['_'] (letter | digit) )* + +Currently any Unicode character with an ordinal value > 127 (non ASCII) is +classified as a ``letter`` and may thus be part of an identifier but later +versions of the language may assign some Unicode characters to belong to the +operator characters instead. + +The following keywords are reserved and cannot be used as identifiers: + +.. code-block:: nim + :file: keywords.txt + +Some keywords are unused; they are reserved for future developments of the +language. + + +Identifier equality +------------------- + +Two identifiers are considered equal if the following algorithm returns true: + +.. code-block:: nim + proc sameIdentifier(a, b: string): bool = + a[0] == b[0] and + a.replace("_", "").toLowerAscii == b.replace("_", "").toLowerAscii + +That means only the first letters are compared in a case sensitive manner. Other +letters are compared case insensitively within the ASCII range and underscores are ignored. + +This rather unorthodox way to do identifier comparisons is called +`partial case insensitivity`:idx: and has some advantages over the conventional +case sensitivity: + +It allows programmers to mostly use their own preferred +spelling style, be it humpStyle or snake_style, and libraries written +by different programmers cannot use incompatible conventions. +A Nim-aware editor or IDE can show the identifiers as preferred. +Another advantage is that it frees the programmer from remembering +the exact spelling of an identifier. The exception with respect to the first +letter allows common code like ``var foo: Foo`` to be parsed unambiguously. + +Historically, Nim was a fully `style-insensitive`:idx: language. This meant that +it was not case-sensitive and underscores were ignored and there was not even a +distinction between ``foo`` and ``Foo``. + + +String literals +--------------- + +Terminal symbol in the grammar: ``STR_LIT``. + +String literals can be delimited by matching double quotes, and can +contain the following `escape sequences`:idx:\ : + +================== =================================================== + Escape sequence Meaning +================== =================================================== + ``\p`` platform specific newline: CRLF on Windows, + LF on Unix + ``\r``, ``\c`` `carriage return`:idx: + ``\n``, ``\l`` `line feed`:idx: (often called `newline`:idx:) + ``\f`` `form feed`:idx: + ``\t`` `tabulator`:idx: + ``\v`` `vertical tabulator`:idx: + ``\\`` `backslash`:idx: + ``\"`` `quotation mark`:idx: + ``\'`` `apostrophe`:idx: + ``\`` '0'..'9'+ `character with decimal value d`:idx:; + all decimal digits directly + following are used for the character + ``\a`` `alert`:idx: + ``\b`` `backspace`:idx: + ``\e`` `escape`:idx: `[ESC]`:idx: + ``\x`` HH `character with hex value HH`:idx:; + exactly two hex digits are allowed +================== =================================================== + + +Strings in Nim may contain any 8-bit value, even embedded zeros. However +some operations may interpret the first binary zero as a terminator. + + +Triple quoted string literals +----------------------------- + +Terminal symbol in the grammar: ``TRIPLESTR_LIT``. + +String literals can also be delimited by three double quotes +``"""`` ... ``"""``. +Literals in this form may run for several lines, may contain ``"`` and do not +interpret any escape sequences. +For convenience, when the opening ``"""`` is followed by a newline (there may +be whitespace between the opening ``"""`` and the newline), +the newline (and the preceding whitespace) is not included in the string. The +ending of the string literal is defined by the pattern ``"""[^"]``, so this: + +.. code-block:: nim + """"long string within quotes"""" + +Produces:: + + "long string within quotes" + + +Raw string literals +------------------- + +Terminal symbol in the grammar: ``RSTR_LIT``. + +There are also raw string literals that are preceded with the +letter ``r`` (or ``R``) and are delimited by matching double quotes (just +like ordinary string literals) and do not interpret the escape sequences. +This is especially convenient for regular expressions or Windows paths: + +.. code-block:: nim + + var f = openFile(r"C:\texts\text.txt") # a raw string, so ``\t`` is no tab + +To produce a single ``"`` within a raw string literal, it has to be doubled: + +.. code-block:: nim + + r"a""b" + +Produces:: + + a"b + +``r""""`` is not possible with this notation, because the three leading +quotes introduce a triple quoted string literal. ``r"""`` is the same +as ``"""`` since triple quoted string literals do not interpret escape +sequences either. + + +Generalized raw string literals +------------------------------- + +Terminal symbols in the grammar: ``GENERALIZED_STR_LIT``, +``GENERALIZED_TRIPLESTR_LIT``. + +The construct ``identifier"string literal"`` (without whitespace between the +identifier and the opening quotation mark) is a +generalized raw string literal. It is a shortcut for the construct +``identifier(r"string literal")``, so it denotes a procedure call with a +raw string literal as its only argument. Generalized raw string literals +are especially convenient for embedding mini languages directly into Nim +(for example regular expressions). + +The construct ``identifier"""string literal"""`` exists too. It is a shortcut +for ``identifier("""string literal""")``. + + +Character literals +------------------ + +Character literals are enclosed in single quotes ``''`` and can contain the +same escape sequences as strings - with one exception: the platform +dependent `newline`:idx: (``\p``) +is not allowed as it may be wider than one character (often it is the pair +CR/LF for example). Here are the valid `escape sequences`:idx: for character +literals: + +================== =================================================== + Escape sequence Meaning +================== =================================================== + ``\r``, ``\c`` `carriage return`:idx: + ``\n``, ``\l`` `line feed`:idx: + ``\f`` `form feed`:idx: + ``\t`` `tabulator`:idx: + ``\v`` `vertical tabulator`:idx: + ``\\`` `backslash`:idx: + ``\"`` `quotation mark`:idx: + ``\'`` `apostrophe`:idx: + ``\`` '0'..'9'+ `character with decimal value d`:idx:; + all decimal digits directly + following are used for the character + ``\a`` `alert`:idx: + ``\b`` `backspace`:idx: + ``\e`` `escape`:idx: `[ESC]`:idx: + ``\x`` HH `character with hex value HH`:idx:; + exactly two hex digits are allowed +================== =================================================== + +A character is not an Unicode character but a single byte. The reason for this +is efficiency: for the overwhelming majority of use-cases, the resulting +programs will still handle UTF-8 properly as UTF-8 was specially designed for +this. Another reason is that Nim can thus support ``array[char, int]`` or +``set[char]`` efficiently as many algorithms rely on this feature. The `Rune` +type is used for Unicode characters, it can represent any Unicode character. +``Rune`` is declared in the `unicode module <unicode.html>`_. + + +Numerical constants +------------------- + +Numerical constants are of a single type and have the form:: + + hexdigit = digit | 'A'..'F' | 'a'..'f' + octdigit = '0'..'7' + bindigit = '0'..'1' + HEX_LIT = '0' ('x' | 'X' ) hexdigit ( ['_'] hexdigit )* + DEC_LIT = digit ( ['_'] digit )* + OCT_LIT = '0' 'o' octdigit ( ['_'] octdigit )* + BIN_LIT = '0' ('b' | 'B' ) bindigit ( ['_'] bindigit )* + + INT_LIT = HEX_LIT + | DEC_LIT + | OCT_LIT + | BIN_LIT + + INT8_LIT = INT_LIT ['\''] ('i' | 'I') '8' + INT16_LIT = INT_LIT ['\''] ('i' | 'I') '16' + INT32_LIT = INT_LIT ['\''] ('i' | 'I') '32' + INT64_LIT = INT_LIT ['\''] ('i' | 'I') '64' + + UINT_LIT = INT_LIT ['\''] ('u' | 'U') + UINT8_LIT = INT_LIT ['\''] ('u' | 'U') '8' + UINT16_LIT = INT_LIT ['\''] ('u' | 'U') '16' + UINT32_LIT = INT_LIT ['\''] ('u' | 'U') '32' + UINT64_LIT = INT_LIT ['\''] ('u' | 'U') '64' + + exponent = ('e' | 'E' ) ['+' | '-'] digit ( ['_'] digit )* + FLOAT_LIT = digit (['_'] digit)* (('.' digit (['_'] digit)* [exponent]) |exponent) + FLOAT32_SUFFIX = ('f' | 'F') ['32'] + FLOAT32_LIT = HEX_LIT '\'' FLOAT32_SUFFIX + | (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] FLOAT32_SUFFIX + FLOAT64_SUFFIX = ( ('f' | 'F') '64' ) | 'd' | 'D' + FLOAT64_LIT = HEX_LIT '\'' FLOAT64_SUFFIX + | (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] FLOAT64_SUFFIX + + +As can be seen in the productions, numerical constants can contain underscores +for readability. Integer and floating point literals may be given in decimal (no +prefix), binary (prefix ``0b``), octal (prefix ``0o``) and hexadecimal +(prefix ``0x``) notation. + +There exists a literal for each numerical type that is +defined. The suffix starting with an apostrophe ('\'') is called a +`type suffix`:idx:. Literals without a type suffix are of an integer type, +unless the literal contains a dot or ``E|e`` in which case it is of +type ``float``. This integer type is ``int`` if the literal is in the range +``low(i32)..high(i32)``, otherwise it is ``int64``. +For notational convenience the apostrophe of a type suffix +is optional if it is not ambiguous (only hexadecimal floating point literals +with a type suffix can be ambiguous). + + +The type suffixes are: + +================= ========================= + Type Suffix Resulting type of literal +================= ========================= + ``'i8`` int8 + ``'i16`` int16 + ``'i32`` int32 + ``'i64`` int64 + ``'u`` uint + ``'u8`` uint8 + ``'u16`` uint16 + ``'u32`` uint32 + ``'u64`` uint64 + ``'f`` float32 + ``'d`` float64 + ``'f32`` float32 + ``'f64`` float64 + ``'f128`` float128 +================= ========================= + +Floating point literals may also be in binary, octal or hexadecimal +notation: +``0B0_10001110100_0000101001000111101011101111111011000101001101001001'f64`` +is approximately 1.72826e35 according to the IEEE floating point standard. + +Literals are bounds checked so that they fit the datatype. Non base-10 +literals are used mainly for flags and bit pattern representations, therefore +bounds checking is done on bit width, not value range. If the literal fits in +the bit width of the datatype, it is accepted. +Hence: 0b10000000'u8 == 0x80'u8 == 128, but, 0b10000000'i8 == 0x80'i8 == -1 +instead of causing an overflow error. + +Operators +--------- + +Nim allows user defined operators. An operator is any combination of the +following characters:: + + = + - * / < > + @ $ ~ & % | + ! ? ^ . : \ + +These keywords are also operators: +``and or not xor shl shr div mod in notin is isnot of``. + +`.`:tok: `=`:tok:, `:`:tok:, `::`:tok: are not available as general operators; they +are used for other notational purposes. + +``*:`` is as a special case treated as the two tokens `*`:tok: and `:`:tok: +(to support ``var v*: T``). + + +Other tokens +------------ + +The following strings denote other tokens:: + + ` ( ) { } [ ] , ; [. .] {. .} (. .) [: + + +The `slice`:idx: operator `..`:tok: takes precedence over other tokens that +contain a dot: `{..}`:tok: are the three tokens `{`:tok:, `..`:tok:, `}`:tok: +and not the two tokens `{.`:tok:, `.}`:tok:. + + + +Syntax +====== + +This section lists Nim's standard syntax. How the parser handles +the indentation is already described in the `Lexical Analysis`_ section. + +Nim allows user-definable operators. +Binary operators have 11 different levels of precedence. + + + +Associativity +------------- + +Binary operators whose first character is ``^`` are right-associative, all +other binary operators are left-associative. + +.. code-block:: nim + proc `^/`(x, y: float): float = + # a right-associative division operator + result = x / y + echo 12 ^/ 4 ^/ 8 # 24.0 (4 / 8 = 0.5, then 12 / 0.5 = 24.0) + echo 12 / 4 / 8 # 0.375 (12 / 4 = 3.0, then 3 / 8 = 0.375) + +Precedence +---------- + +Unary operators always bind stronger than any binary +operator: ``$a + b`` is ``($a) + b`` and not ``$(a + b)``. + +If an unary operator's first character is ``@`` it is a `sigil-like`:idx: +operator which binds stronger than a ``primarySuffix``: ``@x.abc`` is parsed +as ``(@x).abc`` whereas ``$x.abc`` is parsed as ``$(x.abc)``. + + +For binary operators that are not keywords the precedence is determined by the +following rules: + +Operators ending in either ``->``, ``~>`` or ``=>`` are called +`arrow like`:idx:, and have the lowest precedence of all operators. + +If the operator ends with ``=`` and its first character is none of +``<``, ``>``, ``!``, ``=``, ``~``, ``?``, it is an *assignment operator* which +has the second lowest precedence. + +Otherwise precedence is determined by the first character. + +================ =============================================== ================== =============== +Precedence level Operators First character Terminal symbol +================ =============================================== ================== =============== + 10 (highest) ``$ ^`` OP10 + 9 ``* / div mod shl shr %`` ``* % \ /`` OP9 + 8 ``+ -`` ``+ - ~ |`` OP8 + 7 ``&`` ``&`` OP7 + 6 ``..`` ``.`` OP6 + 5 ``== <= < >= > != in notin is isnot not of`` ``= < > !`` OP5 + 4 ``and`` OP4 + 3 ``or xor`` OP3 + 2 ``@ : ?`` OP2 + 1 *assignment operator* (like ``+=``, ``*=``) OP1 + 0 (lowest) *arrow like operator* (like ``->``, ``=>``) OP0 +================ =============================================== ================== =============== + + +Whether an operator is used a prefix operator is also affected by preceding +whitespace (this parsing change was introduced with version 0.13.0): + +.. code-block:: nim + echo $foo + # is parsed as + echo($foo) + + +Spacing also determines whether ``(a, b)`` is parsed as an the argument list +of a call or whether it is parsed as a tuple constructor: + +.. code-block:: nim + echo(1, 2) # pass 1 and 2 to echo + +.. code-block:: nim + echo (1, 2) # pass the tuple (1, 2) to echo + + +Grammar +------- + +The grammar's start symbol is ``module``. + +.. include:: grammar.txt + :literal: + + + +Order of evaluation +=================== + +Order of evaluation is strictly left-to-right, inside-out as it is typical for most others +imperative programming languages: + +.. code-block:: nim + :test: "nim c $1" + + var s = "" + + proc p(arg: int): int = + s.add $arg + result = arg + + discard p(p(1) + p(2)) + + doAssert s == "123" + + +Assignments are not special, the left-hand-side expression is evaluated before the +right-hand side: + +.. code-block:: nim + :test: "nim c $1" + + var v = 0 + proc getI(): int = + result = v + inc v + + var a, b: array[0..2, int] + + proc someCopy(a: var int; b: int) = a = b + + a[getI()] = getI() + + doAssert a == [1, 0, 0] + + v = 0 + someCopy(b[getI()], getI()) + + doAssert b == [1, 0, 0] + + +Rationale: Consistency with overloaded assignment or assignment-like operations, +``a = b`` can be read as ``performSomeCopy(a, b)``. + + +Types +===== + +All expressions have a type which is known at compile time. Nim +is statically typed. One can declare new types, which is in essence defining +an identifier that can be used to denote this custom type. + +These are the major type classes: + +* ordinal types (consist of integer, bool, character, enumeration + (and subranges thereof) types) +* floating point types +* string type +* structured types +* reference (pointer) type +* procedural type +* generic type + + +Ordinal types +------------- +Ordinal types have the following characteristics: + +- Ordinal types are countable and ordered. This property allows + the operation of functions as ``inc``, ``ord``, ``dec`` on ordinal types to + be defined. +- Ordinal values have a smallest possible value. Trying to count further + down than the smallest value gives a checked runtime or static error. +- Ordinal values have a largest possible value. Trying to count further + than the largest value gives a checked runtime or static error. + +Integers, bool, characters and enumeration types (and subranges of these +types) belong to ordinal types. For reasons of simplicity of implementation +the types ``uint`` and ``uint64`` are not ordinal types. + + +Pre-defined integer types +------------------------- +These integer types are pre-defined: + +``int`` + the generic signed integer type; its size is platform dependent and has the + same size as a pointer. This type should be used in general. An integer + literal that has no type suffix is of this type if it is in the range + ``low(int32)..high(int32)`` otherwise the literal's type is ``int64``. + +intXX + additional signed integer types of XX bits use this naming scheme + (example: int16 is a 16 bit wide integer). + The current implementation supports ``int8``, ``int16``, ``int32``, ``int64``. + Literals of these types have the suffix 'iXX. + +``uint`` + the generic `unsigned integer`:idx: type; its size is platform dependent and + has the same size as a pointer. An integer literal with the type + suffix ``'u`` is of this type. + +uintXX + additional signed integer types of XX bits use this naming scheme + (example: uint16 is a 16 bit wide unsigned integer). + The current implementation supports ``uint8``, ``uint16``, ``uint32``, + ``uint64``. Literals of these types have the suffix 'uXX. + Unsigned operations all wrap around; they cannot lead to over- or + underflow errors. + + +In addition to the usual arithmetic operators for signed and unsigned integers +(``+ - *`` etc.) there are also operators that formally work on *signed* +integers but treat their arguments as *unsigned*: They are mostly provided +for backwards compatibility with older versions of the language that lacked +unsigned integer types. These unsigned operations for signed integers use +the ``%`` suffix as convention: + + +====================== ====================================================== +operation meaning +====================== ====================================================== +``a +% b`` unsigned integer addition +``a -% b`` unsigned integer subtraction +``a *% b`` unsigned integer multiplication +``a /% b`` unsigned integer division +``a %% b`` unsigned integer modulo operation +``a <% b`` treat ``a`` and ``b`` as unsigned and compare +``a <=% b`` treat ``a`` and ``b`` as unsigned and compare +``ze(a)`` extends the bits of ``a`` with zeros until it has the + width of the ``int`` type +``toU8(a)`` treats ``a`` as unsigned and converts it to an + unsigned integer of 8 bits (but still the + ``int8`` type) +``toU16(a)`` treats ``a`` as unsigned and converts it to an + unsigned integer of 16 bits (but still the + ``int16`` type) +``toU32(a)`` treats ``a`` as unsigned and converts it to an + unsigned integer of 32 bits (but still the + ``int32`` type) +====================== ====================================================== + +`Automatic type conversion`:idx: is performed in expressions where different +kinds of integer types are used: the smaller type is converted to the larger. + +A `narrowing type conversion`:idx: converts a larger to a smaller type (for +example ``int32 -> int16``. A `widening type conversion`:idx: converts a +smaller type to a larger type (for example ``int16 -> int32``). In Nim only +widening type conversions are *implicit*: + +.. code-block:: nim + var myInt16 = 5i16 + var myInt: int + myInt16 + 34 # of type ``int16`` + myInt16 + myInt # of type ``int`` + myInt16 + 2i32 # of type ``int32`` + +However, ``int`` literals are implicitly convertible to a smaller integer type +if the literal's value fits this smaller type and such a conversion is less +expensive than other implicit conversions, so ``myInt16 + 34`` produces +an ``int16`` result. + +For further details, see `Convertible relation +<#type-relations-convertible-relation>`_. + + +Subrange types +-------------- +A subrange type is a range of values from an ordinal or floating point type (the base +type). To define a subrange type, one must specify it's limiting values: the +lowest and highest value of the type: + +.. code-block:: nim + type + Subrange = range[0..5] + PositiveFloat = range[0.0..Inf] + + +``Subrange`` is a subrange of an integer which can only hold the values 0 +to 5. ``PositiveFloat`` defines a subrange of all positive floating point values. +NaN does not belong to any subrange of floating point types. +Assigning any other value to a variable of type ``Subrange`` is a +checked runtime error (or static error if it can be statically +determined). Assignments from the base type to one of its subrange types +(and vice versa) are allowed. + +A subrange type has the same size as its base type (``int`` in the +Subrange example). + + +Pre-defined floating point types +-------------------------------- + +The following floating point types are pre-defined: + +``float`` + the generic floating point type; its size used to be platform dependent, + but now it is always mapped to ``float64``. + This type should be used in general. + +floatXX + an implementation may define additional floating point types of XX bits using + this naming scheme (example: float64 is a 64 bit wide float). The current + implementation supports ``float32`` and ``float64``. Literals of these types + have the suffix 'fXX. + + +Automatic type conversion in expressions with different kinds +of floating point types is performed: See `Convertible relation`_ for further +details. Arithmetic performed on floating point types follows the IEEE +standard. Integer types are not converted to floating point types automatically +and vice versa. + +The IEEE standard defines five types of floating-point exceptions: + +* Invalid: operations with mathematically invalid operands, + for example 0.0/0.0, sqrt(-1.0), and log(-37.8). +* Division by zero: divisor is zero and dividend is a finite nonzero number, + for example 1.0/0.0. +* Overflow: operation produces a result that exceeds the range of the exponent, + for example MAXDOUBLE+0.0000000000001e308. +* Underflow: operation produces a result that is too small to be represented + as a normal number, for example, MINDOUBLE * MINDOUBLE. +* Inexact: operation produces a result that cannot be represented with infinite + precision, for example, 2.0 / 3.0, log(1.1) and 0.1 in input. + +The IEEE exceptions are either ignored at runtime or mapped to the +Nim exceptions: `FloatInvalidOpError`:idx:, `FloatDivByZeroError`:idx:, +`FloatOverflowError`:idx:, `FloatUnderflowError`:idx:, +and `FloatInexactError`:idx:. +These exceptions inherit from the `FloatingPointError`:idx: base class. + +Nim provides the pragmas `nanChecks`:idx: and `infChecks`:idx: to control +whether the IEEE exceptions are ignored or trap a Nim exception: + +.. code-block:: nim + {.nanChecks: on, infChecks: on.} + var a = 1.0 + var b = 0.0 + echo b / b # raises FloatInvalidOpError + echo a / b # raises FloatOverflowError + +In the current implementation ``FloatDivByZeroError`` and ``FloatInexactError`` +are never raised. ``FloatOverflowError`` is raised instead of +``FloatDivByZeroError``. +There is also a `floatChecks`:idx: pragma that is a short-cut for the +combination of ``nanChecks`` and ``infChecks`` pragmas. ``floatChecks`` are +turned off as default. + +The only operations that are affected by the ``floatChecks`` pragma are +the ``+``, ``-``, ``*``, ``/`` operators for floating point types. + +An implementation should always use the maximum precision available to evaluate +floating pointer values at compile time; this means expressions like +``0.09'f32 + 0.01'f32 == 0.09'f64 + 0.01'f64`` are true. + + +Boolean type +------------ +The boolean type is named `bool`:idx: in Nim and can be one of the two +pre-defined values ``true`` and ``false``. Conditions in ``while``, +``if``, ``elif``, ``when``-statements need to be of type ``bool``. + +This condition holds:: + + ord(false) == 0 and ord(true) == 1 + +The operators ``not, and, or, xor, <, <=, >, >=, !=, ==`` are defined +for the bool type. The ``and`` and ``or`` operators perform short-cut +evaluation. Example: + +.. code-block:: nim + + while p != nil and p.name != "xyz": + # p.name is not evaluated if p == nil + p = p.next + + +The size of the bool type is one byte. + + +Character type +-------------- +The character type is named ``char`` in Nim. Its size is one byte. +Thus it cannot represent an UTF-8 character, but a part of it. +The reason for this is efficiency: for the overwhelming majority of use-cases, +the resulting programs will still handle UTF-8 properly as UTF-8 was specially +designed for this. +Another reason is that Nim can support ``array[char, int]`` or +``set[char]`` efficiently as many algorithms rely on this feature. The +`Rune` type is used for Unicode characters, it can represent any Unicode +character. ``Rune`` is declared in the `unicode module <unicode.html>`_. + + + + +Enumeration types +----------------- +Enumeration types define a new type whose values consist of the ones +specified. The values are ordered. Example: + +.. code-block:: nim + + type + Direction = enum + north, east, south, west + + +Now the following holds:: + + ord(north) == 0 + ord(east) == 1 + ord(south) == 2 + ord(west) == 3 + + # Also allowed: + ord(Direction.west) == 3 + +Thus, north < east < south < west. The comparison operators can be used +with enumeration types. Instead of ``north`` etc, the enum value can also +be qualified with the enum type that it resides in, ``Direction.north``. + +For better interfacing to other programming languages, the fields of enum +types can be assigned an explicit ordinal value. However, the ordinal values +have to be in ascending order. A field whose ordinal value is not +explicitly given is assigned the value of the previous field + 1. + +An explicit ordered enum can have *holes*: + +.. code-block:: nim + type + TokenType = enum + a = 2, b = 4, c = 89 # holes are valid + +However, it is then not an ordinal anymore, so it is not possible to use these +enums as an index type for arrays. The procedures ``inc``, ``dec``, ``succ`` +and ``pred`` are not available for them either. + + +The compiler supports the built-in stringify operator ``$`` for enumerations. +The stringify's result can be controlled by explicitly giving the string +values to use: + +.. code-block:: nim + + type + MyEnum = enum + valueA = (0, "my value A"), + valueB = "value B", + valueC = 2, + valueD = (3, "abc") + +As can be seen from the example, it is possible to both specify a field's +ordinal value and its string value by using a tuple. It is also +possible to only specify one of them. + +An enum can be marked with the ``pure`` pragma so that it's fields are +added to a special module specific hidden scope that is only queried +as the last attempt. Only non-ambiguous symbols are added to this scope. +But one can always access these via type qualification written +as ``MyEnum.value``: + +.. code-block:: nim + + type + MyEnum {.pure.} = enum + valueA, valueB, valueC, valueD, amb + + OtherEnum {.pure.} = enum + valueX, valueY, valueZ, amb + + + echo valueA # MyEnum.valueA + echo amb # Error: Unclear whether it's MyEnum.amb or OtherEnum.amb + echo MyEnum.amb # OK. + + +String type +----------- +All string literals are of the type ``string``. A string in Nim is very +similar to a sequence of characters. However, strings in Nim are both +zero-terminated and have a length field. One can retrieve the length with the +builtin ``len`` procedure; the length never counts the terminating zero. + +The terminating zero cannot be accessed unless the string is converted +to the ``cstring`` type first. The terminating zero assures that this +conversion can be done in O(1) and without any allocations. + +The assignment operator for strings always copies the string. +The ``&`` operator concatenates strings. + +Most native Nim types support conversion to strings with the special ``$`` proc. +When calling the ``echo`` proc, for example, the built-in stringify operation +for the parameter is called: + +.. code-block:: nim + + echo 3 # calls `$` for `int` + +Whenever a user creates a specialized object, implementation of this procedure +provides for ``string`` representation. + +.. code-block:: nim + type + Person = object + name: string + age: int + + proc `$`(p: Person): string = # `$` always returns a string + result = p.name & " is " & + $p.age & # we *need* the `$` in front of p.age which + # is natively an integer to convert it to + # a string + " years old." + +While ``$p.name`` can also be used, the ``$`` operation on a string does +nothing. Note that we cannot rely on automatic conversion from an ``int`` to +a ``string`` like we can for the ``echo`` proc. + +Strings are compared by their lexicographical order. All comparison operators +are available. Strings can be indexed like arrays (lower bound is 0). Unlike +arrays, they can be used in case statements: + +.. code-block:: nim + + case paramStr(i) + of "-v": incl(options, optVerbose) + of "-h", "-?": incl(options, optHelp) + else: write(stdout, "invalid command line option!\n") + +Per convention, all strings are UTF-8 strings, but this is not enforced. For +example, when reading strings from binary files, they are merely a sequence of +bytes. The index operation ``s[i]`` means the i-th *char* of ``s``, not the +i-th *unichar*. The iterator ``runes`` from the `unicode module +<unicode.html>`_ can be used for iteration over all Unicode characters. + + +cstring type +------------ + +The ``cstring`` type meaning `compatible string` is the native representation +of a string for the compilation backend. For the C backend the ``cstring`` type +represents a pointer to a zero-terminated char array +compatible to the type ``char*`` in Ansi C. Its primary purpose lies in easy +interfacing with C. The index operation ``s[i]`` means the i-th *char* of +``s``; however no bounds checking for ``cstring`` is performed making the +index operation unsafe. + +A Nim ``string`` is implicitly convertible +to ``cstring`` for convenience. If a Nim string is passed to a C-style +variadic proc, it is implicitly converted to ``cstring`` too: + +.. code-block:: nim + proc printf(formatstr: cstring) {.importc: "printf", varargs, + header: "<stdio.h>".} + + printf("This works %s", "as expected") + +Even though the conversion is implicit, it is not *safe*: The garbage collector +does not consider a ``cstring`` to be a root and may collect the underlying +memory. However in practice this almost never happens as the GC considers +stack roots conservatively. One can use the builtin procs ``GC_ref`` and +``GC_unref`` to keep the string data alive for the rare cases where it does +not work. + +A `$` proc is defined for cstrings that returns a string. Thus to get a nim +string from a cstring: + +.. code-block:: nim + var str: string = "Hello!" + var cstr: cstring = str + var newstr: string = $cstr + +Structured types +---------------- +A variable of a structured type can hold multiple values at the same +time. Structured types can be nested to unlimited levels. Arrays, sequences, +tuples, objects and sets belong to the structured types. + +Array and sequence types +------------------------ +Arrays are a homogeneous type, meaning that each element in the array +has the same type. Arrays always have a fixed length which is specified at +compile time (except for open arrays). They can be indexed by any ordinal type. +A parameter ``A`` may be an *open array*, in which case it is indexed by +integers from 0 to ``len(A)-1``. An array expression may be constructed by the +array constructor ``[]``. The element type of this array expression is +inferred from the type of the first element. All other elements need to be +implicitly convertable to this type. + +Sequences are similar to arrays but of dynamic length which may change +during runtime (like strings). Sequences are implemented as growable arrays, +allocating pieces of memory as items are added. A sequence ``S`` is always +indexed by integers from 0 to ``len(S)-1`` and its bounds are checked. +Sequences can be constructed by the array constructor ``[]`` in conjunction +with the array to sequence operator ``@``. Another way to allocate space for a +sequence is to call the built-in ``newSeq`` procedure. + +A sequence may be passed to a parameter that is of type *open array*. + +Example: + +.. code-block:: nim + + type + IntArray = array[0..5, int] # an array that is indexed with 0..5 + IntSeq = seq[int] # a sequence of integers + var + x: IntArray + y: IntSeq + x = [1, 2, 3, 4, 5, 6] # [] is the array constructor + y = @[1, 2, 3, 4, 5, 6] # the @ turns the array into a sequence + + let z = [1.0, 2, 3, 4] # the type of z is array[0..3, float] + +The lower bound of an array or sequence may be received by the built-in proc +``low()``, the higher bound by ``high()``. The length may be +received by ``len()``. ``low()`` for a sequence or an open array always returns +0, as this is the first valid index. +One can append elements to a sequence with the ``add()`` proc or the ``&`` +operator, and remove (and get) the last element of a sequence with the +``pop()`` proc. + +The notation ``x[i]`` can be used to access the i-th element of ``x``. + +Arrays are always bounds checked (at compile-time or at runtime). These +checks can be disabled via pragmas or invoking the compiler with the +``--boundChecks:off`` command line switch. + + +Open arrays +----------- + +Often fixed size arrays turn out to be too inflexible; procedures should +be able to deal with arrays of different sizes. The `openarray`:idx: type +allows this; it can only be used for parameters. Openarrays are always +indexed with an ``int`` starting at position 0. The ``len``, ``low`` +and ``high`` operations are available for open arrays too. Any array with +a compatible base type can be passed to an openarray parameter, the index +type does not matter. In addition to arrays sequences can also be passed +to an open array parameter. + +The openarray type cannot be nested: multidimensional openarrays are not +supported because this is seldom needed and cannot be done efficiently. + +.. code-block:: nim + proc testOpenArray(x: openArray[int]) = echo repr(x) + + testOpenArray([1,2,3]) # array[] + testOpenArray(@[1,2,3]) # seq[] + +Varargs +------- + +A ``varargs`` parameter is an openarray parameter that additionally +allows to pass a variable number of arguments to a procedure. The compiler +converts the list of arguments to an array implicitly: + +.. code-block:: nim + proc myWriteln(f: File, a: varargs[string]) = + for s in items(a): + write(f, s) + write(f, "\n") + + myWriteln(stdout, "abc", "def", "xyz") + # is transformed to: + myWriteln(stdout, ["abc", "def", "xyz"]) + +This transformation is only done if the varargs parameter is the +last parameter in the procedure header. It is also possible to perform +type conversions in this context: + +.. code-block:: nim + proc myWriteln(f: File, a: varargs[string, `$`]) = + for s in items(a): + write(f, s) + write(f, "\n") + + myWriteln(stdout, 123, "abc", 4.0) + # is transformed to: + myWriteln(stdout, [$123, $"def", $4.0]) + +In this example ``$`` is applied to any argument that is passed to the +parameter ``a``. (Note that ``$`` applied to strings is a nop.) + +Note that an explicit array constructor passed to a ``varargs`` parameter is +not wrapped in another implicit array construction: + +.. code-block:: nim + proc takeV[T](a: varargs[T]) = discard + + takeV([123, 2, 1]) # takeV's T is "int", not "array of int" + + +``varargs[typed]`` is treated specially: It matches a variable list of arguments +of arbitrary type but *always* constructs an implicit array. This is required +so that the builtin ``echo`` proc does what is expected: + +.. code-block:: nim + proc echo*(x: varargs[typed, `$`]) {...} + + echo @[1, 2, 3] + # prints "@[1, 2, 3]" and not "123" + + +Tuples and object types +----------------------- +A variable of a tuple or object type is a heterogeneous storage +container. +A tuple or object defines various named *fields* of a type. A tuple also +defines an *order* of the fields. Tuples are meant for heterogeneous storage +types with no overhead and few abstraction possibilities. The constructor ``()`` +can be used to construct tuples. The order of the fields in the constructor +must match the order of the tuple's definition. Different tuple-types are +*equivalent* if they specify the same fields of the same type in the same +order. The *names* of the fields also have to be identical. + +The assignment operator for tuples copies each component. +The default assignment operator for objects copies each component. Overloading +of the assignment operator is described in `type-bound-operations-operator`_. + +.. code-block:: nim + + type + Person = tuple[name: string, age: int] # type representing a person: + # a person consists of a name + # and an age + var + person: Person + person = (name: "Peter", age: 30) + # the same, but less readable: + person = ("Peter", 30) + +A tuple with one unnamed field can be constructed with the parentheses and a +trailing comma: + +.. code-block:: nim + proc echoUnaryTuple(a: (int,)) = + echo a[0] + + echoUnaryTuple (1,) + + +In fact, a trailing comma is allowed for every tuple construction. + +The implementation aligns the fields for best access performance. The alignment +is compatible with the way the C compiler does it. + +For consistency with ``object`` declarations, tuples in a ``type`` section +can also be defined with indentation instead of ``[]``: + +.. code-block:: nim + type + Person = tuple # type representing a person + name: string # a person consists of a name + age: natural # and an age + +Objects provide many features that tuples do not. Object provide inheritance +and information hiding. Objects have access to their type at runtime, so that +the ``of`` operator can be used to determine the object's type. The ``of`` operator +is similar to the ``instanceof`` operator in Java. + +.. code-block:: nim + type + Person = object of RootObj + name*: string # the * means that `name` is accessible from other modules + age: int # no * means that the field is hidden + + Student = ref object of Person # a student is a person + id: int # with an id field + + var + student: Student + person: Person + assert(student of Student) # is true + assert(student of Person) # also true + +Object fields that should be visible from outside the defining module, have to +be marked by ``*``. In contrast to tuples, different object types are +never *equivalent*. Objects that have no ancestor are implicitly ``final`` +and thus have no hidden type field. One can use the ``inheritable`` pragma to +introduce new object roots apart from ``system.RootObj``. + + +Object construction +------------------- + +Objects can also be created with an `object construction expression`:idx: that +has the syntax ``T(fieldA: valueA, fieldB: valueB, ...)`` where ``T`` is +an ``object`` type or a ``ref object`` type: + +.. code-block:: nim + var student = Student(name: "Anton", age: 5, id: 3) + +Note that, unlike tuples, objects require the field names along with their values. +For a ``ref object`` type ``system.new`` is invoked implicitly. + + +Object variants +--------------- +Often an object hierarchy is overkill in certain situations where simple +variant types are needed. + +An example: + +.. code-block:: nim + + # This is an example how an abstract syntax tree could be modelled in Nim + type + NodeKind = enum # the different node types + nkInt, # a leaf with an integer value + nkFloat, # a leaf with a float value + nkString, # a leaf with a string value + nkAdd, # an addition + nkSub, # a subtraction + nkIf # an if statement + Node = ref NodeObj + NodeObj = object + case kind: NodeKind # the ``kind`` field is the discriminator + of nkInt: intVal: int + of nkFloat: floatVal: float + of nkString: strVal: string + of nkAdd, nkSub: + leftOp, rightOp: Node + of nkIf: + condition, thenPart, elsePart: Node + + # create a new case object: + var n = Node(kind: nkIf, condition: nil) + # accessing n.thenPart is valid because the ``nkIf`` branch is active: + n.thenPart = Node(kind: nkFloat, floatVal: 2.0) + + # the following statement raises an `FieldError` exception, because + # n.kind's value does not fit and the ``nkString`` branch is not active: + n.strVal = "" + + # invalid: would change the active object branch: + n.kind = nkInt + + var x = Node(kind: nkAdd, leftOp: Node(kind: nkInt, intVal: 4), + rightOp: Node(kind: nkInt, intVal: 2)) + # valid: does not change the active object branch: + x.kind = nkSub + +As can been seen from the example, an advantage to an object hierarchy is that +no casting between different object types is needed. Yet, access to invalid +object fields raises an exception. + +The syntax of ``case`` in an object declaration follows closely the syntax of +the ``case`` statement: The branches in a ``case`` section may be indented too. + +In the example the ``kind`` field is called the `discriminator`:idx:\: For +safety its address cannot be taken and assignments to it are restricted: The +new value must not lead to a change of the active object branch. For an object +branch switch ``system.reset`` has to be used. Also, when the fields of a +particular branch are specified during object construction, the correct value +for the discriminator must be supplied at compile-time. + +Package level objects +--------------------- + +Every Nim module resides in a (nimble) package. An object type can be attached +to the package it resides in. If that is done, the type can be referenced from +other modules as an `incomplete`:idx: object type. This features allows to +break up recursive type dependencies accross module boundaries. Incomplete +object types are always passed ``byref`` and can only be used in pointer like +contexts (``var/ref/ptr IncompleteObject``) in general since the compiler does +not yet know the size of the object. To complete an incomplete object +the ``package`` pragma has to be used. ``package`` implies ``byref``. + +As long as a type ``T`` is incomplete ``sizeof(T)`` or "runtime type +information" for ``T`` is not available. + + +Example: + +.. code-block:: nim + + # module A (in an arbitrary package) + type + Pack.SomeObject = object ## declare as incomplete object of package 'Pack' + Triple = object + a, b, c: ref SomeObject ## pointers to incomplete objects are allowed + + ## Incomplete objects can be used as parameters: + proc myproc(x: SomeObject) = discard + + +.. code-block:: nim + + # module B (in package "Pack") + type + SomeObject* {.package.} = object ## Use 'package' to complete the object + s, t: string + x, y: int + + +Set type +-------- + +.. include:: sets_fragment.txt + +Reference and pointer types +--------------------------- +References (similar to pointers in other programming languages) are a +way to introduce many-to-one relationships. This means different references can +point to and modify the same location in memory (also called `aliasing`:idx:). + +Nim distinguishes between `traced`:idx: and `untraced`:idx: references. +Untraced references are also called *pointers*. Traced references point to +objects of a garbage collected heap, untraced references point to +manually allocated objects or to objects somewhere else in memory. Thus +untraced references are *unsafe*. However for certain low-level operations +(accessing the hardware) untraced references are unavoidable. + +Traced references are declared with the **ref** keyword, untraced references +are declared with the **ptr** keyword. In general, a `ptr T` is implicitly +convertible to the `pointer` type. + +An empty subscript ``[]`` notation can be used to derefer a reference, +the ``addr`` procedure returns the address of an item. An address is always +an untraced reference. +Thus the usage of ``addr`` is an *unsafe* feature. + +The ``.`` (access a tuple/object field operator) +and ``[]`` (array/string/sequence index operator) operators perform implicit +dereferencing operations for reference types: + +.. code-block:: nim + + type + Node = ref NodeObj + NodeObj = object + le, ri: Node + data: int + + var + n: Node + new(n) + n.data = 9 + # no need to write n[].data; in fact n[].data is highly discouraged! + +Automatic dereferencing is also performed for the first argument of a routine +call. But currently this feature has to be only enabled +via ``{.experimental: "implicitDeref".}``: + +.. code-block:: nim + {.experimental: "implicitDeref".} + + proc depth(x: NodeObj): int = ... + + var + n: Node + new(n) + echo n.depth + # no need to write n[].depth either + + + +In order to simplify structural type checking, recursive tuples are not valid: + +.. code-block:: nim + # invalid recursion + type MyTuple = tuple[a: ref MyTuple] + +Likewise ``T = ref T`` is an invalid type. + +As a syntactical extension ``object`` types can be anonymous if +declared in a type section via the ``ref object`` or ``ptr object`` notations. +This feature is useful if an object should only gain reference semantics: + +.. code-block:: nim + + type + Node = ref object + le, ri: Node + data: int + + +To allocate a new traced object, the built-in procedure ``new`` has to be used. +To deal with untraced memory, the procedures ``alloc``, ``dealloc`` and +``realloc`` can be used. The documentation of the system module contains +further information. + +If a reference points to *nothing*, it has the value ``nil``. + +Special care has to be taken if an untraced object contains traced objects like +traced references, strings or sequences: in order to free everything properly, +the built-in procedure ``GCunref`` has to be called before freeing the untraced +memory manually: + +.. code-block:: nim + type + Data = tuple[x, y: int, s: string] + + # allocate memory for Data on the heap: + var d = cast[ptr Data](alloc0(sizeof(Data))) + + # create a new string on the garbage collected heap: + d.s = "abc" + + # tell the GC that the string is not needed anymore: + GCunref(d.s) + + # free the memory: + dealloc(d) + +Without the ``GCunref`` call the memory allocated for the ``d.s`` string would +never be freed. The example also demonstrates two important features for low +level programming: the ``sizeof`` proc returns the size of a type or value +in bytes. The ``cast`` operator can circumvent the type system: the compiler +is forced to treat the result of the ``alloc0`` call (which returns an untyped +pointer) as if it would have the type ``ptr Data``. Casting should only be +done if it is unavoidable: it breaks type safety and bugs can lead to +mysterious crashes. + +**Note**: The example only works because the memory is initialized to zero +(``alloc0`` instead of ``alloc`` does this): ``d.s`` is thus initialized to +binary zero which the string assignment can handle. One needs to know low level +details like this when mixing garbage collected data with unmanaged memory. + +.. XXX finalizers for traced objects + + +Not nil annotation +------------------ + +All types for that ``nil`` is a valid value can be annotated to +exclude ``nil`` as a valid value with the ``not nil`` annotation: + +.. code-block:: nim + type + PObject = ref TObj not nil + TProc = (proc (x, y: int)) not nil + + proc p(x: PObject) = + echo "not nil" + + # compiler catches this: + p(nil) + + # and also this: + var x: PObject + p(x) + +The compiler ensures that every code path initializes variables which contain +non nilable pointers. The details of this analysis are still to be specified +here. + + +Procedural type +--------------- +A procedural type is internally a pointer to a procedure. ``nil`` is +an allowed value for variables of a procedural type. Nim uses procedural +types to achieve `functional`:idx: programming techniques. + +Examples: + +.. code-block:: nim + + proc printItem(x: int) = ... + + proc forEach(c: proc (x: int) {.cdecl.}) = + ... + + forEach(printItem) # this will NOT compile because calling conventions differ + + +.. code-block:: nim + + type + OnMouseMove = proc (x, y: int) {.closure.} + + proc onMouseMove(mouseX, mouseY: int) = + # has default calling convention + echo "x: ", mouseX, " y: ", mouseY + + proc setOnMouseMove(mouseMoveEvent: OnMouseMove) = discard + + # ok, 'onMouseMove' has the default calling convention, which is compatible + # to 'closure': + setOnMouseMove(onMouseMove) + + +A subtle issue with procedural types is that the calling convention of the +procedure influences the type compatibility: procedural types are only +compatible if they have the same calling convention. As a special extension, +a procedure of the calling convention ``nimcall`` can be passed to a parameter +that expects a proc of the calling convention ``closure``. + +Nim supports these `calling conventions`:idx:\: + +`nimcall`:idx: + is the default convention used for a Nim **proc**. It is the + same as ``fastcall``, but only for C compilers that support ``fastcall``. + +`closure`:idx: + is the default calling convention for a **procedural type** that lacks + any pragma annotations. It indicates that the procedure has a hidden + implicit parameter (an *environment*). Proc vars that have the calling + convention ``closure`` take up two machine words: One for the proc pointer + and another one for the pointer to implicitly passed environment. + +`stdcall`:idx: + This the stdcall convention as specified by Microsoft. The generated C + procedure is declared with the ``__stdcall`` keyword. + +`cdecl`:idx: + The cdecl convention means that a procedure shall use the same convention + as the C compiler. Under windows the generated C procedure is declared with + the ``__cdecl`` keyword. + +`safecall`:idx: + This is the safecall convention as specified by Microsoft. The generated C + procedure is declared with the ``__safecall`` keyword. The word *safe* + refers to the fact that all hardware registers shall be pushed to the + hardware stack. + +`inline`:idx: + The inline convention means the the caller should not call the procedure, + but inline its code directly. Note that Nim does not inline, but leaves + this to the C compiler; it generates ``__inline`` procedures. This is + only a hint for the compiler: it may completely ignore it and + it may inline procedures that are not marked as ``inline``. + +`fastcall`:idx: + Fastcall means different things to different C compilers. One gets whatever + the C ``__fastcall`` means. + +`syscall`:idx: + The syscall convention is the same as ``__syscall`` in C. It is used for + interrupts. + +`noconv`:idx: + The generated C code will not have any explicit calling convention and thus + use the C compiler's default calling convention. This is needed because + Nim's default calling convention for procedures is ``fastcall`` to + improve speed. + +Most calling conventions exist only for the Windows 32-bit platform. + +The default calling convention is ``nimcall``, unless it is an inner proc (a +proc inside of a proc). For an inner proc an analysis is performed whether it +accesses its environment. If it does so, it has the calling convention +``closure``, otherwise it has the calling convention ``nimcall``. + + +Distinct type +------------- + +A ``distinct`` type is new type derived from a `base type`:idx: that is +incompatible with its base type. In particular, it is an essential property +of a distinct type that it **does not** imply a subtype relation between it +and its base type. Explicit type conversions from a distinct type to its +base type and vice versa are allowed. See also ``distinctBase`` to get the +reverse operation. + + +Modelling currencies +~~~~~~~~~~~~~~~~~~~~ + +A distinct type can be used to model different physical `units`:idx: with a +numerical base type, for example. The following example models currencies. + +Different currencies should not be mixed in monetary calculations. Distinct +types are a perfect tool to model different currencies: + +.. code-block:: nim + type + Dollar = distinct int + Euro = distinct int + + var + d: Dollar + e: Euro + + echo d + 12 + # Error: cannot add a number with no unit and a ``Dollar`` + +Unfortunately, ``d + 12.Dollar`` is not allowed either, +because ``+`` is defined for ``int`` (among others), not for ``Dollar``. So +a ``+`` for dollars needs to be defined: + +.. code-block:: + proc `+` (x, y: Dollar): Dollar = + result = Dollar(int(x) + int(y)) + +It does not make sense to multiply a dollar with a dollar, but with a +number without unit; and the same holds for division: + +.. code-block:: + proc `*` (x: Dollar, y: int): Dollar = + result = Dollar(int(x) * y) + + proc `*` (x: int, y: Dollar): Dollar = + result = Dollar(x * int(y)) + + proc `div` ... + +This quickly gets tedious. The implementations are trivial and the compiler +should not generate all this code only to optimize it away later - after all +``+`` for dollars should produce the same binary code as ``+`` for ints. +The pragma `borrow`:idx: has been designed to solve this problem; in principle +it generates the above trivial implementations: + +.. code-block:: nim + proc `*` (x: Dollar, y: int): Dollar {.borrow.} + proc `*` (x: int, y: Dollar): Dollar {.borrow.} + proc `div` (x: Dollar, y: int): Dollar {.borrow.} + +The ``borrow`` pragma makes the compiler use the same implementation as +the proc that deals with the distinct type's base type, so no code is +generated. + +But it seems all this boilerplate code needs to be repeated for the ``Euro`` +currency. This can be solved with templates_. + +.. code-block:: nim + template additive(typ: type) = + proc `+` *(x, y: typ): typ {.borrow.} + proc `-` *(x, y: typ): typ {.borrow.} + + # unary operators: + proc `+` *(x: typ): typ {.borrow.} + proc `-` *(x: typ): typ {.borrow.} + + template multiplicative(typ, base: type) = + proc `*` *(x: typ, y: base): typ {.borrow.} + proc `*` *(x: base, y: typ): typ {.borrow.} + proc `div` *(x: typ, y: base): typ {.borrow.} + proc `mod` *(x: typ, y: base): typ {.borrow.} + + template comparable(typ: type) = + proc `<` * (x, y: typ): bool {.borrow.} + proc `<=` * (x, y: typ): bool {.borrow.} + proc `==` * (x, y: typ): bool {.borrow.} + + template defineCurrency(typ, base: untyped) = + type + typ* = distinct base + additive(typ) + multiplicative(typ, base) + comparable(typ) + + defineCurrency(Dollar, int) + defineCurrency(Euro, int) + + +The borrow pragma can also be used to annotate the distinct type to allow +certain builtin operations to be lifted: + +.. code-block:: nim + type + Foo = object + a, b: int + s: string + + Bar {.borrow: `.`.} = distinct Foo + + var bb: ref Bar + new bb + # field access now valid + bb.a = 90 + bb.s = "abc" + +Currently only the dot accessor can be borrowed in this way. + + +Avoiding SQL injection attacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An SQL statement that is passed from Nim to an SQL database might be +modelled as a string. However, using string templates and filling in the +values is vulnerable to the famous `SQL injection attack`:idx:\: + +.. code-block:: nim + import strutils + + proc query(db: DbHandle, statement: string) = ... + + var + username: string + + db.query("SELECT FROM users WHERE name = '$1'" % username) + # Horrible security hole, but the compiler does not mind! + +This can be avoided by distinguishing strings that contain SQL from strings +that don't. Distinct types provide a means to introduce a new string type +``SQL`` that is incompatible with ``string``: + +.. code-block:: nim + type + SQL = distinct string + + proc query(db: DbHandle, statement: SQL) = ... + + var + username: string + + db.query("SELECT FROM users WHERE name = '$1'" % username) + # Error at compile time: `query` expects an SQL string! + + +It is an essential property of abstract types that they **do not** imply a +subtype relation between the abstract type and its base type. Explicit type +conversions from ``string`` to ``SQL`` are allowed: + +.. code-block:: nim + import strutils, sequtils + + proc properQuote(s: string): SQL = + # quotes a string properly for an SQL statement + return SQL(s) + + proc `%` (frmt: SQL, values: openarray[string]): SQL = + # quote each argument: + let v = values.mapIt(SQL, properQuote(it)) + # we need a temporary type for the type conversion :-( + type StrSeq = seq[string] + # call strutils.`%`: + result = SQL(string(frmt) % StrSeq(v)) + + db.query("SELECT FROM users WHERE name = '$1'".SQL % [username]) + +Now we have compile-time checking against SQL injection attacks. Since +``"".SQL`` is transformed to ``SQL("")`` no new syntax is needed for nice +looking ``SQL`` string literals. The hypothetical ``SQL`` type actually +exists in the library as the `TSqlQuery type <db_sqlite.html#TSqlQuery>`_ of +modules like `db_sqlite <db_sqlite.html>`_. + + +Void type +--------- + +The ``void`` type denotes the absence of any type. Parameters of +type ``void`` are treated as non-existent, ``void`` as a return type means that +the procedure does not return a value: + +.. code-block:: nim + proc nothing(x, y: void): void = + echo "ha" + + nothing() # writes "ha" to stdout + +The ``void`` type is particularly useful for generic code: + +.. code-block:: nim + proc callProc[T](p: proc (x: T), x: T) = + when T is void: + p() + else: + p(x) + + proc intProc(x: int) = discard + proc emptyProc() = discard + + callProc[int](intProc, 12) + callProc[void](emptyProc) + +However, a ``void`` type cannot be inferred in generic code: + +.. code-block:: nim + callProc(emptyProc) + # Error: type mismatch: got (proc ()) + # but expected one of: + # callProc(p: proc (T), x: T) + +The ``void`` type is only valid for parameters and return types; other symbols +cannot have the type ``void``. + + +Auto type +--------- + +The ``auto`` type can only be used for return types and parameters. For return +types it causes the compiler to infer the type from the routine body: + +.. code-block:: nim + proc returnsInt(): auto = 1984 + +For parameters it currently creates implicitly generic routines: + +.. code-block:: nim + proc foo(a, b: auto) = discard + +Is the same as: + +.. code-block:: nim + proc foo[T1, T2](a: T1, b: T2) = discard + +However later versions of the language might change this to mean "infer the +parameters' types from the body". Then the above ``foo`` would be rejected as +the parameters' types can not be inferred from an empty ``discard`` statement. + + +Type relations +============== + +The following section defines several relations on types that are needed to +describe the type checking done by the compiler. + + +Type equality +------------- +Nim uses structural type equivalence for most types. Only for objects, +enumerations and distinct types name equivalence is used. The following +algorithm, *in pseudo-code*, determines type equality: + +.. code-block:: nim + proc typeEqualsAux(a, b: PType, + s: var HashSet[(PType, PType)]): bool = + if (a,b) in s: return true + incl(s, (a,b)) + if a.kind == b.kind: + case a.kind + of int, intXX, float, floatXX, char, string, cstring, pointer, + bool, nil, void: + # leaf type: kinds identical; nothing more to check + result = true + of ref, ptr, var, set, seq, openarray: + result = typeEqualsAux(a.baseType, b.baseType, s) + of range: + result = typeEqualsAux(a.baseType, b.baseType, s) and + (a.rangeA == b.rangeA) and (a.rangeB == b.rangeB) + of array: + result = typeEqualsAux(a.baseType, b.baseType, s) and + typeEqualsAux(a.indexType, b.indexType, s) + of tuple: + if a.tupleLen == b.tupleLen: + for i in 0..a.tupleLen-1: + if not typeEqualsAux(a[i], b[i], s): return false + result = true + of object, enum, distinct: + result = a == b + of proc: + result = typeEqualsAux(a.parameterTuple, b.parameterTuple, s) and + typeEqualsAux(a.resultType, b.resultType, s) and + a.callingConvention == b.callingConvention + + proc typeEquals(a, b: PType): bool = + var s: HashSet[(PType, PType)] = {} + result = typeEqualsAux(a, b, s) + +Since types are graphs which can have cycles, the above algorithm needs an +auxiliary set ``s`` to detect this case. + + +Type equality modulo type distinction +------------------------------------- + +The following algorithm (in pseudo-code) determines whether two types +are equal with no respect to ``distinct`` types. For brevity the cycle check +with an auxiliary set ``s`` is omitted: + +.. code-block:: nim + proc typeEqualsOrDistinct(a, b: PType): bool = + if a.kind == b.kind: + case a.kind + of int, intXX, float, floatXX, char, string, cstring, pointer, + bool, nil, void: + # leaf type: kinds identical; nothing more to check + result = true + of ref, ptr, var, set, seq, openarray: + result = typeEqualsOrDistinct(a.baseType, b.baseType) + of range: + result = typeEqualsOrDistinct(a.baseType, b.baseType) and + (a.rangeA == b.rangeA) and (a.rangeB == b.rangeB) + of array: + result = typeEqualsOrDistinct(a.baseType, b.baseType) and + typeEqualsOrDistinct(a.indexType, b.indexType) + of tuple: + if a.tupleLen == b.tupleLen: + for i in 0..a.tupleLen-1: + if not typeEqualsOrDistinct(a[i], b[i]): return false + result = true + of distinct: + result = typeEqualsOrDistinct(a.baseType, b.baseType) + of object, enum: + result = a == b + of proc: + result = typeEqualsOrDistinct(a.parameterTuple, b.parameterTuple) and + typeEqualsOrDistinct(a.resultType, b.resultType) and + a.callingConvention == b.callingConvention + elif a.kind == distinct: + result = typeEqualsOrDistinct(a.baseType, b) + elif b.kind == distinct: + result = typeEqualsOrDistinct(a, b.baseType) + + +Subtype relation +---------------- +If object ``a`` inherits from ``b``, ``a`` is a subtype of ``b``. This subtype +relation is extended to the types ``var``, ``ref``, ``ptr``: + +.. code-block:: nim + proc isSubtype(a, b: PType): bool = + if a.kind == b.kind: + case a.kind + of object: + var aa = a.baseType + while aa != nil and aa != b: aa = aa.baseType + result = aa == b + of var, ref, ptr: + result = isSubtype(a.baseType, b.baseType) + +.. XXX nil is a special value! + + +Covariance +---------- + +Covariance in Nim can be introduced only though pointer-like types such +as ``ptr`` and ``ref``. Sequence, Array and OpenArray types, instantiated +with pointer-like types will be considered covariant if and only if they +are also immutable. The introduction of a ``var`` modifier or additional +``ptr`` or ``ref`` indirections would result in invariant treatment of +these types. + +``proc`` types are currently always invariant, but future versions of Nim +may relax this rule. + +User-defined generic types may also be covariant with respect to some of +their parameters. By default, all generic params are considered invariant, +but you may choose the apply the prefix modifier ``in`` to a parameter to +make it contravariant or ``out`` to make it covariant: + +.. code-block:: nim + type + AnnotatedPtr[out T] = + metadata: MyTypeInfo + p: ref T + + RingBuffer[out T] = + startPos: int + data: seq[T] + + Action {.importcpp: "std::function<void ('0)>".} [in T] = object + +When the designated generic parameter is used to instantiate a pointer-like +type as in the case of `AnnotatedPtr` above, the resulting generic type will +also have pointer-like covariance: + +.. code-block:: nim + type + GuiWidget = object of RootObj + Button = object of GuiWidget + ComboBox = object of GuiWidget + + var + widgetPtr: AnnotatedPtr[GuiWidget] + buttonPtr: AnnotatedPtr[Button] + + ... + + proc drawWidget[T](x: AnnotatedPtr[GuiWidget]) = ... + + # you can call procs expecting base types by supplying a derived type + drawWidget(buttonPtr) + + # and you can convert more-specific pointer types to more general ones + widgetPtr = buttonPtr + +Just like with regular pointers, covariance will be enabled only for immutable +values: + +.. code-block:: nim + proc makeComboBox[T](x: var AnnotatedPtr[GuiWidget]) = + x.p = new(ComboBox) + + makeComboBox(buttonPtr) # Error, AnnotatedPtr[Button] cannot be modified + # to point to a ComboBox + +On the other hand, in the `RingBuffer` example above, the designated generic +param is used to instantiate the non-pointer ``seq`` type, which means that +the resulting generic type will have covariance that mimics an array or +sequence (i.e. it will be covariant only when instantiated with ``ptr`` and +``ref`` types): + +.. code-block:: nim + + type + Base = object of RootObj + Derived = object of Base + + proc consumeBaseValues(b: RingBuffer[Base]) = ... + + var derivedValues: RingBuffer[Derived] + + consumeBaseValues(derivedValues) # Error, Base and Derived values may differ + # in size + + proc consumeBasePointers(b: RingBuffer[ptr Base]) = ... + + var derivedPointers: RingBuffer[ptr Derived] + + consumeBaseValues(derivedPointers) # This is legal + +Please note that Nim will treat the user-defined pointer-like types as +proper alternatives to the built-in pointer types. That is, types such +as `seq[AnnotatedPtr[T]]` or `RingBuffer[AnnotatedPtr[T]]` will also be +considered covariant and you can create new pointer-like types by instantiating +other user-defined pointer-like types. + +The contravariant parameters introduced with the ``in`` modifier are currently +useful only when interfacing with imported types having such semantics. + + +Convertible relation +-------------------- +A type ``a`` is **implicitly** convertible to type ``b`` iff the following +algorithm returns true: + +.. code-block:: nim + # XXX range types? + proc isImplicitlyConvertible(a, b: PType): bool = + if isSubtype(a, b) or isCovariant(a, b): + return true + case a.kind + of int: result = b in {int8, int16, int32, int64, uint, uint8, uint16, + uint32, uint64, float, float32, float64} + of int8: result = b in {int16, int32, int64, int} + of int16: result = b in {int32, int64, int} + of int32: result = b in {int64, int} + of uint: result = b in {uint32, uint64} + of uint8: result = b in {uint16, uint32, uint64} + of uint16: result = b in {uint32, uint64} + of uint32: result = b in {uint64} + of float: result = b in {float32, float64} + of float32: result = b in {float64, float} + of float64: result = b in {float32, float} + of seq: + result = b == openArray and typeEquals(a.baseType, b.baseType) + of array: + result = b == openArray and typeEquals(a.baseType, b.baseType) + if a.baseType == char and a.indexType.rangeA == 0: + result = b = cstring + of cstring, ptr: + result = b == pointer + of string: + result = b == cstring + +A type ``a`` is **explicitly** convertible to type ``b`` iff the following +algorithm returns true: + +.. code-block:: nim + proc isIntegralType(t: PType): bool = + result = isOrdinal(t) or t.kind in {float, float32, float64} + + proc isExplicitlyConvertible(a, b: PType): bool = + result = false + if isImplicitlyConvertible(a, b): return true + if typeEqualsOrDistinct(a, b): return true + if isIntegralType(a) and isIntegralType(b): return true + if isSubtype(a, b) or isSubtype(b, a): return true + +The convertible relation can be relaxed by a user-defined type +`converter`:idx:. + +.. code-block:: nim + converter toInt(x: char): int = result = ord(x) + + var + x: int + chr: char = 'a' + + # implicit conversion magic happens here + x = chr + echo x # => 97 + # you can use the explicit form too + x = chr.toInt + echo x # => 97 + +The type conversion ``T(a)`` is an L-value if ``a`` is an L-value and +``typeEqualsOrDistinct(T, type(a))`` holds. + + +Assignment compatibility +------------------------ + +An expression ``b`` can be assigned to an expression ``a`` iff ``a`` is an +`l-value` and ``isImplicitlyConvertible(b.typ, a.typ)`` holds. + + +Overloading resolution +====================== + +In a call ``p(args)`` the routine ``p`` that matches best is selected. If +multiple routines match equally well, the ambiguity is reported at compiletime. + +Every arg in args needs to match. There are multiple different categories how an +argument can match. Let ``f`` be the formal parameter's type and ``a`` the type +of the argument. + +1. Exact match: ``a`` and ``f`` are of the same type. +2. Literal match: ``a`` is an integer literal of value ``v`` + and ``f`` is a signed or unsigned integer type and ``v`` is in ``f``'s + range. Or: ``a`` is a floating point literal of value ``v`` + and ``f`` is a floating point type and ``v`` is in ``f``'s + range. +3. Generic match: ``f`` is a generic type and ``a`` matches, for + instance ``a`` is ``int`` and ``f`` is a generic (constrained) parameter + type (like in ``[T]`` or ``[T: int|char]``. +4. Subrange or subtype match: ``a`` is a ``range[T]`` and ``T`` + matches ``f`` exactly. Or: ``a`` is a subtype of ``f``. +5. Integral conversion match: ``a`` is convertible to ``f`` and ``f`` and ``a`` + is some integer or floating point type. +6. Conversion match: ``a`` is convertible to ``f``, possibly via a user + defined ``converter``. + +These matching categories have a priority: An exact match is better than a +literal match and that is better than a generic match etc. In the following +``count(p, m)`` counts the number of matches of the matching category ``m`` +for the routine ``p``. + +A routine ``p`` matches better than a routine ``q`` if the following +algorithm returns true:: + + for each matching category m in ["exact match", "literal match", + "generic match", "subtype match", + "integral match", "conversion match"]: + if count(p, m) > count(q, m): return true + elif count(p, m) == count(q, m): + discard "continue with next category m" + else: + return false + return "ambiguous" + + +Some examples: + +.. code-block:: nim + proc takesInt(x: int) = echo "int" + proc takesInt[T](x: T) = echo "T" + proc takesInt(x: int16) = echo "int16" + + takesInt(4) # "int" + var x: int32 + takesInt(x) # "T" + var y: int16 + takesInt(y) # "int16" + var z: range[0..4] = 0 + takesInt(z) # "T" + + +If this algorithm returns "ambiguous" further disambiguation is performed: +If the argument ``a`` matches both the parameter type ``f`` of ``p`` +and ``g`` of ``q`` via a subtyping relation, the inheritance depth is taken +into account: + +.. code-block:: nim + type + A = object of RootObj + B = object of A + C = object of B + + proc p(obj: A) = + echo "A" + + proc p(obj: B) = + echo "B" + + var c = C() + # not ambiguous, calls 'B', not 'A' since B is a subtype of A + # but not vice versa: + p(c) + + proc pp(obj: A, obj2: B) = echo "A B" + proc pp(obj: B, obj2: A) = echo "B A" + + # but this is ambiguous: + pp(c, c) + + +Likewise for generic matches the most specialized generic type (that still +matches) is preferred: + +.. code-block:: nim + proc gen[T](x: ref ref T) = echo "ref ref T" + proc gen[T](x: ref T) = echo "ref T" + proc gen[T](x: T) = echo "T" + + var ri: ref int + gen(ri) # "ref T" + + +Overloading based on 'var T' +---------------------------- + +If the formal parameter ``f`` is of type ``var T`` in addition to the ordinary +type checking, the argument is checked to be an `l-value`:idx:. ``var T`` +matches better than just ``T`` then. + +.. code-block:: nim + proc sayHi(x: int): string = + # matches a non-var int + result = $x + proc sayHi(x: var int): string = + # matches a var int + result = $(x + 10) + + proc sayHello(x: int) = + var m = x # a mutable version of x + echo sayHi(x) # matches the non-var version of sayHi + echo sayHi(m) # matches the var version of sayHi + + sayHello(3) # 3 + # 13 + +Automatic dereferencing +----------------------- + +If the `experimental mode <#pragmas-experimental-pragma>`_ is active and no other match +is found, the first argument ``a`` is dereferenced automatically if it's a +pointer type and overloading resolution is tried with ``a[]`` instead. + +Automatic self insertions +------------------------- + +**Note**: The ``.this`` pragma is deprecated and should not be used anymore. + +Starting with version 0.14 of the language, Nim supports ``field`` as a +shortcut for ``self.field`` comparable to the `this`:idx: keyword in Java +or C++. This feature has to be explicitly enabled via a ``{.this: self.}`` +statement pragma. This pragma is active for the rest of the module: + +.. code-block:: nim + type + Parent = object of RootObj + parentField: int + Child = object of Parent + childField: int + + {.this: self.} + proc sumFields(self: Child): int = + result = parentField + childField + # is rewritten to: + # result = self.parentField + self.childField + +Instead of ``self`` any other identifier can be used too, but +``{.this: self.}`` will become the default directive for the whole language +eventually. + +In addition to fields, routine applications are also rewritten, but only +if no other interpretation of the call is possible: + +.. code-block:: nim + proc test(self: Child) = + echo childField, " ", sumFields() + # is rewritten to: + echo self.childField, " ", sumFields(self) + # but NOT rewritten to: + echo self, self.childField, " ", sumFields(self) + + +Lazy type resolution for untyped +-------------------------------- + +**Note**: An `unresolved`:idx: expression is an expression for which no symbol +lookups and no type checking have been performed. + +Since templates and macros that are not declared as ``immediate`` participate +in overloading resolution it's essential to have a way to pass unresolved +expressions to a template or macro. This is what the meta-type ``untyped`` +accomplishes: + +.. code-block:: nim + template rem(x: untyped) = discard + + rem unresolvedExpression(undeclaredIdentifier) + +A parameter of type ``untyped`` always matches any argument (as long as there is +any argument passed to it). + +But one has to watch out because other overloads might trigger the +argument's resolution: + +.. code-block:: nim + template rem(x: untyped) = discard + proc rem[T](x: T) = discard + + # undeclared identifier: 'unresolvedExpression' + rem unresolvedExpression(undeclaredIdentifier) + +``untyped`` and ``varargs[untyped]`` are the only metatype that are lazy in this sense, the other +metatypes ``typed`` and ``type`` are not lazy. + + +Varargs matching +---------------- + +See `Varargs <#types-varargs>`_. + + +Statements and expressions +========================== + +Nim uses the common statement/expression paradigm: Statements do not +produce a value in contrast to expressions. However, some expressions are +statements. + +Statements are separated into `simple statements`:idx: and +`complex statements`:idx:. +Simple statements are statements that cannot contain other statements like +assignments, calls or the ``return`` statement; complex statements can +contain other statements. To avoid the `dangling else problem`:idx:, complex +statements always have to be indented. The details can be found in the grammar. + + +Statement list expression +------------------------- + +Statements can also occur in an expression context that looks +like ``(stmt1; stmt2; ...; ex)``. This is called +an statement list expression or ``(;)``. The type +of ``(stmt1; stmt2; ...; ex)`` is the type of ``ex``. All the other statements +must be of type ``void``. (One can use ``discard`` to produce a ``void`` type.) +``(;)`` does not introduce a new scope. + + +Discard statement +----------------- + +Example: + +.. code-block:: nim + proc p(x, y: int): int = + result = x + y + + discard p(3, 4) # discard the return value of `p` + +The ``discard`` statement evaluates its expression for side-effects and +throws the expression's resulting value away. + +Ignoring the return value of a procedure without using a discard statement is +a static error. + +The return value can be ignored implicitly if the called proc/iterator has +been declared with the `discardable`:idx: pragma: + +.. code-block:: nim + proc p(x, y: int): int {.discardable.} = + result = x + y + + p(3, 4) # now valid + +An empty ``discard`` statement is often used as a null statement: + +.. code-block:: nim + proc classify(s: string) = + case s[0] + of SymChars, '_': echo "an identifier" + of '0'..'9': echo "a number" + else: discard + + +Void context +------------ + +In a list of statements every expression except the last one needs to have the +type ``void``. In addition to this rule an assignment to the builtin ``result`` +symbol also triggers a mandatory ``void`` context for the subsequent expressions: + +.. code-block:: nim + proc invalid*(): string = + result = "foo" + "invalid" # Error: value of type 'string' has to be discarded + +.. code-block:: nim + proc valid*(): string = + let x = 317 + "valid" + + +Var statement +------------- + +Var statements declare new local and global variables and +initialize them. A comma separated list of variables can be used to specify +variables of the same type: + +.. code-block:: nim + + var + a: int = 0 + x, y, z: int + +If an initializer is given the type can be omitted: the variable is then of the +same type as the initializing expression. Variables are always initialized +with a default value if there is no initializing expression. The default +value depends on the type and is always a zero in binary. + +============================ ============================================== +Type default value +============================ ============================================== +any integer type 0 +any float 0.0 +char '\\0' +bool false +ref or pointer type nil +procedural type nil +sequence ``@[]`` +string ``""`` +tuple[x: A, y: B, ...] (default(A), default(B), ...) + (analogous for objects) +array[0..., T] [default(T), ...] +range[T] default(T); this may be out of the valid range +T = enum cast[T](0); this may be an invalid value +============================ ============================================== + + +The implicit initialization can be avoided for optimization reasons with the +`noinit`:idx: pragma: + +.. code-block:: nim + var + a {.noInit.}: array[0..1023, char] + +If a proc is annotated with the ``noinit`` pragma this refers to its implicit +``result`` variable: + +.. code-block:: nim + proc returnUndefinedValue: int {.noinit.} = discard + + +The implicit initialization can be also prevented by the `requiresInit`:idx: +type pragma. The compiler requires an explicit initialization for the object +and all of its fields. However it does a `control flow analysis`:idx: to prove +the variable has been initialized and does not rely on syntactic properties: + +.. code-block:: nim + type + MyObject = object {.requiresInit.} + + proc p() = + # the following is valid: + var x: MyObject + if someCondition(): + x = a() + else: + x = a() + # use x + + +let statement +------------- + +A ``let`` statement declares new local and global `single assignment`:idx: +variables and binds a value to them. The syntax is the same as that of the ``var`` +statement, except that the keyword ``var`` is replaced by the keyword ``let``. +Let variables are not l-values and can thus not be passed to ``var`` parameters +nor can their address be taken. They cannot be assigned new values. + +For let variables the same pragmas are available as for ordinary variables. + + +Tuple unpacking +--------------- + +In a ``var`` or ``let`` statement tuple unpacking can be performed. The special +identifier ``_`` can be used to ignore some parts of the tuple: + +.. code-block:: nim + proc returnsTuple(): (int, int, int) = (4, 2, 3) + + let (x, _, z) = returnsTuple() + + + +Const section +------------- + +`Constants`:idx: are symbols which are bound to a value. The constant's value +cannot change. The compiler must be able to evaluate the expression in a +constant declaration at compile time. + +Nim contains a sophisticated compile-time evaluator, so procedures which +have no side-effect can be used in constant expressions too: + +.. code-block:: nim + import strutils + const + constEval = contains("abc", 'b') # computed at compile time! + + +The rules for compile-time computability are: + +1. Literals are compile-time computable. +2. Type conversions are compile-time computable. +3. Procedure calls of the form ``p(X)`` are compile-time computable if + ``p`` is a proc without side-effects (see the `noSideEffect pragma + <#pragmas-nosideeffect-pragma>`_ for details) and if ``X`` is a + (possibly empty) list of compile-time computable arguments. + + +Constants cannot be of type ``ptr``, ``ref`` or ``var``, nor can +they contain such a type. + + +Static statement/expression +--------------------------- + +A static statement/expression can be used to enforce compile +time evaluation explicitly. Enforced compile time evaluation can even evaluate +code that has side effects: + +.. code-block:: + + static: + echo "echo at compile time" + +It's a static error if the compiler cannot perform the evaluation at compile +time. + +The current implementation poses some restrictions for compile time +evaluation: Code which contains ``cast`` or makes use of the foreign function +interface cannot be evaluated at compile time. Later versions of Nim will +support the FFI at compile time. + + +If statement +------------ + +Example: + +.. code-block:: nim + + var name = readLine(stdin) + + if name == "Andreas": + echo "What a nice name!" + elif name == "": + echo "Don't you have a name?" + else: + echo "Boring name..." + +The ``if`` statement is a simple way to make a branch in the control flow: +The expression after the keyword ``if`` is evaluated, if it is true +the corresponding statements after the ``:`` are executed. Otherwise +the expression after the ``elif`` is evaluated (if there is an +``elif`` branch), if it is true the corresponding statements after +the ``:`` are executed. This goes on until the last ``elif``. If all +conditions fail, the ``else`` part is executed. If there is no ``else`` +part, execution continues with the next statement. + +In ``if`` statements new scopes begin immediately after the ``if``/``elif``/``else`` keywords and ends after the corresponding *then* block. +For visualization purposes the scopes have been enclosed in ``{| |}`` in the following example: + +.. code-block:: nim + if {| (let m = input =~ re"(\w+)=\w+"; m.isMatch): + echo "key ", m[0], " value ", m[1] |} + elif {| (let m = input =~ re""; m.isMatch): + echo "new m in this scope" |} + else: {| + echo "m not declared here" |} + +Case statement +-------------- + +Example: + +.. code-block:: nim + + case readline(stdin) + of "delete-everything", "restart-computer": + echo "permission denied" + of "go-for-a-walk": echo "please yourself" + else: echo "unknown command" + + # indentation of the branches is also allowed; and so is an optional colon + # after the selecting expression: + case readline(stdin): + of "delete-everything", "restart-computer": + echo "permission denied" + of "go-for-a-walk": echo "please yourself" + else: echo "unknown command" + + +The ``case`` statement is similar to the if statement, but it represents +a multi-branch selection. The expression after the keyword ``case`` is +evaluated and if its value is in a *slicelist* the corresponding statements +(after the ``of`` keyword) are executed. If the value is not in any +given *slicelist* the ``else`` part is executed. If there is no ``else`` +part and not all possible values that ``expr`` can hold occur in a +``slicelist``, a static error occurs. This holds only for expressions of +ordinal types. "All possible values" of ``expr`` are determined by ``expr``'s +type. To suppress the static error an ``else`` part with an +empty ``discard`` statement should be used. + +For non ordinal types it is not possible to list every possible value and so +these always require an ``else`` part. + +As case statements perform compile-time exhaustiveness checks, the value in +every ``of`` branch must be known at compile time. This fact is also exploited +to generate more performant code. + +As a special semantic extension, an expression in an ``of`` branch of a case +statement may evaluate to a set or array constructor; the set or array is then +expanded into a list of its elements: + +.. code-block:: nim + const + SymChars: set[char] = {'a'..'z', 'A'..'Z', '\x80'..'\xFF'} + + proc classify(s: string) = + case s[0] + of SymChars, '_': echo "an identifier" + of '0'..'9': echo "a number" + else: echo "other" + + # is equivalent to: + proc classify(s: string) = + case s[0] + of 'a'..'z', 'A'..'Z', '\x80'..'\xFF', '_': echo "an identifier" + of '0'..'9': echo "a number" + else: echo "other" + + +When statement +-------------- + +Example: + +.. code-block:: nim + + when sizeof(int) == 2: + echo "running on a 16 bit system!" + elif sizeof(int) == 4: + echo "running on a 32 bit system!" + elif sizeof(int) == 8: + echo "running on a 64 bit system!" + else: + echo "cannot happen!" + +The ``when`` statement is almost identical to the ``if`` statement with some +exceptions: + +* Each condition (``expr``) has to be a constant expression (of type ``bool``). +* The statements do not open a new scope. +* The statements that belong to the expression that evaluated to true are + translated by the compiler, the other statements are not checked for + semantics! However, each condition is checked for semantics. + +The ``when`` statement enables conditional compilation techniques. As +a special syntactic extension, the ``when`` construct is also available +within ``object`` definitions. + + +When nimvm statement +-------------------- + +``nimvm`` is a special symbol, that may be used as expression of ``when nimvm`` +statement to differentiate execution path between runtime and compile time. + +Example: + +.. code-block:: nim + proc someProcThatMayRunInCompileTime(): bool = + when nimvm: + # This code runs in compile time + result = true + else: + # This code runs in runtime + result = false + const ctValue = someProcThatMayRunInCompileTime() + let rtValue = someProcThatMayRunInCompileTime() + assert(ctValue == true) + assert(rtValue == false) + +``when nimvm`` statement must meet the following requirements: + +* Its expression must always be ``nimvm``. More complex expressions are not + allowed. +* It must not contain ``elif`` branches. +* It must contain ``else`` branch. +* Code in branches must not affect semantics of the code that follows the + ``when nimvm`` statement. E.g. it must not define symbols that are used in + the following code. + +Return statement +---------------- + +Example: + +.. code-block:: nim + return 40+2 + +The ``return`` statement ends the execution of the current procedure. +It is only allowed in procedures. If there is an ``expr``, this is syntactic +sugar for: + +.. code-block:: nim + result = expr + return result + + +``return`` without an expression is a short notation for ``return result`` if +the proc has a return type. The `result`:idx: variable is always the return +value of the procedure. It is automatically declared by the compiler. As all +variables, ``result`` is initialized to (binary) zero: + +.. code-block:: nim + proc returnZero(): int = + # implicitly returns 0 + + +Yield statement +--------------- + +Example: + +.. code-block:: nim + yield (1, 2, 3) + +The ``yield`` statement is used instead of the ``return`` statement in +iterators. It is only valid in iterators. Execution is returned to the body +of the for loop that called the iterator. Yield does not end the iteration +process, but execution is passed back to the iterator if the next iteration +starts. See the section about iterators (`Iterators and the for statement`_) +for further information. + + +Block statement +--------------- + +Example: + +.. code-block:: nim + var found = false + block myblock: + for i in 0..3: + for j in 0..3: + if a[j][i] == 7: + found = true + break myblock # leave the block, in this case both for-loops + echo found + +The block statement is a means to group statements to a (named) ``block``. +Inside the block, the ``break`` statement is allowed to leave the block +immediately. A ``break`` statement can contain a name of a surrounding +block to specify which block is to leave. + + +Break statement +--------------- + +Example: + +.. code-block:: nim + break + +The ``break`` statement is used to leave a block immediately. If ``symbol`` +is given, it is the name of the enclosing block that is to leave. If it is +absent, the innermost block is left. + + +While statement +--------------- + +Example: + +.. code-block:: nim + echo "Please tell me your password:" + var pw = readLine(stdin) + while pw != "12345": + echo "Wrong password! Next try:" + pw = readLine(stdin) + + +The ``while`` statement is executed until the ``expr`` evaluates to false. +Endless loops are no error. ``while`` statements open an `implicit block`, +so that they can be left with a ``break`` statement. + + +Continue statement +------------------ + +A ``continue`` statement leads to the immediate next iteration of the +surrounding loop construct. It is only allowed within a loop. A continue +statement is syntactic sugar for a nested block: + +.. code-block:: nim + while expr1: + stmt1 + continue + stmt2 + +Is equivalent to: + +.. code-block:: nim + while expr1: + block myBlockName: + stmt1 + break myBlockName + stmt2 + + +Assembler statement +------------------- + +The direct embedding of assembler code into Nim code is supported +by the unsafe ``asm`` statement. Identifiers in the assembler code that refer to +Nim identifiers shall be enclosed in a special character which can be +specified in the statement's pragmas. The default special character is ``'`'``: + +.. code-block:: nim + {.push stackTrace:off.} + proc addInt(a, b: int): int = + # a in eax, and b in edx + asm """ + mov eax, `a` + add eax, `b` + jno theEnd + call `raiseOverflow` + theEnd: + """ + {.pop.} + +If the GNU assembler is used, quotes and newlines are inserted automatically: + +.. code-block:: nim + proc addInt(a, b: int): int = + asm """ + addl %%ecx, %%eax + jno 1 + call `raiseOverflow` + 1: + :"=a"(`result`) + :"a"(`a`), "c"(`b`) + """ + +Instead of: + +.. code-block:: nim + proc addInt(a, b: int): int = + asm """ + "addl %%ecx, %%eax\n" + "jno 1\n" + "call `raiseOverflow`\n" + "1: \n" + :"=a"(`result`) + :"a"(`a`), "c"(`b`) + """ + +Using statement +--------------- + +The using statement provides syntactic convenience in modules where +the same parameter names and types are used over and over. Instead of: + +.. code-block:: nim + proc foo(c: Context; n: Node) = ... + proc bar(c: Context; n: Node, counter: int) = ... + proc baz(c: Context; n: Node) = ... + +One can tell the compiler about the convention that a parameter of +name ``c`` should default to type ``Context``, ``n`` should default to +``Node`` etc.: + +.. code-block:: nim + using + c: Context + n: Node + counter: int + + proc foo(c, n) = ... + proc bar(c, n, counter) = ... + proc baz(c, n) = ... + + proc mixedMode(c, n; x, y: int) = + # 'c' is inferred to be of the type 'Context' + # 'n' is inferred to be of the type 'Node' + # But 'x' and 'y' are of type 'int'. + +The ``using`` section uses the same indentation based grouping syntax as +a ``var`` or ``let`` section. + +Note that ``using`` is not applied for ``template`` since untyped template +parameters default to the type ``system.untyped``. + +Mixing parameters that should use the ``using`` declaration with parameters +that are explicitly typed is possible and requires a semicolon between them. + + +If expression +------------- + +An `if expression` is almost like an if statement, but it is an expression. +Example: + +.. code-block:: nim + var y = if x > 8: 9 else: 10 + +An if expression always results in a value, so the ``else`` part is +required. ``Elif`` parts are also allowed. + +When expression +--------------- + +Just like an `if expression`, but corresponding to the when statement. + +Case expression +--------------- + +The `case expression` is again very similar to the case statement: + +.. code-block:: nim + var favoriteFood = case animal + of "dog": "bones" + of "cat": "mice" + elif animal.endsWith"whale": "plankton" + else: + echo "I'm not sure what to serve, but everybody loves ice cream" + "ice cream" + +As seen in the above example, the case expression can also introduce side +effects. When multiple statements are given for a branch, Nim will use +the last expression as the result value, much like in an `expr` template. + +Table constructor +----------------- + +A table constructor is syntactic sugar for an array constructor: + +.. code-block:: nim + {"key1": "value1", "key2", "key3": "value2"} + + # is the same as: + [("key1", "value1"), ("key2", "value2"), ("key3", "value2")] + + +The empty table can be written ``{:}`` (in contrast to the empty set +which is ``{}``) which is thus another way to write as the empty array +constructor ``[]``. This slightly unusual way of supporting tables +has lots of advantages: + +* The order of the (key,value)-pairs is preserved, thus it is easy to + support ordered dicts with for example ``{key: val}.newOrderedTable``. +* A table literal can be put into a ``const`` section and the compiler + can easily put it into the executable's data section just like it can + for arrays and the generated data section requires a minimal amount + of memory. +* Every table implementation is treated equal syntactically. +* Apart from the minimal syntactic sugar the language core does not need to + know about tables. + + +Type conversions +---------------- +Syntactically a `type conversion` is like a procedure call, but a +type name replaces the procedure name. A type conversion is always +safe in the sense that a failure to convert a type to another +results in an exception (if it cannot be determined statically). + +Ordinary procs are often preferred over type conversions in Nim: For instance, +``$`` is the ``toString`` operator by convention and ``toFloat`` and ``toInt`` +can be used to convert from floating point to integer or vice versa. + + +Type casts +---------- +Example: + +.. code-block:: nim + cast[int](x) + +Type casts are a crude mechanism to interpret the bit pattern of +an expression as if it would be of another type. Type casts are +only needed for low-level programming and are inherently unsafe. + + +The addr operator +----------------- +The ``addr`` operator returns the address of an l-value. If the type of the +location is ``T``, the `addr` operator result is of the type ``ptr T``. An +address is always an untraced reference. Taking the address of an object that +resides on the stack is **unsafe**, as the pointer may live longer than the +object on the stack and can thus reference a non-existing object. One can get +the address of variables, but one can't use it on variables declared through +``let`` statements: + +.. code-block:: nim + + let t1 = "Hello" + var + t2 = t1 + t3 : pointer = addr(t2) + echo repr(addr(t2)) + # --> ref 0x7fff6b71b670 --> 0x10bb81050"Hello" + echo cast[ptr string](t3)[] + # --> Hello + # The following line doesn't compile: + echo repr(addr(t1)) + # Error: expression has no address + + +The unsafeAddr operator +----------------------- + +For easier interoperability with other compiled languages such as C, retrieving +the address of a ``let`` variable, a parameter or a ``for`` loop variable, the +``unsafeAddr`` operation can be used: + +.. code-block:: nim + + let myArray = [1, 2, 3] + foreignProcThatTakesAnAddr(unsafeAddr myArray) + + +Procedures +========== + +What most programming languages call `methods`:idx: or `functions`:idx: are +called `procedures`:idx: in Nim. A procedure +declaration consists of an identifier, zero or more formal parameters, a return +value type and a block of code. Formal parameters are declared as a list of +identifiers separated by either comma or semicolon. A parameter is given a type +by ``: typename``. The type applies to all parameters immediately before it, +until either the beginning of the parameter list, a semicolon separator or an +already typed parameter, is reached. The semicolon can be used to make +separation of types and subsequent identifiers more distinct. + +.. code-block:: nim + # Using only commas + proc foo(a, b: int, c, d: bool): int + + # Using semicolon for visual distinction + proc foo(a, b: int; c, d: bool): int + + # Will fail: a is untyped since ';' stops type propagation. + proc foo(a; b: int; c, d: bool): int + +A parameter may be declared with a default value which is used if the caller +does not provide a value for the argument. + +.. code-block:: nim + # b is optional with 47 as its default value + proc foo(a: int, b: int = 47): int + +Parameters can be declared mutable and so allow the proc to modify those +arguments, by using the type modifier `var`. + +.. code-block:: nim + # "returning" a value to the caller through the 2nd argument + # Notice that the function uses no actual return value at all (ie void) + proc foo(inp: int, outp: var int) = + outp = inp + 47 + +If the proc declaration has no body, it is a `forward`:idx: declaration. If the +proc returns a value, the procedure body can access an implicitly declared +variable named `result`:idx: that represents the return value. Procs can be +overloaded. The overloading resolution algorithm determines which proc is the +best match for the arguments. Example: + +.. code-block:: nim + + proc toLower(c: char): char = # toLower for characters + if c in {'A'..'Z'}: + result = chr(ord(c) + (ord('a') - ord('A'))) + else: + result = c + + proc toLower(s: string): string = # toLower for strings + result = newString(len(s)) + for i in 0..len(s) - 1: + result[i] = toLower(s[i]) # calls toLower for characters; no recursion! + +Calling a procedure can be done in many different ways: + +.. code-block:: nim + proc callme(x, y: int, s: string = "", c: char, b: bool = false) = ... + + # call with positional arguments # parameter bindings: + callme(0, 1, "abc", '\t', true) # (x=0, y=1, s="abc", c='\t', b=true) + # call with named and positional arguments: + callme(y=1, x=0, "abd", '\t') # (x=0, y=1, s="abd", c='\t', b=false) + # call with named arguments (order is not relevant): + callme(c='\t', y=1, x=0) # (x=0, y=1, s="", c='\t', b=false) + # call as a command statement: no () needed: + callme 0, 1, "abc", '\t' # (x=0, y=1, s="abc", c='\t', b=false) + +A procedure may call itself recursively. + + +`Operators`:idx: are procedures with a special operator symbol as identifier: + +.. code-block:: nim + proc `$` (x: int): string = + # converts an integer to a string; this is a prefix operator. + result = intToStr(x) + +Operators with one parameter are prefix operators, operators with two +parameters are infix operators. (However, the parser distinguishes these from +the operator's position within an expression.) There is no way to declare +postfix operators: all postfix operators are built-in and handled by the +grammar explicitly. + +Any operator can be called like an ordinary proc with the '`opr`' +notation. (Thus an operator can have more than two parameters): + +.. code-block:: nim + proc `*+` (a, b, c: int): int = + # Multiply and add + result = a * b + c + + assert `*+`(3, 4, 6) == `+`(`*`(a, b), c) + + +Export marker +------------- + +If a declared symbol is marked with an `asterisk`:idx: it is exported from the +current module: + +.. code-block:: nim + + proc exportedEcho*(s: string) = echo s + proc `*`*(a: string; b: int): string = + result = newStringOfCap(a.len * b) + for i in 1..b: result.add a + + var exportedVar*: int + const exportedConst* = 78 + type + ExportedType* = object + exportedField*: int + + +Method call syntax +------------------ + +For object oriented programming, the syntax ``obj.method(args)`` can be used +instead of ``method(obj, args)``. The parentheses can be omitted if there are no +remaining arguments: ``obj.len`` (instead of ``len(obj)``). + +This method call syntax is not restricted to objects, it can be used +to supply any type of first argument for procedures: + +.. code-block:: nim + + echo "abc".len # is the same as echo len "abc" + echo "abc".toUpper() + echo {'a', 'b', 'c'}.card + stdout.writeLine("Hallo") # the same as writeLine(stdout, "Hallo") + +Another way to look at the method call syntax is that it provides the missing +postfix notation. + +The method call syntax conflicts with explicit generic instantiations: +``p[T](x)`` cannot be written as ``x.p[T]`` because ``x.p[T]`` is always +parsed as ``(x.p)[T]``. + +See also: `Limitations of the method call syntax +<#templates-limitations-of-the-method-call-syntax>`_. + +The ``[: ]`` notation has been designed to mitigate this issue: ``x.p[:T]`` +is rewritten by the parser to ``p[T](x)``, ``x.p[:T](y)`` is rewritten to +``p[T](x, y)``. Note that ``[: ]`` has no AST representation, the rewrite +is performed directly in the parsing step. + + +Properties +---------- +Nim has no need for *get-properties*: Ordinary get-procedures that are called +with the *method call syntax* achieve the same. But setting a value is +different; for this a special setter syntax is needed: + +.. code-block:: nim + # Module asocket + type + Socket* = ref object of RootObj + host: int # cannot be accessed from the outside of the module + + proc `host=`*(s: var Socket, value: int) {.inline.} = + ## setter of hostAddr. + ## This accesses the 'host' field and is not a recursive call to + ## ``host=`` because the builtin dot access is preferred if it is + ## avaliable: + s.host = value + + proc host*(s: Socket): int {.inline.} = + ## getter of hostAddr + ## This accesses the 'host' field and is not a recursive call to + ## ``host`` because the builtin dot access is preferred if it is + ## avaliable: + s.host + +.. code-block:: nim + # module B + import asocket + var s: Socket + new s + s.host = 34 # same as `host=`(s, 34) + + +Command invocation syntax +------------------------- + +Routines can be invoked without the ``()`` if the call is syntactically +a statement. This command invocation syntax also works for +expressions, but then only a single argument may follow. This restriction +means ``echo f 1, f 2`` is parsed as ``echo(f(1), f(2))`` and not as +``echo(f(1, f(2)))``. The method call syntax may be used to provide one +more argument in this case: + +.. code-block:: nim + proc optarg(x: int, y: int = 0): int = x + y + proc singlearg(x: int): int = 20*x + + echo optarg 1, " ", singlearg 2 # prints "1 40" + + let fail = optarg 1, optarg 8 # Wrong. Too many arguments for a command call + let x = optarg(1, optarg 8) # traditional procedure call with 2 arguments + let y = 1.optarg optarg 8 # same thing as above, w/o the parenthesis + assert x == y + +The command invocation syntax also can't have complex expressions as arguments. +For example: (`anonymous procs`_), ``if``, ``case`` or ``try``. The (`do +notation`_) is limited, but usable for a single proc (see the example in the +corresponding section). Function calls with no arguments still needs () to +distinguish between a call and the function itself as a first class value. + + +Closures +-------- + +Procedures can appear at the top level in a module as well as inside other +scopes, in which case they are called nested procs. A nested proc can access +local variables from its enclosing scope and if it does so it becomes a +closure. Any captured variables are stored in a hidden additional argument +to the closure (its environment) and they are accessed by reference by both +the closure and its enclosing scope (i.e. any modifications made to them are +visible in both places). The closure environment may be allocated on the heap +or on the stack if the compiler determines that this would be safe. + +Creating closures in loops +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since closures capture local variables by reference it is often not wanted +behavior inside loop bodies. See `closureScope <system.html#closureScope>`_ +for details on how to change this behavior. + +Anonymous Procs +--------------- + +Procs can also be treated as expressions, in which case it's allowed to omit +the proc's name. + +.. code-block:: nim + var cities = @["Frankfurt", "Tokyo", "New York", "Kyiv"] + + cities.sort(proc (x,y: string): int = + cmp(x.len, y.len)) + + +Procs as expressions can appear both as nested procs and inside top level +executable code. + + +Func +---- + +The ``func`` keyword introduces a shortcut for a `noSideEffect`:idx: proc. + +.. code-block:: nim + func binarySearch[T](a: openArray[T]; elem: T): int + +Is short for: + +.. code-block:: nim + proc binarySearch[T](a: openArray[T]; elem: T): int {.noSideEffect.} + + +Do notation +----------- + +As a special more convenient notation, proc expressions involved in procedure +calls can use the ``do`` keyword: + +.. code-block:: nim + sort(cities) do (x,y: string) -> int: + cmp(x.len, y.len) + + # Less parenthesis using the method plus command syntax: + cities = cities.map do (x:string) -> string: + "City of " & x + + # In macros, the do notation is often used for quasi-quoting + macroResults.add quote do: + if not `ex`: + echo `info`, ": Check failed: ", `expString` + +``do`` is written after the parentheses enclosing the regular proc params. +The proc expression represented by the do block is appended to them. +In calls using the command syntax, the do block will bind to the immediately +preceeding expression, transforming it in a call. + +``do`` with parentheses is an anonymous ``proc``; however a ``do`` without +parentheses is just a block of code. The ``do`` notation can be used to +pass multiple blocks to a macro: + +.. code-block:: nim + macro performWithUndo(task, undo: untyped) = ... + + performWithUndo do: + # multiple-line block of code + # to perform the task + do: + # code to undo it + + +Nonoverloadable builtins +------------------------ + +The following builtin procs cannot be overloaded for reasons of implementation +simplicity (they require specialized semantic checking):: + + declared, defined, definedInScope, compiles, sizeOf, + is, shallowCopy, getAst, astToStr, spawn, procCall + +Thus they act more like keywords than like ordinary identifiers; unlike a +keyword however, a redefinition may `shadow`:idx: the definition in +the ``system`` module. From this list the following should not be written in dot +notation ``x.f`` since ``x`` cannot be type checked before it gets passed +to ``f``:: + + declared, defined, definedInScope, compiles, getAst, astToStr + + +Var parameters +-------------- +The type of a parameter may be prefixed with the ``var`` keyword: + +.. code-block:: nim + proc divmod(a, b: int; res, remainder: var int) = + res = a div b + remainder = a mod b + + var + x, y: int + + divmod(8, 5, x, y) # modifies x and y + assert x == 1 + assert y == 3 + +In the example, ``res`` and ``remainder`` are `var parameters`. +Var parameters can be modified by the procedure and the changes are +visible to the caller. The argument passed to a var parameter has to be +an l-value. Var parameters are implemented as hidden pointers. The +above example is equivalent to: + +.. code-block:: nim + proc divmod(a, b: int; res, remainder: ptr int) = + res[] = a div b + remainder[] = a mod b + + var + x, y: int + divmod(8, 5, addr(x), addr(y)) + assert x == 1 + assert y == 3 + +In the examples, var parameters or pointers are used to provide two +return values. This can be done in a cleaner way by returning a tuple: + +.. code-block:: nim + proc divmod(a, b: int): tuple[res, remainder: int] = + (a div b, a mod b) + + var t = divmod(8, 5) + + assert t.res == 1 + assert t.remainder == 3 + +One can use `tuple unpacking`:idx: to access the tuple's fields: + +.. code-block:: nim + var (x, y) = divmod(8, 5) # tuple unpacking + assert x == 1 + assert y == 3 + + +**Note**: ``var`` parameters are never necessary for efficient parameter +passing. Since non-var parameters cannot be modified the compiler is always +free to pass arguments by reference if it considers it can speed up execution. + + +Var return type +--------------- + +A proc, converter or iterator may return a ``var`` type which means that the +returned value is an l-value and can be modified by the caller: + +.. code-block:: nim + var g = 0 + + proc writeAccessToG(): var int = + result = g + + writeAccessToG() = 6 + assert g == 6 + +It is a compile time error if the implicitly introduced pointer could be +used to access a location beyond its lifetime: + +.. code-block:: nim + proc writeAccessToG(): var int = + var g = 0 + result = g # Error! + +For iterators, a component of a tuple return type can have a ``var`` type too: + +.. code-block:: nim + iterator mpairs(a: var seq[string]): tuple[key: int, val: var string] = + for i in 0..a.high: + yield (i, a[i]) + +In the standard library every name of a routine that returns a ``var`` type +starts with the prefix ``m`` per convention. + + +.. include:: manual/var_t_return.rst + +Future directions +~~~~~~~~~~~~~~~~~ + +Later versions of Nim can be more precise about the borrowing rule with +a syntax like: + +.. code-block:: nim + proc foo(other: Y; container: var X): var T from container + +Here ``var T from container`` explicitly exposes that the +location is deviated from the second parameter (called +'container' in this case). The syntax ``var T from p`` specifies a type +``varTy[T, 2]`` which is incompatible with ``varTy[T, 1]``. + + + +Overloading of the subscript operator +------------------------------------- + +The ``[]`` subscript operator for arrays/openarrays/sequences can be overloaded. + + +Multi-methods +============= + +Procedures always use static dispatch. Multi-methods use dynamic +dispatch. For dynamic dispatch to work on an object it should be a reference +type as well. + +.. code-block:: nim + type + Expression = ref object of RootObj ## abstract base class for an expression + Literal = ref object of Expression + x: int + PlusExpr = ref object of Expression + a, b: Expression + + method eval(e: Expression): int {.base.} = + # override this base method + quit "to override!" + + method eval(e: Literal): int = return e.x + + method eval(e: PlusExpr): int = + # watch out: relies on dynamic binding + result = eval(e.a) + eval(e.b) + + proc newLit(x: int): Literal = + new(result) + result.x = x + + proc newPlus(a, b: Expression): PlusExpr = + new(result) + result.a = a + result.b = b + + echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4))) + +In the example the constructors ``newLit`` and ``newPlus`` are procs +because they should use static binding, but ``eval`` is a method because it +requires dynamic binding. + +As can be seen in the example, base methods have to be annotated with +the `base`:idx: pragma. The ``base`` pragma also acts as a reminder for the +programmer that a base method ``m`` is used as the foundation to determine all +the effects that a call to ``m`` might cause. + +In a multi-method all parameters that have an object type are used for the +dispatching: + +.. code-block:: nim + type + Thing = ref object of RootObj + Unit = ref object of Thing + x: int + + method collide(a, b: Thing) {.base, inline.} = + quit "to override!" + + method collide(a: Thing, b: Unit) {.inline.} = + echo "1" + + method collide(a: Unit, b: Thing) {.inline.} = + echo "2" + + var a, b: Unit + new a + new b + collide(a, b) # output: 2 + + +Invocation of a multi-method cannot be ambiguous: collide 2 is preferred over +collide 1 because the resolution works from left to right. +In the example ``Unit, Thing`` is preferred over ``Thing, Unit``. + +**Performance note**: Nim does not produce a virtual method table, but +generates dispatch trees. This avoids the expensive indirect branch for method +calls and enables inlining. However, other optimizations like compile time +evaluation or dead code elimination do not work with methods. + + +Iterators and the for statement +=============================== + +The `for`:idx: statement is an abstract mechanism to iterate over the elements +of a container. It relies on an `iterator`:idx: to do so. Like ``while`` +statements, ``for`` statements open an `implicit block`:idx:, so that they +can be left with a ``break`` statement. + +The ``for`` loop declares iteration variables - their scope reaches until the +end of the loop body. The iteration variables' types are inferred by the +return type of the iterator. + +An iterator is similar to a procedure, except that it can be called in the +context of a ``for`` loop. Iterators provide a way to specify the iteration over +an abstract type. A key role in the execution of a ``for`` loop plays the +``yield`` statement in the called iterator. Whenever a ``yield`` statement is +reached the data is bound to the ``for`` loop variables and control continues +in the body of the ``for`` loop. The iterator's local variables and execution +state are automatically saved between calls. Example: + +.. code-block:: nim + # this definition exists in the system module + iterator items*(a: string): char {.inline.} = + var i = 0 + while i < len(a): + yield a[i] + inc(i) + + for ch in items("hello world"): # `ch` is an iteration variable + echo ch + +The compiler generates code as if the programmer would have written this: + +.. code-block:: nim + var i = 0 + while i < len(a): + var ch = a[i] + echo ch + inc(i) + +If the iterator yields a tuple, there can be as many iteration variables +as there are components in the tuple. The i'th iteration variable's type is +the type of the i'th component. In other words, implicit tuple unpacking in a +for loop context is supported. + +Implict items/pairs invocations +------------------------------- + +If the for loop expression ``e`` does not denote an iterator and the for loop +has exactly 1 variable, the for loop expression is rewritten to ``items(e)``; +ie. an ``items`` iterator is implicitly invoked: + +.. code-block:: nim + for x in [1,2,3]: echo x + +If the for loop has exactly 2 variables, a ``pairs`` iterator is implicitly +invoked. + +Symbol lookup of the identifiers ``items``/``pairs`` is performed after +the rewriting step, so that all overloads of ``items``/``pairs`` are taken +into account. + + +First class iterators +--------------------- + +There are 2 kinds of iterators in Nim: *inline* and *closure* iterators. +An `inline iterator`:idx: is an iterator that's always inlined by the compiler +leading to zero overhead for the abstraction, but may result in a heavy +increase in code size. Inline iterators are second class citizens; +They can be passed as parameters only to other inlining code facilities like +templates, macros and other inline iterators. + +In contrast to that, a `closure iterator`:idx: can be passed around more freely: + +.. code-block:: nim + iterator count0(): int {.closure.} = + yield 0 + + iterator count2(): int {.closure.} = + var x = 1 + yield x + inc x + yield x + + proc invoke(iter: iterator(): int {.closure.}) = + for x in iter(): echo x + + invoke(count0) + invoke(count2) + +Closure iterators have other restrictions than inline iterators: + +1. ``yield`` in a closure iterator can not occur in a ``try`` statement. +2. For now, a closure iterator cannot be evaluated at compile time. +3. ``return`` is allowed in a closure iterator (but rarely useful) and ends + iteration. +4. Neither inline nor closure iterators can be recursive. +5. Closure iterators are not supported by the js backend. + +Iterators that are neither marked ``{.closure.}`` nor ``{.inline.}`` explicitly +default to being inline, but this may change in future versions of the +implementation. + +The ``iterator`` type is always of the calling convention ``closure`` +implicitly; the following example shows how to use iterators to implement +a `collaborative tasking`:idx: system: + +.. code-block:: nim + # simple tasking: + type + Task = iterator (ticker: int) + + iterator a1(ticker: int) {.closure.} = + echo "a1: A" + yield + echo "a1: B" + yield + echo "a1: C" + yield + echo "a1: D" + + iterator a2(ticker: int) {.closure.} = + echo "a2: A" + yield + echo "a2: B" + yield + echo "a2: C" + + proc runTasks(t: varargs[Task]) = + var ticker = 0 + while true: + let x = t[ticker mod t.len] + if finished(x): break + x(ticker) + inc ticker + + runTasks(a1, a2) + +The builtin ``system.finished`` can be used to determine if an iterator has +finished its operation; no exception is raised on an attempt to invoke an +iterator that has already finished its work. + +Note that ``system.finished`` is error prone to use because it only returns +``true`` one iteration after the iterator has finished: + +.. code-block:: nim + iterator mycount(a, b: int): int {.closure.} = + var x = a + while x <= b: + yield x + inc x + + var c = mycount # instantiate the iterator + while not finished(c): + echo c(1, 3) + + # Produces + 1 + 2 + 3 + 0 + +Instead this code has to be used: + +.. code-block:: nim + var c = mycount # instantiate the iterator + while true: + let value = c(1, 3) + if finished(c): break # and discard 'value'! + echo value + +It helps to think that the iterator actually returns a +pair ``(value, done)`` and ``finished`` is used to access the hidden ``done`` +field. + + +Closure iterators are *resumable functions* and so one has to provide the +arguments to every call. To get around this limitation one can capture +parameters of an outer factory proc: + +.. code-block:: nim + proc mycount(a, b: int): iterator (): int = + result = iterator (): int = + var x = a + while x <= b: + yield x + inc x + + let foo = mycount(1, 4) + + for f in foo(): + echo f + +.. + Implicit return type + -------------------- + + Since inline iterators must always produce values that will be consumed in + a for loop, the compiler will implicitly use the ``auto`` return type if no + type is given by the user. In contrast, since closure iterators can be used + as a collaborative tasking system, ``void`` is a valid return type for them. + + +Converters +========== + +A converter is like an ordinary proc except that it enhances +the "implicitly convertible" type relation (see `Convertible relation`_): + +.. code-block:: nim + # bad style ahead: Nim is not C. + converter toBool(x: int): bool = x != 0 + + if 4: + echo "compiles" + + +A converter can also be explicitly invoked for improved readability. Note that +implicit converter chaining is not supported: If there is a converter from +type A to type B and from type B to type C the implicit conversion from A to C +is not provided. + + +Type sections +============= + +Example: + +.. code-block:: nim + type # example demonstrating mutually recursive types + Node = ref object # an object managed by the garbage collector (ref) + le, ri: Node # left and right subtrees + sym: ref Sym # leaves contain a reference to a Sym + + Sym = object # a symbol + name: string # the symbol's name + line: int # the line the symbol was declared in + code: Node # the symbol's abstract syntax tree + +A type section begins with the ``type`` keyword. It contains multiple +type definitions. A type definition binds a type to a name. Type definitions +can be recursive or even mutually recursive. Mutually recursive types are only +possible within a single ``type`` section. Nominal types like ``objects`` +or ``enums`` can only be defined in a ``type`` section. + + + +Exception handling +================== + +Try statement +------------- + +Example: + +.. code-block:: nim + # read the first two lines of a text file that should contain numbers + # and tries to add them + var + f: File + if open(f, "numbers.txt"): + try: + var a = readLine(f) + var b = readLine(f) + echo "sum: " & $(parseInt(a) + parseInt(b)) + except OverflowError: + echo "overflow!" + except ValueError: + echo "could not convert string to integer" + except IOError: + echo "IO error!" + except: + echo "Unknown exception!" + finally: + close(f) + + +The statements after the ``try`` are executed in sequential order unless +an exception ``e`` is raised. If the exception type of ``e`` matches any +listed in an ``except`` clause the corresponding statements are executed. +The statements following the ``except`` clauses are called +`exception handlers`:idx:. + +The empty `except`:idx: clause is executed if there is an exception that is +not listed otherwise. It is similar to an ``else`` clause in ``if`` statements. + +If there is a `finally`:idx: clause, it is always executed after the +exception handlers. + +The exception is *consumed* in an exception handler. However, an +exception handler may raise another exception. If the exception is not +handled, it is propagated through the call stack. This means that often +the rest of the procedure - that is not within a ``finally`` clause - +is not executed (if an exception occurs). + + +Try expression +-------------- + +Try can also be used as an expression; the type of the ``try`` branch then +needs to fit the types of ``except`` branches, but the type of the ``finally`` +branch always has to be ``void``: + +.. code-block:: nim + let x = try: parseInt("133a") + except: -1 + finally: echo "hi" + + +To prevent confusing code there is a parsing limitation; if the ``try`` +follows a ``(`` it has to be written as a one liner: + +.. code-block:: nim + let x = (try: parseInt("133a") except: -1) + + +Except clauses +-------------- + +Within an ``except`` clause, it is possible to use +``getCurrentException`` to retrieve the exception that has been +raised: + +.. code-block:: nim + try: + # ... + except IOError: + let e = getCurrentException() + # Now use "e" + +Note that ``getCurrentException`` always returns a ``ref Exception`` +type. If a variable of the proper type is needed (in the example +above, ``IOError``), one must convert it explicitly: + +.. code-block:: nim + try: + # ... + except IOError: + let e = (ref IOError)(getCurrentException()) + # "e" is now of the proper type + +However, this is seldom needed. The most common case is to extract an +error message from ``e``, and for such situations it is enough to use +``getCurrentExceptionMsg``: + +.. code-block:: nim + try: + # ... + except IOError: + echo "I/O error: " & getCurrentExceptionMsg() + + +Defer statement +--------------- + +Instead of a ``try finally`` statement a ``defer`` statement can be used. + +Any statements following the ``defer`` in the current block will be considered +to be in an implicit try block: + +.. code-block:: nim + :test: "nim c $1" + + proc main = + var f = open("numbers.txt") + defer: close(f) + f.write "abc" + f.write "def" + +Is rewritten to: + +.. code-block:: nim + :test: "nim c $1" + + proc main = + var f = open("numbers.txt") + try: + f.write "abc" + f.write "def" + finally: + close(f) + +Top level ``defer`` statements are not supported +since it's unclear what such a statement should refer to. + + +Raise statement +--------------- + +Example: + +.. code-block:: nim + raise newEOS("operating system failed") + +Apart from built-in operations like array indexing, memory allocation, etc. +the ``raise`` statement is the only way to raise an exception. + +.. XXX document this better! + +If no exception name is given, the current exception is `re-raised`:idx:. The +`ReraiseError`:idx: exception is raised if there is no exception to +re-raise. It follows that the ``raise`` statement *always* raises an +exception. + + +Exception hierarchy +------------------- + +The exception tree is defined in the `system <system.html>`_ module. +Every exception inherits from ``system.Exception``. Exceptions that indicate +programming bugs inherit from ``system.Defect`` (which is a subtype of ``Exception``) +and are stricly speaking not catchable as they can also be mapped to an operation +that terminates the whole process. Exceptions that indicate any other runtime error +that can be caught inherit from ``system.CatchableError`` +(which is a subtype of ``Exception``). + + +Imported exceptions +------------------- + +It is possible to raise/catch imported C++ exceptions. Types imported using +`importcpp` can be raised or caught. Exceptions are raised by value and +caught by reference. Example: + +.. code-block:: nim + + type + std_exception {.importcpp: "std::exception", header: "<exception>".} = object + + proc what(s: std_exception): cstring {.importcpp: "((char *)#.what())".} + + try: + raise std_exception() + except std_exception as ex: + echo ex.what() + + + +Effect system +============= + +Exception tracking +------------------ + +Nim supports exception tracking. The `raises`:idx: pragma can be used +to explicitly define which exceptions a proc/iterator/method/converter is +allowed to raise. The compiler verifies this: + +.. code-block:: nim + :test: "nim c $1" + + proc p(what: bool) {.raises: [IOError, OSError].} = + if what: raise newException(IOError, "IO") + else: raise newException(OSError, "OS") + +An empty ``raises`` list (``raises: []``) means that no exception may be raised: + +.. code-block:: nim + proc p(): bool {.raises: [].} = + try: + unsafeCall() + result = true + except: + result = false + + +A ``raises`` list can also be attached to a proc type. This affects type +compatibility: + +.. code-block:: nim + :test: "nim c $1" + :status: 1 + + type + Callback = proc (s: string) {.raises: [IOError].} + var + c: Callback + + proc p(x: string) = + raise newException(OSError, "OS") + + c = p # type error + + +For a routine ``p`` the compiler uses inference rules to determine the set of +possibly raised exceptions; the algorithm operates on ``p``'s call graph: + +1. Every indirect call via some proc type ``T`` is assumed to + raise ``system.Exception`` (the base type of the exception hierarchy) and + thus any exception unless ``T`` has an explicit ``raises`` list. + However if the call is of the form ``f(...)`` where ``f`` is a parameter + of the currently analysed routine it is ignored. The call is optimistically + assumed to have no effect. Rule 2 compensates for this case. +2. Every expression of some proc type within a call that is not a call + itself (and not nil) is assumed to be called indirectly somehow and thus + its raises list is added to ``p``'s raises list. +3. Every call to a proc ``q`` which has an unknown body (due to a forward + declaration or an ``importc`` pragma) is assumed to + raise ``system.Exception`` unless ``q`` has an explicit ``raises`` list. +4. Every call to a method ``m`` is assumed to + raise ``system.Exception`` unless ``m`` has an explicit ``raises`` list. +5. For every other call the analysis can determine an exact ``raises`` list. +6. For determining a ``raises`` list, the ``raise`` and ``try`` statements + of ``p`` are taken into consideration. + +Rules 1-2 ensure the following works: + +.. code-block:: nim + proc noRaise(x: proc()) {.raises: [].} = + # unknown call that might raise anything, but valid: + x() + + proc doRaise() {.raises: [IOError].} = + raise newException(IOError, "IO") + + proc use() {.raises: [].} = + # doesn't compile! Can raise IOError! + noRaise(doRaise) + +So in many cases a callback does not cause the compiler to be overly +conservative in its effect analysis. + + +Tag tracking +------------ + +The exception tracking is part of Nim's `effect system`:idx:. Raising an +exception is an *effect*. Other effects can also be defined. A user defined +effect is a means to *tag* a routine and to perform checks against this tag: + +.. code-block:: nim + :test: "nim c $1" + :status: 1 + + type IO = object ## input/output effect + proc readLine(): string {.tags: [IO].} = discard + + proc no_IO_please() {.tags: [].} = + # the compiler prevents this: + let x = readLine() + +A tag has to be a type name. A ``tags`` list - like a ``raises`` list - can +also be attached to a proc type. This affects type compatibility. + +The inference for tag tracking is analogous to the inference for +exception tracking. + + +Read/Write tracking +------------------- + +**Note**: Read/write tracking is not yet implemented! + +The inference for read/write tracking is analogous to the inference for +exception tracking. + + +Effects pragma +-------------- + +The ``effects`` pragma has been designed to assist the programmer with the +effects analysis. It is a statement that makes the compiler output all inferred +effects up to the ``effects``'s position: + +.. code-block:: nim + proc p(what: bool) = + if what: + raise newException(IOError, "IO") + {.effects.} + else: + raise newException(OSError, "OS") + +The compiler produces a hint message that ``IOError`` can be raised. ``OSError`` +is not listed as it cannot be raised in the branch the ``effects`` pragma +appears in. + + +Generics +======== + +Generics are Nim's means to parametrize procs, iterators or types with +`type parameters`:idx:. Depending on context, the brackets are used either to +introduce type parameters or to instantiate a generic proc, iterator or type. + +The following example shows a generic binary tree can be modelled: + +.. code-block:: nim + :test: "nim c $1" + + type + BinaryTree*[T] = ref object # BinaryTree is a generic type with + # generic param ``T`` + le, ri: BinaryTree[T] # left and right subtrees; may be nil + data: T # the data stored in a node + + proc newNode*[T](data: T): BinaryTree[T] = + # constructor for a node + result = BinaryTree[T](le: nil, ri: nil, data: data) + + proc add*[T](root: var BinaryTree[T], n: BinaryTree[T]) = + # insert a node into the tree + if root == nil: + root = n + else: + var it = root + while it != nil: + # compare the data items; uses the generic ``cmp`` proc + # that works for any type that has a ``==`` and ``<`` operator + var c = cmp(it.data, n.data) + if c < 0: + if it.le == nil: + it.le = n + return + it = it.le + else: + if it.ri == nil: + it.ri = n + return + it = it.ri + + proc add*[T](root: var BinaryTree[T], data: T) = + # convenience proc: + add(root, newNode(data)) + + iterator preorder*[T](root: BinaryTree[T]): T = + # Preorder traversal of a binary tree. + # Since recursive iterators are not yet implemented, + # this uses an explicit stack (which is more efficient anyway): + var stack: seq[BinaryTree[T]] = @[root] + while stack.len > 0: + var n = stack.pop() + while n != nil: + yield n.data + add(stack, n.ri) # push right subtree onto the stack + n = n.le # and follow the left pointer + + var + root: BinaryTree[string] # instantiate a BinaryTree with ``string`` + add(root, newNode("hello")) # instantiates ``newNode`` and ``add`` + add(root, "world") # instantiates the second ``add`` proc + for str in preorder(root): + stdout.writeLine(str) + +The ``T`` is called a `generic type parameter`:idx: or +a `type variable`:idx:. + + +Is operator +----------- + +The ``is`` operator checks for type equivalence at compile time. It is +therefore very useful for type specialization within generic code: + +.. code-block:: nim + type + Table[Key, Value] = object + keys: seq[Key] + values: seq[Value] + when not (Key is string): # empty value for strings used for optimization + deletedKeys: seq[bool] + + +Type Classes +------------ + +A type class is a special pseudo-type that can be used to match against +types in the context of overload resolution or the ``is`` operator. +Nim supports the following built-in type classes: + +================== =================================================== +type class matches +================== =================================================== +``object`` any object type +``tuple`` any tuple type + +``enum`` any enumeration +``proc`` any proc type +``ref`` any ``ref`` type +``ptr`` any ``ptr`` type +``var`` any ``var`` type +``distinct`` any distinct type +``array`` any array type +``set`` any set type +``seq`` any seq type +``auto`` any type +``any`` distinct auto (see below) +================== =================================================== + +Furthermore, every generic type automatically creates a type class of the same +name that will match any instantiation of the generic type. + +Type classes can be combined using the standard boolean operators to form +more complex type classes: + +.. code-block:: nim + # create a type class that will match all tuple and object types + type RecordType = tuple or object + + proc printFields(rec: RecordType) = + for key, value in fieldPairs(rec): + echo key, " = ", value + +Procedures utilizing type classes in such manner are considered to be +`implicitly generic`:idx:. They will be instantiated once for each unique +combination of param types used within the program. + +Nim also allows for type classes and regular types to be specified +as `type constraints`:idx: of the generic type parameter: + +.. code-block:: nim + proc onlyIntOrString[T: int|string](x, y: T) = discard + + onlyIntOrString(450, 616) # valid + onlyIntOrString(5.0, 0.0) # type mismatch + onlyIntOrString("xy", 50) # invalid as 'T' cannot be both at the same time + +By default, during overload resolution each named type class will bind to +exactly one concrete type. We call such type classes `bind once`:idx: types. +Here is an example taken directly from the system module to illustrate this: + +.. code-block:: nim + proc `==`*(x, y: tuple): bool = + ## requires `x` and `y` to be of the same tuple type + ## generic ``==`` operator for tuples that is lifted from the components + ## of `x` and `y`. + result = true + for a, b in fields(x, y): + if a != b: result = false + +Alternatively, the ``distinct`` type modifier can be applied to the type class +to allow each param matching the type class to bind to a different type. Such +type classes are called `bind many`:idx: types. + +Procs written with the implicitly generic style will often need to refer to the +type parameters of the matched generic type. They can be easily accessed using +the dot syntax: + +.. code-block:: nim + type Matrix[T, Rows, Columns] = object + ... + + proc `[]`(m: Matrix, row, col: int): Matrix.T = + m.data[col * high(Matrix.Columns) + row] + +Alternatively, the `type` operator can be used over the proc params for similar +effect when anonymous or distinct type classes are used. + +When a generic type is instantiated with a type class instead of a concrete +type, this results in another more specific type class: + +.. code-block:: nim + seq[ref object] # Any sequence storing references to any object type + + type T1 = auto + proc foo(s: seq[T1], e: T1) + # seq[T1] is the same as just `seq`, but T1 will be allowed to bind + # to a single type, while the signature is being matched + + Matrix[Ordinal] # Any Matrix instantiation using integer values + +As seen in the previous example, in such instantiations, it's not necessary to +supply all type parameters of the generic type, because any missing ones will +be inferred to have the equivalent of the `any` type class and thus they will +match anything without discrimination. + + +Generic inference restrictions +------------------------------ + +The types ``var T`` and ``typedesc[T]`` cannot be inferred in a generic +instantiation. The following is not allowed: + +.. code-block:: nim + :test: "nim c $1" + :status: 1 + + proc g[T](f: proc(x: T); x: T) = + f(x) + + proc c(y: int) = echo y + proc v(y: var int) = + y += 100 + var i: int + + # allowed: infers 'T' to be of type 'int' + g(c, 42) + + # not valid: 'T' is not inferred to be of type 'var int' + g(v, i) + + # also not allowed: explict instantiation via 'var int' + g[var int](v, i) + + +Concepts +-------- + +**Note**: Concepts are still in development. + +Concepts, also known as "user-defined type classes", are used to specify an +arbitrary set of requirements that the matched type must satisfy. + +Concepts are written in the following form: + +.. code-block:: nim + type + Comparable = concept x, y + (x < y) is bool + + Stack[T] = concept s, var v + s.pop() is T + v.push(T) + + s.len is Ordinal + + for value in s: + value is T + +The concept is a match if: + +a) all of the expressions within the body can be compiled for the tested type +b) all statically evaluable boolean expressions in the body must be true + +The identifiers following the ``concept`` keyword represent instances of the +currently matched type. You can apply any of the standard type modifiers such +as ``var``, ``ref``, ``ptr`` and ``static`` to denote a more specific type of +instance. You can also apply the `type` modifier to create a named instance of +the type itself: + +.. code-block:: nim + type + MyConcept = concept x, var v, ref r, ptr p, static s, type T + ... + +Within the concept body, types can appear in positions where ordinary values +and parameters are expected. This provides a more convenient way to check for +the presence of callable symbols with specific signatures: + +.. code-block:: nim + type + OutputStream = concept var s + s.write(string) + +In order to check for symbols accepting ``type`` params, you must prefix +the type with the explicit ``type`` modifier. The named instance of the +type, following the ``concept`` keyword is also considered to have the +explicit modifier and will be matched only as a type. + +.. code-block:: nim + type + # Let's imagine a user-defined casting framework with operators + # such as `val.to(string)` and `val.to(JSonValue)`. We can test + # for these with the following concept: + MyCastables = concept x + x.to(type string) + x.to(type JSonValue) + + # Let's define a couple of concepts, known from Algebra: + AdditiveMonoid* = concept x, y, type T + x + y is T + T.zero is T # require a proc such as `int.zero` or 'Position.zero' + + AdditiveGroup* = concept x, y, type T + x is AdditiveMonoid + -x is T + x - y is T + +Please note that the ``is`` operator allows one to easily verify the precise +type signatures of the required operations, but since type inference and +default parameters are still applied in the concept body, it's also possible +to describe usage protocols that do not reveal implementation details. + +Much like generics, concepts are instantiated exactly once for each tested type +and any static code included within the body is executed only once. + + +Concept diagnostics +------------------- + +By default, the compiler will report the matching errors in concepts only when +no other overload can be selected and a normal compilation error is produced. +When you need to understand why the compiler is not matching a particular +concept and, as a result, a wrong overload is selected, you can apply the +``explain`` pragma to either the concept body or a particular call-site. + +.. code-block:: nim + type + MyConcept {.explain.} = concept ... + + overloadedProc(x, y, z) {.explain.} + +This will provide Hints in the compiler output either every time the concept is +not matched or only on the particular call-site. + + +Generic concepts and type binding rules +--------------------------------------- + +The concept types can be parametric just like the regular generic types: + +.. code-block:: nim + ### matrixalgo.nim + + import typetraits + + type + AnyMatrix*[R, C: static int; T] = concept m, var mvar, type M + M.ValueType is T + M.Rows == R + M.Cols == C + + m[int, int] is T + mvar[int, int] = T + + type TransposedType = stripGenericParams(M)[C, R, T] + + AnySquareMatrix*[N: static int, T] = AnyMatrix[N, N, T] + + AnyTransform3D* = AnyMatrix[4, 4, float] + + proc transposed*(m: AnyMatrix): m.TransposedType = + for r in 0 ..< m.R: + for c in 0 ..< m.C: + result[r, c] = m[c, r] + + proc determinant*(m: AnySquareMatrix): int = + ... + + proc setPerspectiveProjection*(m: AnyTransform3D) = + ... + + -------------- + ### matrix.nim + + type + Matrix*[M, N: static int; T] = object + data: array[M*N, T] + + proc `[]`*(M: Matrix; m, n: int): M.T = + M.data[m * M.N + n] + + proc `[]=`*(M: var Matrix; m, n: int; v: M.T) = + M.data[m * M.N + n] = v + + # Adapt the Matrix type to the concept's requirements + template Rows*(M: type Matrix): expr = M.M + template Cols*(M: type Matrix): expr = M.N + template ValueType*(M: type Matrix): type = M.T + + ------------- + ### usage.nim + + import matrix, matrixalgo + + var + m: Matrix[3, 3, int] + projectionMatrix: Matrix[4, 4, float] + + echo m.transposed.determinant + setPerspectiveProjection projectionMatrix + +When the concept type is matched against a concrete type, the unbound type +parameters are inferred from the body of the concept in a way that closely +resembles the way generic parameters of callable symbols are inferred on +call sites. + +Unbound types can appear both as params to calls such as `s.push(T)` and +on the right-hand side of the ``is`` operator in cases such as `x.pop is T` +and `x.data is seq[T]`. + +Unbound static params will be inferred from expressions involving the `==` +operator and also when types dependent on them are being matched: + +.. code-block:: nim + type + MatrixReducer[M, N: static int; T] = concept x + x.reduce(SquareMatrix[N, T]) is array[M, int] + +The Nim compiler includes a simple linear equation solver, allowing it to +infer static params in some situations where integer arithmetic is involved. + +Just like in regular type classes, Nim discriminates between ``bind once`` +and ``bind many`` types when matching the concept. You can add the ``distinct`` +modifier to any of the otherwise inferable types to get a type that will be +matched without permanently inferring it. This may be useful when you need +to match several procs accepting the same wide class of types: + +.. code-block:: nim + type + Enumerable[T] = concept e + for v in e: + v is T + + type + MyConcept = concept o + # this could be inferred to a type such as Enumerable[int] + o.foo is distinct Enumerable + + # this could be inferred to a different type such as Enumerable[float] + o.bar is distinct Enumerable + + # it's also possible to give an alias name to a `bind many` type class + type Enum = distinct Enumerable + o.baz is Enum + +On the other hand, using ``bind once`` types allows you to test for equivalent +types used in multiple signatures, without actually requiring any concrete +types, thus allowing you to encode implementation-defined types: + +.. code-block:: nim + type + MyConcept = concept x + type T1 = auto + x.foo(T1) + x.bar(T1) # both procs must accept the same type + + type T2 = seq[SomeNumber] + x.alpha(T2) + x.omega(T2) # both procs must accept the same type + # and it must be a numeric sequence + +As seen in the previous examples, you can refer to generic concepts such as +`Enumerable[T]` just by their short name. Much like the regular generic types, +the concept will be automatically instantiated with the bind once auto type +in the place of each missing generic param. + +Please note that generic concepts such as `Enumerable[T]` can be matched +against concrete types such as `string`. Nim doesn't require the concept +type to have the same number of parameters as the type being matched. +If you wish to express a requirement towards the generic parameters of +the matched type, you can use a type mapping operator such as `genericHead` +or `stripGenericParams` within the body of the concept to obtain the +uninstantiated version of the type, which you can then try to instantiate +in any required way. For example, here is how one might define the classic +`Functor` concept from Haskell and then demonstrate that Nim's `Option[T]` +type is an instance of it: + +.. code-block:: nim + :test: "nim c $1" + + import sugar, typetraits + + type + Functor[A] = concept f + type MatchedGenericType = genericHead(f.type) + # `f` will be a value of a type such as `Option[T]` + # `MatchedGenericType` will become the `Option` type + + f.val is A + # The Functor should provide a way to obtain + # a value stored inside it + + type T = auto + map(f, A -> T) is MatchedGenericType[T] + # And it should provide a way to map one instance of + # the Functor to a instance of a different type, given + # a suitable `map` operation for the enclosed values + + import options + echo Option[int] is Functor # prints true + + +Concept derived values +---------------------- + +All top level constants or types appearing within the concept body are +accessible through the dot operator in procs where the concept was successfully +matched to a concrete type: + +.. code-block:: nim + type + DateTime = concept t1, t2, type T + const Min = T.MinDate + T.Now is T + + t1 < t2 is bool + + type TimeSpan = type(t1 - t2) + TimeSpan * int is TimeSpan + TimeSpan + TimeSpan is TimeSpan + + t1 + TimeSpan is T + + proc eventsJitter(events: Enumerable[DateTime]): float = + var + # this variable will have the inferred TimeSpan type for + # the concrete Date-like value the proc was called with: + averageInterval: DateTime.TimeSpan + + deviation: float + ... + + +Concept refinement +------------------ + +When the matched type within a concept is directly tested against a different +concept, we say that the outer concept is a refinement of the inner concept and +thus it is more-specific. When both concepts are matched in a call during +overload resolution, Nim will assign a higher precedence to the most specific +one. As an alternative way of defining concept refinements, you can use the +object inheritance syntax involving the ``of`` keyword: + +.. code-block:: nim + type + Graph = concept g, type G of EqualyComparable, Copyable + type + VertexType = G.VertexType + EdgeType = G.EdgeType + + VertexType is Copyable + EdgeType is Copyable + + var + v: VertexType + e: EdgeType + + IncidendeGraph = concept of Graph + # symbols such as variables and types from the refined + # concept are automatically in scope: + + g.source(e) is VertexType + g.target(e) is VertexType + + g.outgoingEdges(v) is Enumerable[EdgeType] + + BidirectionalGraph = concept g, type G + # The following will also turn the concept into a refinement when it + # comes to overload resolution, but it doesn't provide the convenient + # symbol inheritance + g is IncidendeGraph + + g.incomingEdges(G.VertexType) is Enumerable[G.EdgeType] + + proc f(g: IncidendeGraph) + proc f(g: BidirectionalGraph) # this one will be preferred if we pass a type + # matching the BidirectionalGraph concept + +.. + Converter type classes + ---------------------- + + Concepts can also be used to convert a whole range of types to a single type or + a small set of simpler types. This is achieved with a `return` statement within + the concept body: + + .. code-block:: nim + type + Stringable = concept x + $x is string + return $x + + StringRefValue[CharType] = object + base: ptr CharType + len: int + + StringRef = concept x + # the following would be an overloaded proc for cstring, string, seq and + # other user-defined types, returning either a StringRefValue[char] or + # StringRefValue[wchar] + return makeStringRefValue(x) + + # the varargs param will here be converted to an array of StringRefValues + # the proc will have only two instantiations for the two character types + proc log(format: static string, varargs[StringRef]) + + # this proc will allow char and wchar values to be mixed in + # the same call at the cost of additional instantiations + # the varargs param will be converted to a tuple + proc log(format: static string, varargs[distinct StringRef]) + + +.. + VTable types + ------------ + + Concepts allow Nim to define a great number of algorithms, using only + static polymorphism and without erasing any type information or sacrificing + any execution speed. But when polymorphic collections of objects are required, + the user must use one of the provided type erasure techniques - either common + base types or VTable types. + + VTable types are represented as "fat pointers" storing a reference to an + object together with a reference to a table of procs implementing a set of + required operations (the so called vtable). + + In contrast to other programming languages, the vtable in Nim is stored + externally to the object, allowing you to create multiple different vtable + views for the same object. Thus, the polymorphism in Nim is unbounded - + any type can implement an unlimited number of protocols or interfaces not + originally envisioned by the type's author. + + Any concept type can be turned into a VTable type by using the ``vtref`` + or the ``vtptr`` compiler magics. Under the hood, these magics generate + a converter type class, which converts the regular instances of the matching + types to the corresponding VTable type. + + .. code-block:: nim + type + IntEnumerable = vtref Enumerable[int] + + MyObject = object + enumerables: seq[IntEnumerable] + streams: seq[OutputStream.vtref] + + proc addEnumerable(o: var MyObject, e: IntEnumerable) = + o.enumerables.add e + + proc addStream(o: var MyObject, e: OutputStream.vtref) = + o.streams.add e + + The procs that will be included in the vtable are derived from the concept + body and include all proc calls for which all param types were specified as + concrete types. All such calls should include exactly one param of the type + matched against the concept (not necessarily in the first position), which + will be considered the value bound to the vtable. + + Overloads will be created for all captured procs, accepting the vtable type + in the position of the captured underlying object. + + Under these rules, it's possible to obtain a vtable type for a concept with + unbound type parameters or one instantiated with metatypes (type classes), + but it will include a smaller number of captured procs. A completely empty + vtable will be reported as an error. + + The ``vtref`` magic produces types which can be bound to ``ref`` types and + the ``vtptr`` magic produced types bound to ``ptr`` types. + + +Symbol lookup in generics +------------------------- + +Open and Closed symbols +~~~~~~~~~~~~~~~~~~~~~~~ + +The symbol binding rules in generics are slightly subtle: There are "open" and +"closed" symbols. A "closed" symbol cannot be re-bound in the instantiation +context, an "open" symbol can. Per default overloaded symbols are open +and every other symbol is closed. + +Open symbols are looked up in two different contexts: Both the context +at definition and the context at instantiation are considered: + +.. code-block:: nim + :test: "nim c $1" + + type + Index = distinct int + + proc `==` (a, b: Index): bool {.borrow.} + + var a = (0, 0.Index) + var b = (0, 0.Index) + + echo a == b # works! + +In the example the generic ``==`` for tuples (as defined in the system module) +uses the ``==`` operators of the tuple's components. However, the ``==`` for +the ``Index`` type is defined *after* the ``==`` for tuples; yet the example +compiles as the instantiation takes the currently defined symbols into account +too. + +Mixin statement +--------------- + +A symbol can be forced to be open by a `mixin`:idx: declaration: + +.. code-block:: nim + :test: "nim c $1" + + proc create*[T](): ref T = + # there is no overloaded 'init' here, so we need to state that it's an + # open symbol explicitly: + mixin init + new result + init result + + +Bind statement +-------------- + +The ``bind`` statement is the counterpart to the ``mixin`` statement. It +can be used to explicitly declare identifiers that should be bound early (i.e. +the identifiers should be looked up in the scope of the template/generic +definition): + +.. code-block:: nim + # Module A + var + lastId = 0 + + template genId*: untyped = + bind lastId + inc(lastId) + lastId + +.. code-block:: nim + # Module B + import A + + echo genId() + +But a ``bind`` is rarely useful because symbol binding from the definition +scope is the default. + + + +Templates +========= + +A template is a simple form of a macro: It is a simple substitution +mechanism that operates on Nim's abstract syntax trees. It is processed in +the semantic pass of the compiler. + +The syntax to *invoke* a template is the same as calling a procedure. + +Example: + +.. code-block:: nim + template `!=` (a, b: untyped): untyped = + # this definition exists in the System module + not (a == b) + + assert(5 != 6) # the compiler rewrites that to: assert(not (5 == 6)) + +The ``!=``, ``>``, ``>=``, ``in``, ``notin``, ``isnot`` operators are in fact +templates: + +| ``a > b`` is transformed into ``b < a``. +| ``a in b`` is transformed into ``contains(b, a)``. +| ``notin`` and ``isnot`` have the obvious meanings. + +The "types" of templates can be the symbols ``untyped``, +``typed`` or ``type``. These are "meta types", they can only be used in certain +contexts. Regular types can be used too; this implies that ``typed`` expressions +are expected. + + +Typed vs untyped parameters +--------------------------- + +An ``untyped`` parameter means that symbol lookups and type resolution is not +performed before the expression is passed to the template. This means that for +example *undeclared* identifiers can be passed to the template: + +.. code-block:: nim + :test: "nim c $1" + + template declareInt(x: untyped) = + var x: int + + declareInt(x) # valid + x = 3 + + +.. code-block:: nim + :test: "nim c $1" + :status: 1 + + template declareInt(x: typed) = + var x: int + + declareInt(x) # invalid, because x has not been declared and so has no type + +A template where every parameter is ``untyped`` is called an `immediate`:idx: +template. For historical reasons templates can be explicitly annotated with +an ``immediate`` pragma and then these templates do not take part in +overloading resolution and the parameters' types are *ignored* by the +compiler. Explicit immediate templates are now deprecated. + +**Note**: For historical reasons ``stmt`` is an alias for ``typed`` and +``expr`` an alias for ``untyped``, but new code should use the newer, +clearer names. + + +Passing a code block to a template +---------------------------------- + +You can pass a block of statements as the last argument to a template +following the special ``:`` syntax: + +.. code-block:: nim + :test: "nim c $1" + + template withFile(f, fn, mode, actions: untyped): untyped = + var f: File + if open(f, fn, mode): + try: + actions + finally: + close(f) + else: + quit("cannot open: " & fn) + + withFile(txt, "ttempl3.txt", fmWrite): # special colon + txt.writeLine("line 1") + txt.writeLine("line 2") + +In the example, the two ``writeLine`` statements are bound to the ``actions`` +parameter. + + +Usually to pass a block of code to a template the parameter that accepts +the block needs to be of type ``untyped``. Because symbol lookups are then +delayed until template instantiation time: + +.. code-block:: nim + :test: "nim c $1" + :status: 1 + + template t(body: typed) = + block: + body + + t: + var i = 1 + echo i + + t: + var i = 2 # fails with 'attempt to redeclare i' + echo i + +The above code fails with the mysterious error message that ``i`` has already +been declared. The reason for this is that the ``var i = ...`` bodies need to +be type-checked before they are passed to the ``body`` parameter and type +checking in Nim implies symbol lookups. For the symbol lookups to succeed +``i`` needs to be added to the current (i.e. outer) scope. After type checking +these additions to the symbol table are not rolled back (for better or worse). +The same code works with ``untyped`` as the passed body is not required to be +type-checked: + +.. code-block:: nim + :test: "nim c $1" + + template t(body: untyped) = + block: + body + + t: + var i = 1 + echo i + + t: + var i = 2 # compiles + echo i + + +Varargs of untyped +------------------ + +In addition to the ``untyped`` meta-type that prevents type checking there is +also ``varargs[untyped]`` so that not even the number of parameters is fixed: + +.. code-block:: nim + :test: "nim c $1" + + template hideIdentifiers(x: varargs[untyped]) = discard + + hideIdentifiers(undeclared1, undeclared2) + +However, since a template cannot iterate over varargs, this feature is +generally much more useful for macros. + +**Note**: For historical reasons ``varargs[expr]`` is not equivalent +to ``varargs[untyped]``. + + +Symbol binding in templates +--------------------------- + +A template is a `hygienic`:idx: macro and so opens a new scope. Most symbols are +bound from the definition scope of the template: + +.. code-block:: nim + # Module A + var + lastId = 0 + + template genId*: untyped = + inc(lastId) + lastId + +.. code-block:: nim + # Module B + import A + + echo genId() # Works as 'lastId' has been bound in 'genId's defining scope + +As in generics symbol binding can be influenced via ``mixin`` or ``bind`` +statements. + + + +Identifier construction +----------------------- + +In templates identifiers can be constructed with the backticks notation: + +.. code-block:: nim + :test: "nim c $1" + + template typedef(name: untyped, typ: type) = + type + `T name`* {.inject.} = typ + `P name`* {.inject.} = ref `T name` + + typedef(myint, int) + var x: PMyInt + +In the example ``name`` is instantiated with ``myint``, so \`T name\` becomes +``Tmyint``. + + +Lookup rules for template parameters +------------------------------------ + +A parameter ``p`` in a template is even substituted in the expression ``x.p``. +Thus template arguments can be used as field names and a global symbol can be +shadowed by the same argument name even when fully qualified: + +.. code-block:: nim + # module 'm' + + type + Lev = enum + levA, levB + + var abclev = levB + + template tstLev(abclev: Lev) = + echo abclev, " ", m.abclev + + tstLev(levA) + # produces: 'levA levA' + +But the global symbol can properly be captured by a ``bind`` statement: + +.. code-block:: nim + # module 'm' + + type + Lev = enum + levA, levB + + var abclev = levB + + template tstLev(abclev: Lev) = + bind m.abclev + echo abclev, " ", m.abclev + + tstLev(levA) + # produces: 'levA levB' + + +Hygiene in templates +-------------------- + +Per default templates are `hygienic`:idx:\: Local identifiers declared in a +template cannot be accessed in the instantiation context: + +.. code-block:: nim + :test: "nim c $1" + + template newException*(exceptn: type, message: string): untyped = + var + e: ref exceptn # e is implicitly gensym'ed here + new(e) + e.msg = message + e + + # so this works: + let e = "message" + raise newException(IoError, e) + + +Whether a symbol that is declared in a template is exposed to the instantiation +scope is controlled by the `inject`:idx: and `gensym`:idx: pragmas: gensym'ed +symbols are not exposed but inject'ed are. + +The default for symbols of entity ``type``, ``var``, ``let`` and ``const`` +is ``gensym`` and for ``proc``, ``iterator``, ``converter``, ``template``, +``macro`` is ``inject``. However, if the name of the entity is passed as a +template parameter, it is an inject'ed symbol: + +.. code-block:: nim + template withFile(f, fn, mode: untyped, actions: untyped): untyped = + block: + var f: File # since 'f' is a template param, it's injected implicitly + ... + + withFile(txt, "ttempl3.txt", fmWrite): + txt.writeLine("line 1") + txt.writeLine("line 2") + + +The ``inject`` and ``gensym`` pragmas are second class annotations; they have +no semantics outside of a template definition and cannot be abstracted over: + +.. code-block:: nim + {.pragma myInject: inject.} + + template t() = + var x {.myInject.}: int # does NOT work + + +To get rid of hygiene in templates, one can use the `dirty`:idx: pragma for +a template. ``inject`` and ``gensym`` have no effect in ``dirty`` templates. + + + +Limitations of the method call syntax +------------------------------------- + +The expression ``x`` in ``x.f`` needs to be semantically checked (that means +symbol lookup and type checking) before it can be decided that it needs to be +rewritten to ``f(x)``. Therefore the dot syntax has some limitations when it +is used to invoke templates/macros: + +.. code-block:: nim + :test: "nim c $1" + :status: 1 + + template declareVar(name: untyped) = + const name {.inject.} = 45 + + # Doesn't compile: + unknownIdentifier.declareVar + + +Another common example is this: + +.. code-block:: nim + :test: "nim c $1" + :status: 1 + + from sequtils import toSeq + + iterator something: string = + yield "Hello" + yield "World" + + var info = something().toSeq + +The problem here is that the compiler already decided that ``something()`` as +an iterator is not callable in this context before ``toSeq`` gets its +chance to convert it into a sequence. + + +Macros +====== + +A macro is a special function that is executed at compile-time. +Normally the input for a macro is an abstract syntax +tree (AST) of the code that is passed to it. The macro can then do +transformations on it and return the transformed AST. The +transformed AST is then passed to the compiler as if the macro +invocation would have been replaced by its result in the source +code. This can be used to implement `domain specific +languages`:idx:. + +While macros enable advanced compile-time code transformations, they +cannot change Nim's syntax. However, this is no real restriction because +Nim's syntax is flexible enough anyway. + +To write macros, one needs to know how the Nim concrete syntax is converted +to an AST. + +There are two ways to invoke a macro: +(1) invoking a macro like a procedure call (`expression macros`) +(2) invoking a macro with the special ``macrostmt`` syntax (`statement macros`) + + +Expression Macros +----------------- + +The following example implements a powerful ``debug`` command that accepts a +variable number of arguments: + +.. code-block:: nim + :test: "nim c $1" + + # to work with Nim syntax trees, we need an API that is defined in the + # ``macros`` module: + import macros + + macro debug(args: varargs[untyped]): untyped = + # `args` is a collection of `NimNode` values that each contain the + # AST for an argument of the macro. A macro always has to + # return a `NimNode`. A node of kind `nnkStmtList` is suitable for + # this use case. + result = nnkStmtList.newTree() + # iterate over any argument that is passed to this macro: + for n in args: + # add a call to the statement list that writes the expression; + # `toStrLit` converts an AST to its string representation: + result.add newCall("write", newIdentNode("stdout"), newLit(n.repr)) + # add a call to the statement list that writes ": " + result.add newCall("write", newIdentNode("stdout"), newLit(": ")) + # add a call to the statement list that writes the expressions value: + result.add newCall("writeLine", newIdentNode("stdout"), n) + + var + a: array[0..10, int] + x = "some string" + a[0] = 42 + a[1] = 45 + + debug(a[0], a[1], x) + +The macro call expands to: + +.. code-block:: nim + write(stdout, "a[0]") + write(stdout, ": ") + writeLine(stdout, a[0]) + + write(stdout, "a[1]") + write(stdout, ": ") + writeLine(stdout, a[1]) + + write(stdout, "x") + write(stdout, ": ") + writeLine(stdout, x) + + +Arguments that are passed to a ``varargs`` parameter are wrapped in an array +constructor expression. This is why ``debug`` iterates over all of ``n``'s +children. + + +BindSym +------- + +The above ``debug`` macro relies on the fact that ``write``, ``writeLine`` and +``stdout`` are declared in the system module and thus visible in the +instantiating context. There is a way to use bound identifiers +(aka `symbols`:idx:) instead of using unbound identifiers. The ``bindSym`` +builtin can be used for that: + +.. code-block:: nim + :test: "nim c $1" + + import macros + + macro debug(n: varargs[typed]): untyped = + result = newNimNode(nnkStmtList, n) + for x in n: + # we can bind symbols in scope via 'bindSym': + add(result, newCall(bindSym"write", bindSym"stdout", toStrLit(x))) + add(result, newCall(bindSym"write", bindSym"stdout", newStrLitNode(": "))) + add(result, newCall(bindSym"writeLine", bindSym"stdout", x)) + + var + a: array[0..10, int] + x = "some string" + a[0] = 42 + a[1] = 45 + + debug(a[0], a[1], x) + +The macro call expands to: + +.. code-block:: nim + write(stdout, "a[0]") + write(stdout, ": ") + writeLine(stdout, a[0]) + + write(stdout, "a[1]") + write(stdout, ": ") + writeLine(stdout, a[1]) + + write(stdout, "x") + write(stdout, ": ") + writeLine(stdout, x) + +However, the symbols ``write``, ``writeLine`` and ``stdout`` are already bound +and are not looked up again. As the example shows, ``bindSym`` does work with +overloaded symbols implicitly. + + +Statement Macros +---------------- + +Statement macros are defined just as expression macros. However, they are +invoked by an expression following a colon. + +The following example outlines a macro that generates a lexical analyzer from +regular expressions: + +.. code-block:: nim + import macros + + macro case_token(n: untyped): untyped = + # creates a lexical analyzer from regular expressions + # ... (implementation is an exercise for the reader :-) + discard + + case_token: # this colon tells the parser it is a macro statement + of r"[A-Za-z_]+[A-Za-z_0-9]*": + return tkIdentifier + of r"0-9+": + return tkInteger + of r"[\+\-\*\?]+": + return tkOperator + else: + return tkUnknown + + +**Style note**: For code readability, it is the best idea to use the least +powerful programming construct that still suffices. So the "check list" is: + +(1) Use an ordinary proc/iterator, if possible. +(2) Else: Use a generic proc/iterator, if possible. +(3) Else: Use a template, if possible. +(4) Else: Use a macro. + + +Macros as pragmas +----------------- + +Whole routines (procs, iterators etc.) can also be passed to a template or +a macro via the pragma notation: + +.. code-block:: nim + template m(s: untyped) = discard + + proc p() {.m.} = discard + +This is a simple syntactic transformation into: + +.. code-block:: nim + template m(s: untyped) = discard + + m: + proc p() = discard + + +For loop macros +--------------- + +A macro that takes as its only input parameter an expression of the special +type ``system.ForLoopStmt`` can rewrite the entirety of a ``for`` loop: + +.. code-block:: nim + :test: "nim c $1" + + import macros + {.experimental: "forLoopMacros".} + + macro enumerate(x: ForLoopStmt): untyped = + expectKind x, nnkForStmt + # we strip off the first for loop variable and use + # it as an integer counter: + result = newStmtList() + result.add newVarStmt(x[0], newLit(0)) + var body = x[^1] + if body.kind != nnkStmtList: + body = newTree(nnkStmtList, body) + body.add newCall(bindSym"inc", x[0]) + var newFor = newTree(nnkForStmt) + for i in 1..x.len-3: + newFor.add x[i] + # transform enumerate(X) to 'X' + newFor.add x[^2][1] + newFor.add body + result.add newFor + # now wrap the whole macro in a block to create a new scope + result = quote do: + block: `result` + + for a, b in enumerate(items([1, 2, 3])): + echo a, " ", b + + # without wrapping the macro in a block, we'd need to choose different + # names for `a` and `b` here to avoid redefinition errors + for a, b in enumerate([1, 2, 3, 5]): + echo a, " ", b + + +Currently for loop macros must be enabled explicitly +via ``{.experimental: "forLoopMacros".}``. + + +Case statement macros +--------------------- + +A macro that needs to be called `match`:idx: can be used to rewrite +``case`` statements in order to implement `pattern matching`:idx: for +certain types. The following example implements a simplistic form of +pattern matching for tuples, leveraging the existing equality operator +for tuples (as provided in ``system.==``): + +.. code-block:: nim + :test: "nim c $1" + + {.experimental: "caseStmtMacros".} + + import macros + + macro match(n: tuple): untyped = + result = newTree(nnkIfStmt) + let selector = n[0] + for i in 1 ..< n.len: + let it = n[i] + case it.kind + of nnkElse, nnkElifBranch, nnkElifExpr, nnkElseExpr: + result.add it + of nnkOfBranch: + for j in 0..it.len-2: + let cond = newCall("==", selector, it[j]) + result.add newTree(nnkElifBranch, cond, it[^1]) + else: + error "'match' cannot handle this node", it + echo repr result + + case ("foo", 78) + of ("foo", 78): echo "yes" + of ("bar", 88): echo "no" + else: discard + + +Currently case statement macros must be enabled explicitly +via ``{.experimental: "caseStmtMacros".}``. + +``match`` macros are subject to overload resolution. First the +``case``'s selector expression is used to determine which ``match`` +macro to call. To this macro is then the complete ``case`` statement +body is passed and the macro is evaluated. + +In other words, the macro needs to transform the full ``case`` statement +but only the statement's selector expression is used to determine which +``macro`` to call. + + +Special Types +============= + +static[T] +--------- + +**Note**: static[T] is still in development. + +As their name suggests, static parameters must be known at compile-time: + +.. code-block:: nim + + proc precompiledRegex(pattern: static string): RegEx = + var res {.global.} = re(pattern) + return res + + precompiledRegex("/d+") # Replaces the call with a precompiled + # regex, stored in a global variable + + precompiledRegex(paramStr(1)) # Error, command-line options + # are not known at compile-time + + +For the purposes of code generation, all static params are treated as +generic params - the proc will be compiled separately for each unique +supplied value (or combination of values). + +Static params can also appear in the signatures of generic types: + +.. code-block:: nim + + type + Matrix[M,N: static int; T: Number] = array[0..(M*N - 1), T] + # Note how `Number` is just a type constraint here, while + # `static int` requires us to supply a compile-time int value + + AffineTransform2D[T] = Matrix[3, 3, T] + AffineTransform3D[T] = Matrix[4, 4, T] + + var m1: AffineTransform3D[float] # OK + var m2: AffineTransform2D[string] # Error, `string` is not a `Number` + +Please note that ``static T`` is just a syntactic convenience for the +underlying generic type ``static[T]``. The type param can be omitted +to obtain the type class of all values known at compile-time. A more +specific type class can be created by instantiating ``static`` with +another type class. + +You can force the evaluation of a certain expression at compile-time by +coercing it to a corresponding ``static`` type: + +.. code-block:: nim + import math + + echo static(fac(5)), " ", static[bool](16.isPowerOfTwo) + +The complier will report any failure to evaluate the expression or a +possible type mismatch error. + +type[T] +------- + +In many contexts, Nim allows you to treat the names of types as regular +values. These values exists only during the compilation phase, but since +all values must have a type, ``type`` is considered their special type. + +``type`` acts like a generic type. For instance, the type of the symbol +``int`` is ``type[int]``. Just like with regular generic types, when the +generic param is ommited, ``type`` denotes the type class of all types. +As a syntactic convenience, you can also use ``type`` as a modifier. +``type int`` is considered the same as ``type[int]``. + +Procs featuring ``type`` params are considered implicitly generic. +They will be instantiated for each unique combination of supplied types +and within the body of the proc, the name of each param will refer to +the bound concrete type: + +.. code-block:: nim + + proc new(T: type): ref T = + echo "allocating ", T.name + new(result) + + var n = Node.new + var tree = new(BinaryTree[int]) + +When multiple type params are present, they will bind freely to different +types. To force a bind-once behavior one can use an explicit generic param: + +.. code-block:: nim + proc acceptOnlyTypePairs[T, U](A, B: type[T]; C, D: type[U]) + +Once bound, type params can appear in the rest of the proc signature: + +.. code-block:: nim + :test: "nim c $1" + + template declareVariableWithType(T: type, value: T) = + var x: T = value + + declareVariableWithType int, 42 + + +Overload resolution can be further influenced by constraining the set of +types that will match the type param: + +.. code-block:: nim + :test: "nim c $1" + + template maxval(T: type int): int = high(int) + template maxval(T: type float): float = Inf + + var i = int.maxval + var f = float.maxval + when false: + var s = string.maxval # error, maxval is not implemented for string + +The constraint can be a concrete type or a type class. + +type operator +------------- + +You can obtain the type of a given expression by constructing a ``type`` +value from it (in many other languages this is known as the `typeof`:idx: +operator): + +.. code-block:: nim + var x = 0 + var y: type(x) # y has type int + +You may add a constraint to the resulting type to trigger a compile-time error +if the expression doesn't have the expected type: + +.. code-block:: nim + var x = 0 + var y: type[object](x) # Error: type mismatch: got <int> but expected 'object' + +If ``type`` is used to determine the result type of a proc/iterator/converter +call ``c(X)`` (where ``X`` stands for a possibly empty list of arguments), the +interpretation where ``c`` is an iterator is preferred over the +other interpretations: + +.. code-block:: nim + import strutils + + # strutils contains both a ``split`` proc and iterator, but since an + # an iterator is the preferred interpretation, `y` has the type ``string``: + var y: type("a b c".split) + + +Special Operators +================= + +dot operators +------------- + +**Note**: Dot operators are still experimental and so need to be enabled +via ``{.experimental: "dotOperators".}``. + +Nim offers a special family of dot operators that can be used to +intercept and rewrite proc call and field access attempts, referring +to previously undeclared symbol names. They can be used to provide a +fluent interface to objects lying outside the static confines of the +type system such as values from dynamic scripting languages +or dynamic file formats such as JSON or XML. + +When Nim encounters an expression that cannot be resolved by the +standard overload resolution rules, the current scope will be searched +for a dot operator that can be matched against a re-written form of +the expression, where the unknown field or proc name is passed to +an ``untyped`` parameter: + +.. code-block:: nim + a.b # becomes `.`(a, b) + a.b(c, d) # becomes `.`(a, b, c, d) + +The matched dot operators can be symbols of any callable kind (procs, +templates and macros), depending on the desired effect: + +.. code-block:: nim + template `.` (js: PJsonNode, field: untyped): JSON = js[astToStr(field)] + + var js = parseJson("{ x: 1, y: 2}") + echo js.x # outputs 1 + echo js.y # outputs 2 + +The following dot operators are available: + +operator `.` +------------ +This operator will be matched against both field accesses and method calls. + +operator `.()` +--------------- +This operator will be matched exclusively against method calls. It has higher +precedence than the `.` operator and this allows one to handle expressions like +`x.y` and `x.y()` differently if one is interfacing with a scripting language +for example. + +operator `.=` +------------- +This operator will be matched against assignments to missing fields. + +.. code-block:: nim + a.b = c # becomes `.=`(a, b, c) + + + + +Type bound operations +===================== + +There are 3 operations that are bound to a type: + +1. Assignment +2. Destruction +3. Deep copying for communication between threads + +These operations can be *overridden* instead of *overloaded*. This means the +implementation is automatically lifted to structured types. For instance if type +``T`` has an overridden assignment operator ``=`` this operator is also used +for assignments of the type ``seq[T]``. Since these operations are bound to a +type they have to be bound to a nominal type for reasons of simplicity of +implementation: This means an overridden ``deepCopy`` for ``ref T`` is really +bound to ``T`` and not to ``ref T``. This also means that one cannot override +``deepCopy`` for both ``ptr T`` and ``ref T`` at the same time; instead a +helper distinct or object type has to be used for one pointer type. + + +operator `=` +------------ + +This operator is the assignment operator. Note that in the contexts +``result = expr``, ``parameter = defaultValue`` or for +parameter passing no assignment is performed. For a type ``T`` that has an +overloaded assignment operator ``var v = T()`` is rewritten +to ``var v: T; v = T()``; in other words ``var`` and ``let`` contexts do count +as assignments. + +The assignment operator needs to be attached to an object or distinct +type ``T``. Its signature has to be ``(var T, T)``. Example: + +.. code-block:: nim + type + Concrete = object + a, b: string + + proc `=`(d: var Concrete; src: Concrete) = + shallowCopy(d.a, src.a) + shallowCopy(d.b, src.b) + echo "Concrete '=' called" + + var x, y: array[0..2, Concrete] + var cA, cB: Concrete + + var cATup, cBTup: tuple[x: int, ha: Concrete] + + x = y + cA = cB + cATup = cBTup + + + +destructors +----------- + +A destructor must have a single parameter with a concrete type (the name of a +generic type is allowed too). The name of the destructor has to be ``=destroy``. + +``=destroy(v)`` will be automatically invoked for every local stack +variable ``v`` that goes out of scope. + +If a structured type features a field with destructable type and +the user has not provided an explicit implementation, a destructor for the +structured type will be automatically generated. Calls to any base class +destructors in both user-defined and generated destructors will be inserted. + +A destructor is attached to the type it destructs; expressions of this type +can then only be used in *destructible contexts* and as parameters: + +.. code-block:: nim + type + MyObj = object + x, y: int + p: pointer + + proc `=destroy`(o: var MyObj) = + if o.p != nil: dealloc o.p + + proc open: MyObj = + result = MyObj(x: 1, y: 2, p: alloc(3)) + + proc work(o: MyObj) = + echo o.x + # No destructor invoked here for 'o' as 'o' is a parameter. + + proc main() = + # destructor automatically invoked at the end of the scope: + var x = open() + # valid: pass 'x' to some other proc: + work(x) + + # Error: usage of a type with a destructor in a non destructible context + echo open() + +A destructible context is currently only the following: + +1. The ``expr`` in ``var x = expr``. +2. The ``expr`` in ``let x = expr``. +3. The ``expr`` in ``return expr``. +4. The ``expr`` in ``result = expr`` where ``result`` is the special symbol + introduced by the compiler. + +These rules ensure that the construction is tied to a variable and can easily +be destructed at its scope exit. Later versions of the language will improve +the support of destructors. + +Be aware that destructors are not called for objects allocated with ``new``. +This may change in future versions of language, but for now the `finalizer`:idx: +parameter to ``new`` has to be used. + +**Note**: Destructors are still experimental and the spec might change +significantly in order to incorporate an escape analysis. + + +deepCopy +-------- + +``=deepCopy`` is a builtin that is invoked whenever data is passed to +a ``spawn``'ed proc to ensure memory safety. The programmer can override its +behaviour for a specific ``ref`` or ``ptr`` type ``T``. (Later versions of the +language may weaken this restriction.) + +The signature has to be: + +.. code-block:: nim + proc `=deepCopy`(x: T): T + +This mechanism will be used by most data structures that support shared memory +like channels to implement thread safe automatic memory management. + +The builtin ``deepCopy`` can even clone closures and their environments. See +the documentation of `spawn`_ for details. + + +Term rewriting macros +===================== + +Term rewriting macros are macros or templates that have not only +a *name* but also a *pattern* that is searched for after the semantic checking +phase of the compiler: This means they provide an easy way to enhance the +compilation pipeline with user defined optimizations: + +.. code-block:: nim + template optMul{`*`(a, 2)}(a: int): int = a+a + + let x = 3 + echo x * 2 + +The compiler now rewrites ``x * 2`` as ``x + x``. The code inside the +curlies is the pattern to match against. The operators ``*``, ``**``, +``|``, ``~`` have a special meaning in patterns if they are written in infix +notation, so to match verbatim against ``*`` the ordinary function call syntax +needs to be used. + + +Unfortunately optimizations are hard to get right and even the tiny example +is **wrong**: + +.. code-block:: nim + template optMul{`*`(a, 2)}(a: int): int = a+a + + proc f(): int = + echo "side effect!" + result = 55 + + echo f() * 2 + +We cannot duplicate 'a' if it denotes an expression that has a side effect! +Fortunately Nim supports side effect analysis: + +.. code-block:: nim + template optMul{`*`(a, 2)}(a: int{noSideEffect}): int = a+a + + proc f(): int = + echo "side effect!" + result = 55 + + echo f() * 2 # not optimized ;-) + +You can make one overload matching with a constraint and one without, and the +one with a constraint will have precedence, and so you can handle both cases +differently. + +So what about ``2 * a``? We should tell the compiler ``*`` is commutative. We +cannot really do that however as the following code only swaps arguments +blindly: + +.. code-block:: nim + template mulIsCommutative{`*`(a, b)}(a, b: int): int = b*a + +What optimizers really need to do is a *canonicalization*: + +.. code-block:: nim + template canonMul{`*`(a, b)}(a: int{lit}, b: int): int = b*a + +The ``int{lit}`` parameter pattern matches against an expression of +type ``int``, but only if it's a literal. + + + +Parameter constraints +--------------------- + +The `parameter constraint`:idx: expression can use the operators ``|`` (or), +``&`` (and) and ``~`` (not) and the following predicates: + +=================== ===================================================== +Predicate Meaning +=================== ===================================================== +``atom`` The matching node has no children. +``lit`` The matching node is a literal like "abc", 12. +``sym`` The matching node must be a symbol (a bound + identifier). +``ident`` The matching node must be an identifier (an unbound + identifier). +``call`` The matching AST must be a call/apply expression. +``lvalue`` The matching AST must be an lvalue. +``sideeffect`` The matching AST must have a side effect. +``nosideeffect`` The matching AST must have no side effect. +``param`` A symbol which is a parameter. +``genericparam`` A symbol which is a generic parameter. +``module`` A symbol which is a module. +``type`` A symbol which is a type. +``var`` A symbol which is a variable. +``let`` A symbol which is a ``let`` variable. +``const`` A symbol which is a constant. +``result`` The special ``result`` variable. +``proc`` A symbol which is a proc. +``method`` A symbol which is a method. +``iterator`` A symbol which is an iterator. +``converter`` A symbol which is a converter. +``macro`` A symbol which is a macro. +``template`` A symbol which is a template. +``field`` A symbol which is a field in a tuple or an object. +``enumfield`` A symbol which is a field in an enumeration. +``forvar`` A for loop variable. +``label`` A label (used in ``block`` statements). +``nk*`` The matching AST must have the specified kind. + (Example: ``nkIfStmt`` denotes an ``if`` statement.) +``alias`` States that the marked parameter needs to alias + with *some* other parameter. +``noalias`` States that *every* other parameter must not alias + with the marked parameter. +=================== ===================================================== + +Predicates that share their name with a keyword have to be escaped with +backticks: `` `const` ``. +The ``alias`` and ``noalias`` predicates refer not only to the matching AST, +but also to every other bound parameter; syntactically they need to occur after +the ordinary AST predicates: + +.. code-block:: nim + template ex{a = b + c}(a: int{noalias}, b, c: int) = + # this transformation is only valid if 'b' and 'c' do not alias 'a': + a = b + inc a, c + + +Pattern operators +----------------- + +The operators ``*``, ``**``, ``|``, ``~`` have a special meaning in patterns +if they are written in infix notation. + + +The ``|`` operator +~~~~~~~~~~~~~~~~~~ + +The ``|`` operator if used as infix operator creates an ordered choice: + +.. code-block:: nim + template t{0|1}(): untyped = 3 + let a = 1 + # outputs 3: + echo a + +The matching is performed after the compiler performed some optimizations like +constant folding, so the following does not work: + +.. code-block:: nim + template t{0|1}(): untyped = 3 + # outputs 1: + echo 1 + +The reason is that the compiler already transformed the 1 into "1" for +the ``echo`` statement. However, a term rewriting macro should not change the +semantics anyway. In fact they can be deactivated with the ``--patterns:off`` +command line option or temporarily with the ``patterns`` pragma. + + +The ``{}`` operator +~~~~~~~~~~~~~~~~~~~ + +A pattern expression can be bound to a pattern parameter via the ``expr{param}`` +notation: + +.. code-block:: nim + template t{(0|1|2){x}}(x: untyped): untyped = x+1 + let a = 1 + # outputs 2: + echo a + + +The ``~`` operator +~~~~~~~~~~~~~~~~~~ + +The ``~`` operator is the **not** operator in patterns: + +.. code-block:: nim + template t{x = (~x){y} and (~x){z}}(x, y, z: bool) = + x = y + if x: x = z + + var + a = false + b = true + c = false + a = b and c + echo a + + +The ``*`` operator +~~~~~~~~~~~~~~~~~~ + +The ``*`` operator can *flatten* a nested binary expression like ``a & b & c`` +to ``&(a, b, c)``: + +.. code-block:: nim + var + calls = 0 + + proc `&&`(s: varargs[string]): string = + result = s[0] + for i in 1..len(s)-1: result.add s[i] + inc calls + + template optConc{ `&&` * a }(a: string): untyped = &&a + + let space = " " + echo "my" && (space & "awe" && "some " ) && "concat" + + # check that it's been optimized properly: + doAssert calls == 1 + + +The second operator of `*` must be a parameter; it is used to gather all the +arguments. The expression ``"my" && (space & "awe" && "some " ) && "concat"`` +is passed to ``optConc`` in ``a`` as a special list (of kind ``nkArgList``) +which is flattened into a call expression; thus the invocation of ``optConc`` +produces: + +.. code-block:: nim + `&&`("my", space & "awe", "some ", "concat") + + +The ``**`` operator +~~~~~~~~~~~~~~~~~~~ + +The ``**`` is much like the ``*`` operator, except that it gathers not only +all the arguments, but also the matched operators in reverse polish notation: + +.. code-block:: nim + import macros + + type + Matrix = object + dummy: int + + proc `*`(a, b: Matrix): Matrix = discard + proc `+`(a, b: Matrix): Matrix = discard + proc `-`(a, b: Matrix): Matrix = discard + proc `$`(a: Matrix): string = result = $a.dummy + proc mat21(): Matrix = + result.dummy = 21 + + macro optM{ (`+`|`-`|`*`) ** a }(a: Matrix): untyped = + echo treeRepr(a) + result = newCall(bindSym"mat21") + + var x, y, z: Matrix + + echo x + y * z - x + +This passes the expression ``x + y * z - x`` to the ``optM`` macro as +an ``nnkArgList`` node containing:: + + Arglist + Sym "x" + Sym "y" + Sym "z" + Sym "*" + Sym "+" + Sym "x" + Sym "-" + +(Which is the reverse polish notation of ``x + y * z - x``.) + + +Parameters +---------- + +Parameters in a pattern are type checked in the matching process. If a +parameter is of the type ``varargs`` it is treated specially and it can match +0 or more arguments in the AST to be matched against: + +.. code-block:: nim + template optWrite{ + write(f, x) + ((write|writeLine){w})(f, y) + }(x, y: varargs[untyped], f: File, w: untyped) = + w(f, x, y) + + + +Example: Partial evaluation +--------------------------- + +The following example shows how some simple partial evaluation can be +implemented with term rewriting: + +.. code-block:: nim + proc p(x, y: int; cond: bool): int = + result = if cond: x + y else: x - y + + template optP1{p(x, y, true)}(x, y: untyped): untyped = x + y + template optP2{p(x, y, false)}(x, y: untyped): untyped = x - y + + +Example: Hoisting +----------------- + +The following example shows how some form of hoisting can be implemented: + +.. code-block:: nim + import pegs + + template optPeg{peg(pattern)}(pattern: string{lit}): Peg = + var gl {.global, gensym.} = peg(pattern) + gl + + for i in 0 .. 3: + echo match("(a b c)", peg"'(' @ ')'") + echo match("W_HI_Le", peg"\y 'while'") + +The ``optPeg`` template optimizes the case of a peg constructor with a string +literal, so that the pattern will only be parsed once at program startup and +stored in a global ``gl`` which is then re-used. This optimization is called +hoisting because it is comparable to classical loop hoisting. + + +AST based overloading +===================== + +Parameter constraints can also be used for ordinary routine parameters; these +constraints affect ordinary overloading resolution then: + +.. code-block:: nim + proc optLit(a: string{lit|`const`}) = + echo "string literal" + proc optLit(a: string) = + echo "no string literal" + + const + constant = "abc" + + var + variable = "xyz" + + optLit("literal") + optLit(constant) + optLit(variable) + +However, the constraints ``alias`` and ``noalias`` are not available in +ordinary routines. + + +Move optimization +----------------- + +The ``call`` constraint is particularly useful to implement a move +optimization for types that have copying semantics: + +.. code-block:: nim + proc `[]=`*(t: var Table, key: string, val: string) = + ## puts a (key, value)-pair into `t`. The semantics of string require + ## a copy here: + let idx = findInsertionPosition(key) + t[idx].key = key + t[idx].val = val + + proc `[]=`*(t: var Table, key: string{call}, val: string{call}) = + ## puts a (key, value)-pair into `t`. Optimized version that knows that + ## the strings are unique and thus don't need to be copied: + let idx = findInsertionPosition(key) + shallowCopy t[idx].key, key + shallowCopy t[idx].val, val + + var t: Table + # overloading resolution ensures that the optimized []= is called here: + t[f()] = g() + + + +Modules +======= +Nim supports splitting a program into pieces by a module concept. +Each module needs to be in its own file and has its own `namespace`:idx:. +Modules enable `information hiding`:idx: and `separate compilation`:idx:. +A module may gain access to symbols of another module by the `import`:idx: +statement. `Recursive module dependencies`:idx: are allowed, but slightly +subtle. Only top-level symbols that are marked with an asterisk (``*``) are +exported. A valid module name can only be a valid Nim identifier (and thus its +filename is ``identifier.nim``). + +The algorithm for compiling modules is: + +- compile the whole module as usual, following import statements recursively + +- if there is a cycle only import the already parsed symbols (that are + exported); if an unknown identifier occurs then abort + +This is best illustrated by an example: + +.. code-block:: nim + # Module A + type + T1* = int # Module A exports the type ``T1`` + import B # the compiler starts parsing B + + proc main() = + var i = p(3) # works because B has been parsed completely here + + main() + + +.. code-block:: nim + # Module B + import A # A is not parsed here! Only the already known symbols + # of A are imported. + + proc p*(x: A.T1): A.T1 = + # this works because the compiler has already + # added T1 to A's interface symbol table + result = x + 1 + + +Import statement +~~~~~~~~~~~~~~~~ + +After the ``import`` statement a list of module names can follow or a single +module name followed by an ``except`` list to prevent some symbols to be +imported: + +.. code-block:: nim + :test: "nim c $1" + :status: 1 + + import strutils except `%`, toUpperAscii + + # doesn't work then: + echo "$1" % "abc".toUpperAscii + + +It is not checked that the ``except`` list is really exported from the module. +This feature allows to compile against an older version of the module that +does not export these identifiers. + + +Include statement +~~~~~~~~~~~~~~~~~ +The ``include`` statement does something fundamentally different than +importing a module: it merely includes the contents of a file. The ``include`` +statement is useful to split up a large module into several files: + +.. code-block:: nim + include fileA, fileB, fileC + + + +Module names in imports +~~~~~~~~~~~~~~~~~~~~~~~ + +A module alias can be introduced via the ``as`` keyword: + +.. code-block:: nim + import strutils as su, sequtils as qu + + echo su.format("$1", "lalelu") + +The original module name is then not accessible. The notations +``path/to/module`` or ``"path/to/module"`` can be used to refer to a module +in subdirectories: + +.. code-block:: nim + import lib/pure/os, "lib/pure/times" + +Note that the module name is still ``strutils`` and not ``lib/pure/strutils`` +and so one **cannot** do: + +.. code-block:: nim + import lib/pure/strutils + echo lib/pure/strutils.toUpperAscii("abc") + +Likewise the following does not make sense as the name is ``strutils`` already: + +.. code-block:: nim + import lib/pure/strutils as strutils + + +Collective imports from a directory +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The syntax ``import dir / [moduleA, moduleB]`` can be used to import multiple modules +from the same directory. + +Path names are syntactically either Nim identifiers or string literals. If the path +name is not a valid Nim identifier it needs to be a string literal: + +.. code-block:: nim + import "gfx/3d/somemodule" # in quotes because '3d' is not a valid Nim identifier + + +Pseudo import/include paths +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A directory can also be a so called "pseudo directory". They can be used to +avoid ambiguity when there are multiple modules with the same path. + +There are two pseudo directories: + +1. ``std``: The ``std`` pseudo directory is the abstract location of Nim's standard + library. For example, the syntax ``import std / strutils`` is used to unambiguously + refer to the standard library's ``strutils`` module. +2. ``pkg``: The ``pkg`` pseudo directory is used to unambiguously refer to a Nimble + package. However, for technical details that lie outside of the scope of this document + its semantics are: *Use the search path to look for module name but ignore the standard + library locations*. In other words, it is the opposite of ``std``. + + +From import statement +~~~~~~~~~~~~~~~~~~~~~ + +After the ``from`` statement a module name follows followed by +an ``import`` to list the symbols one likes to use without explicit +full qualification: + +.. code-block:: nim + :test: "nim c $1" + + from strutils import `%` + + echo "$1" % "abc" + # always possible: full qualification: + echo strutils.replace("abc", "a", "z") + +It's also possible to use ``from module import nil`` if one wants to import +the module but wants to enforce fully qualified access to every symbol +in ``module``. + + +Export statement +~~~~~~~~~~~~~~~~ + +An ``export`` statement can be used for symbol forwarding so that client +modules don't need to import a module's dependencies: + +.. code-block:: nim + # module B + type MyObject* = object + +.. code-block:: nim + # module A + import B + export B.MyObject + + proc `$`*(x: MyObject): string = "my object" + + +.. code-block:: nim + # module C + import A + + # B.MyObject has been imported implicitly here: + var x: MyObject + echo $x + +When the exported symbol is another module, all of its definitions will +be forwarded. You can use an ``except`` list to exclude some of the symbols. + + +Scope rules +----------- +Identifiers are valid from the point of their declaration until the end of +the block in which the declaration occurred. The range where the identifier +is known is the scope of the identifier. The exact scope of an +identifier depends on the way it was declared. + +Block scope +~~~~~~~~~~~ +The *scope* of a variable declared in the declaration part of a block +is valid from the point of declaration until the end of the block. If a +block contains a second block, in which the identifier is redeclared, +then inside this block, the second declaration will be valid. Upon +leaving the inner block, the first declaration is valid again. An +identifier cannot be redefined in the same block, except if valid for +procedure or iterator overloading purposes. + + +Tuple or object scope +~~~~~~~~~~~~~~~~~~~~~ +The field identifiers inside a tuple or object definition are valid in the +following places: + +* To the end of the tuple/object definition. +* Field designators of a variable of the given tuple/object type. +* In all descendant types of the object type. + +Module scope +~~~~~~~~~~~~ +All identifiers of a module are valid from the point of declaration until +the end of the module. Identifiers from indirectly dependent modules are *not* +available. The `system`:idx: module is automatically imported in every module. + +If a module imports an identifier by two different modules, each occurrence of +the identifier has to be qualified, unless it is an overloaded procedure or +iterator in which case the overloading resolution takes place: + +.. code-block:: nim + # Module A + var x*: string + +.. code-block:: nim + # Module B + var x*: int + +.. code-block:: nim + # Module C + import A, B + write(stdout, x) # error: x is ambiguous + write(stdout, A.x) # no error: qualifier used + + var x = 4 + write(stdout, x) # not ambiguous: uses the module C's x + + +Compiler Messages +================= + +The Nim compiler emits different kinds of messages: `hint`:idx:, +`warning`:idx:, and `error`:idx: messages. An *error* message is emitted if +the compiler encounters any static error. + + + +Pragmas +======= + +Pragmas are Nim's method to give the compiler additional information / +commands without introducing a massive number of new keywords. Pragmas are +processed on the fly during semantic checking. Pragmas are enclosed in the +special ``{.`` and ``.}`` curly brackets. Pragmas are also often used as a +first implementation to play with a language feature before a nicer syntax +to access the feature becomes available. + + +deprecated pragma +----------------- + +The deprecated pragma is used to mark a symbol as deprecated: + +.. code-block:: nim + proc p() {.deprecated.} + var x {.deprecated.}: char + +This pragma can also take in an optional warning string to relay to developers. + +.. code-block:: nim + proc thing(x: bool) {.deprecated: "See arguments of otherThing()".} + +It can also be used as a statement, in that case it takes a list of *renamings*. + +.. code-block:: nim + type + File = object + Stream = ref object + {.deprecated: [TFile: File, PStream: Stream].} + +noSideEffect pragma +------------------- +The ``noSideEffect`` pragma is used to mark a proc/iterator to have no side +effects. This means that the proc/iterator only changes locations that are +reachable from its parameters and the return value only depends on the +arguments. If none of its parameters have the type ``var T`` +or ``ref T`` or ``ptr T`` this means no locations are modified. It is a static +error to mark a proc/iterator to have no side effect if the compiler cannot +verify this. + +As a special semantic rule, the built-in `debugEcho <system.html#debugEcho>`_ +pretends to be free of side effects, so that it can be used for debugging +routines marked as ``noSideEffect``. + +``func`` is syntactic sugar for a proc with no side effects: + +.. code-block:: nim + func `+` (x, y: int): int + + +compileTime pragma +------------------ +The ``compileTime`` pragma is used to mark a proc or variable to be used at +compile time only. No code will be generated for it. Compile time procs are +useful as helpers for macros. Since version 0.12.0 of the language, a proc +that uses ``system.NimNode`` within its parameter types is implicitly declared +``compileTime``: + +.. code-block:: nim + proc astHelper(n: NimNode): NimNode = + result = n + +Is the same as: + +.. code-block:: nim + proc astHelper(n: NimNode): NimNode {.compileTime.} = + result = n + + +noReturn pragma +--------------- +The ``noreturn`` pragma is used to mark a proc that never returns. + + +acyclic pragma +-------------- +The ``acyclic`` pragma can be used for object types to mark them as acyclic +even though they seem to be cyclic. This is an **optimization** for the garbage +collector to not consider objects of this type as part of a cycle: + +.. code-block:: nim + type + Node = ref NodeObj + NodeObj {.acyclic.} = object + left, right: Node + data: string + +Or if we directly use a ref object: + +.. code-block:: nim + type + Node = ref object {.acyclic.} + left, right: Node + data: string + +In the example a tree structure is declared with the ``Node`` type. Note that +the type definition is recursive and the GC has to assume that objects of +this type may form a cyclic graph. The ``acyclic`` pragma passes the +information that this cannot happen to the GC. If the programmer uses the +``acyclic`` pragma for data types that are in reality cyclic, the GC may leak +memory, but nothing worse happens. + +**Future directions**: The ``acyclic`` pragma may become a property of a +``ref`` type: + +.. code-block:: nim + type + Node = acyclic ref NodeObj + NodeObj = object + left, right: Node + data: string + + +final pragma +------------ +The ``final`` pragma can be used for an object type to specify that it +cannot be inherited from. Note that inheritance is only available for +objects that inherit from an existing object (via the ``object of SuperType`` +syntax) or that have been marked as ``inheritable``. + + +shallow pragma +-------------- +The ``shallow`` pragma affects the semantics of a type: The compiler is +allowed to make a shallow copy. This can cause serious semantic issues and +break memory safety! However, it can speed up assignments considerably, +because the semantics of Nim require deep copying of sequences and strings. +This can be expensive, especially if sequences are used to build a tree +structure: + +.. code-block:: nim + type + NodeKind = enum nkLeaf, nkInner + Node {.shallow.} = object + case kind: NodeKind + of nkLeaf: + strVal: string + of nkInner: + children: seq[Node] + + +pure pragma +----------- +An object type can be marked with the ``pure`` pragma so that its type +field which is used for runtime type identification is omitted. This used to be +necessary for binary compatibility with other compiled languages. + +An enum type can be marked as ``pure``. Then access of its fields always +requires full qualification. + + +asmNoStackFrame pragma +---------------------- +A proc can be marked with the ``asmNoStackFrame`` pragma to tell the compiler +it should not generate a stack frame for the proc. There are also no exit +statements like ``return result;`` generated and the generated C function is +declared as ``__declspec(naked)`` or ``__attribute__((naked))`` (depending on +the used C compiler). + +**Note**: This pragma should only be used by procs which consist solely of +assembler statements. + +error pragma +------------ +The ``error`` pragma is used to make the compiler output an error message +with the given content. Compilation does not necessarily abort after an error +though. + +The ``error`` pragma can also be used to +annotate a symbol (like an iterator or proc). The *usage* of the symbol then +triggers a compile-time error. This is especially useful to rule out that some +operation is valid due to overloading and type conversions: + +.. code-block:: nim + ## check that underlying int values are compared and not the pointers: + proc `==`(x, y: ptr int): bool {.error.} + + +fatal pragma +------------ +The ``fatal`` pragma is used to make the compiler output an error message +with the given content. In contrast to the ``error`` pragma, compilation +is guaranteed to be aborted by this pragma. Example: + +.. code-block:: nim + when not defined(objc): + {.fatal: "Compile this program with the objc command!".} + +warning pragma +-------------- +The ``warning`` pragma is used to make the compiler output a warning message +with the given content. Compilation continues after the warning. + +hint pragma +----------- +The ``hint`` pragma is used to make the compiler output a hint message with +the given content. Compilation continues after the hint. + +line pragma +----------- +The ``line`` pragma can be used to affect line information of the annotated +statement as seen in stack backtraces: + +.. code-block:: nim + + template myassert*(cond: untyped, msg = "") = + if not cond: + # change run-time line information of the 'raise' statement: + {.line: InstantiationInfo().}: + raise newException(EAssertionFailed, msg) + +If the ``line`` pragma is used with a parameter, the parameter needs be a +``tuple[filename: string, line: int]``. If it is used without a parameter, +``system.InstantiationInfo()`` is used. + + +linearScanEnd pragma +-------------------- +The ``linearScanEnd`` pragma can be used to tell the compiler how to +compile a Nim `case`:idx: statement. Syntactically it has to be used as a +statement: + +.. code-block:: nim + case myInt + of 0: + echo "most common case" + of 1: + {.linearScanEnd.} + echo "second most common case" + of 2: echo "unlikely: use branch table" + else: echo "unlikely too: use branch table for ", myInt + +In the example, the case branches ``0`` and ``1`` are much more common than +the other cases. Therefore the generated assembler code should test for these +values first, so that the CPU's branch predictor has a good chance to succeed +(avoiding an expensive CPU pipeline stall). The other cases might be put into a +jump table for O(1) overhead, but at the cost of a (very likely) pipeline +stall. + +The ``linearScanEnd`` pragma should be put into the last branch that should be +tested against via linear scanning. If put into the last branch of the +whole ``case`` statement, the whole ``case`` statement uses linear scanning. + + +computedGoto pragma +------------------- +The ``computedGoto`` pragma can be used to tell the compiler how to +compile a Nim `case`:idx: in a ``while true`` statement. +Syntactically it has to be used as a statement inside the loop: + +.. code-block:: nim + + type + MyEnum = enum + enumA, enumB, enumC, enumD, enumE + + proc vm() = + var instructions: array[0..100, MyEnum] + instructions[2] = enumC + instructions[3] = enumD + instructions[4] = enumA + instructions[5] = enumD + instructions[6] = enumC + instructions[7] = enumA + instructions[8] = enumB + + instructions[12] = enumE + var pc = 0 + while true: + {.computedGoto.} + let instr = instructions[pc] + case instr + of enumA: + echo "yeah A" + of enumC, enumD: + echo "yeah CD" + of enumB: + echo "yeah B" + of enumE: + break + inc(pc) + + vm() + +As the example shows ``computedGoto`` is mostly useful for interpreters. If +the underlying backend (C compiler) does not support the computed goto +extension the pragma is simply ignored. + + +unroll pragma +------------- +The ``unroll`` pragma can be used to tell the compiler that it should unroll +a `for`:idx: or `while`:idx: loop for runtime efficiency: + +.. code-block:: nim + proc searchChar(s: string, c: char): int = + for i in 0 .. s.high: + {.unroll: 4.} + if s[i] == c: return i + result = -1 + +In the above example, the search loop is unrolled by a factor 4. The unroll +factor can be left out too; the compiler then chooses an appropriate unroll +factor. + +**Note**: Currently the compiler recognizes but ignores this pragma. + + +immediate pragma +---------------- + +The immediate pragma is obsolete. See `Typed vs untyped parameters`_. + + +compilation option pragmas +-------------------------- +The listed pragmas here can be used to override the code generation options +for a proc/method/converter. + +The implementation currently provides the following possible options (various +others may be added later). + +=============== =============== ============================================ +pragma allowed values description +=============== =============== ============================================ +checks on|off Turns the code generation for all runtime + checks on or off. +boundChecks on|off Turns the code generation for array bound + checks on or off. +overflowChecks on|off Turns the code generation for over- or + underflow checks on or off. +nilChecks on|off Turns the code generation for nil pointer + checks on or off. +assertions on|off Turns the code generation for assertions + on or off. +warnings on|off Turns the warning messages of the compiler + on or off. +hints on|off Turns the hint messages of the compiler + on or off. +optimization none|speed|size Optimize the code for speed or size, or + disable optimization. +patterns on|off Turns the term rewriting templates/macros + on or off. +callconv cdecl|... Specifies the default calling convention for + all procedures (and procedure types) that + follow. +=============== =============== ============================================ + +Example: + +.. code-block:: nim + {.checks: off, optimization: speed.} + # compile without runtime checks and optimize for speed + + +push and pop pragmas +-------------------- +The `push/pop`:idx: pragmas are very similar to the option directive, +but are used to override the settings temporarily. Example: + +.. code-block:: nim + {.push checks: off.} + # compile this section without runtime checks as it is + # speed critical + # ... some code ... + {.pop.} # restore old settings + + +register pragma +--------------- +The ``register`` pragma is for variables only. It declares the variable as +``register``, giving the compiler a hint that the variable should be placed +in a hardware register for faster access. C compilers usually ignore this +though and for good reasons: Often they do a better job without it anyway. + +In highly specific cases (a dispatch loop of a bytecode interpreter for +example) it may provide benefits, though. + + +global pragma +------------- +The ``global`` pragma can be applied to a variable within a proc to instruct +the compiler to store it in a global location and initialize it once at program +startup. + +.. code-block:: nim + proc isHexNumber(s: string): bool = + var pattern {.global.} = re"[0-9a-fA-F]+" + result = s.match(pattern) + +When used within a generic proc, a separate unique global variable will be +created for each instantiation of the proc. The order of initialization of +the created global variables within a module is not defined, but all of them +will be initialized after any top-level variables in their originating module +and before any variable in a module that imports it. + + +.. + NoForward pragma + ---------------- + The ``noforward`` pragma can be used to turn on and off a special compilation + mode that to large extent eliminates the need for forward declarations. In this + mode, the proc definitions may appear out of order and the compiler will postpone + their semantic analysis and compilation until it actually needs to generate code + using the definitions. In this regard, this mode is similar to the modus operandi + of dynamic scripting languages, where the function calls are not resolved until + the code is executed. Here is the detailed algorithm taken by the compiler: + + 1. When a callable symbol is first encountered, the compiler will only note the + symbol callable name and it will add it to the appropriate overload set in the + current scope. At this step, it won't try to resolve any of the type expressions + used in the signature of the symbol (so they can refer to other not yet defined + symbols). + + 2. When a top level call is encountered (usually at the very end of the module), + the compiler will try to determine the actual types of all of the symbols in the + matching overload set. This is a potentially recursive process as the signatures + of the symbols may include other call expressions, whose types will be resolved + at this point too. + + 3. Finally, after the best overload is picked, the compiler will start + compiling the body of the respective symbol. This in turn will lead the + compiler to discover more call expressions that need to be resolved and steps + 2 and 3 will be repeated as necessary. + + Please note that if a callable symbol is never used in this scenario, its body + will never be compiled. This is the default behavior leading to best compilation + times, but if exhaustive compilation of all definitions is required, using + ``nim check`` provides this option as well. + + Example: + + .. code-block:: nim + + {.noforward: on.} + + proc foo(x: int) = + bar x + + proc bar(x: int) = + echo x + + foo(10) + + +pragma pragma +------------- + +The ``pragma`` pragma can be used to declare user defined pragmas. This is +useful because Nim's templates and macros do not affect pragmas. User +defined pragmas are in a different module-wide scope than all other symbols. +They cannot be imported from a module. + +Example: + +.. code-block:: nim + when appType == "lib": + {.pragma: rtl, exportc, dynlib, cdecl.} + else: + {.pragma: rtl, importc, dynlib: "client.dll", cdecl.} + + proc p*(a, b: int): int {.rtl.} = + result = a+b + +In the example a new pragma named ``rtl`` is introduced that either imports +a symbol from a dynamic library or exports the symbol for dynamic library +generation. + + +Disabling certain messages +-------------------------- +Nim generates some warnings and hints ("line too long") that may annoy the +user. A mechanism for disabling certain messages is provided: Each hint +and warning message contains a symbol in brackets. This is the message's +identifier that can be used to enable or disable it: + +.. code-block:: Nim + {.hint[LineTooLong]: off.} # turn off the hint about too long lines + +This is often better than disabling all warnings at once. + + +used pragma +----------- + +Nim produces a warning for symbols that are not exported and not used either. +The ``used`` pragma can be attached to a symbol to suppress this warning. This +is particularly useful when the symbol was generated by a macro: + +.. code-block:: nim + template implementArithOps(T) = + proc echoAdd(a, b: T) {.used.} = + echo a + b + proc echoSub(a, b: T) {.used.} = + echo a - b + + # no warning produced for the unused 'echoSub' + implementArithOps(int) + echoAdd 3, 5 + + + +experimental pragma +------------------- + +The ``experimental`` pragma enables experimental language features. Depending +on the concrete feature this means that the feature is either considered +too unstable for an otherwise stable release or that the future of the feature +is uncertain (it may be removed any time). + +Example: + +.. code-block:: nim + {.experimental: "parallel".} + + proc useParallel() = + parallel: + for i in 0..4: + echo "echo in parallel" + + +As a top level statement, the experimental pragma enables a feature for the +rest of the module it's enabled in. This is problematic for macro and generic +instantiations that cross a module scope. Currently these usages have to be +put into a ``.push/pop`` environment: + +.. code-block:: nim + + # client.nim + proc useParallel*[T](unused: T) = + # use a generic T here to show the problem. + {.push experimental: "parallel".} + parallel: + for i in 0..4: + echo "echo in parallel" + + {.pop.} + + +.. code-block:: nim + + import client + useParallel(1) + + +Implementation Specific Pragmas +=============================== + +This section describes additional pragmas that the current Nim implementation +supports but which should not be seen as part of the language specification. + +Bitsize pragma +-------------- + +The ``bitsize`` pragma is for object field members. It declares the field as +a bitfield in C/C++. + +.. code-block:: Nim + type + mybitfield = object + flag {.bitsize:1.}: cuint + +generates: + +.. code-block:: C + struct mybitfield { + unsigned int flag:1; + }; + + +Volatile pragma +--------------- +The ``volatile`` pragma is for variables only. It declares the variable as +``volatile``, whatever that means in C/C++ (its semantics are not well defined +in C/C++). + +**Note**: This pragma will not exist for the LLVM backend. + + +NoDecl pragma +------------- +The ``noDecl`` pragma can be applied to almost any symbol (variable, proc, +type, etc.) and is sometimes useful for interoperability with C: +It tells Nim that it should not generate a declaration for the symbol in +the C code. For example: + +.. code-block:: Nim + var + EACCES {.importc, noDecl.}: cint # pretend EACCES was a variable, as + # Nim does not know its value + +However, the ``header`` pragma is often the better alternative. + +**Note**: This will not work for the LLVM backend. + + +Header pragma +------------- +The ``header`` pragma is very similar to the ``noDecl`` pragma: It can be +applied to almost any symbol and specifies that it should not be declared +and instead the generated code should contain an ``#include``: + +.. code-block:: Nim + type + PFile {.importc: "FILE*", header: "<stdio.h>".} = distinct pointer + # import C's FILE* type; Nim will treat it as a new pointer type + +The ``header`` pragma always expects a string constant. The string contant +contains the header file: As usual for C, a system header file is enclosed +in angle brackets: ``<>``. If no angle brackets are given, Nim +encloses the header file in ``""`` in the generated C code. + +**Note**: This will not work for the LLVM backend. + + +IncompleteStruct pragma +----------------------- +The ``incompleteStruct`` pragma tells the compiler to not use the +underlying C ``struct`` in a ``sizeof`` expression: + +.. code-block:: Nim + type + DIR* {.importc: "DIR", header: "<dirent.h>", + pure, incompleteStruct.} = object + + +Compile pragma +-------------- +The ``compile`` pragma can be used to compile and link a C/C++ source file +with the project: + +.. code-block:: Nim + {.compile: "myfile.cpp".} + +**Note**: Nim computes a SHA1 checksum and only recompiles the file if it +has changed. You can use the ``-f`` command line option to force recompilation +of the file. + + +Link pragma +----------- +The ``link`` pragma can be used to link an additional file with the project: + +.. code-block:: Nim + {.link: "myfile.o".} + + +PassC pragma +------------ +The ``passC`` pragma can be used to pass additional parameters to the C +compiler like you would using the commandline switch ``--passC``: + +.. code-block:: Nim + {.passC: "-Wall -Werror".} + +Note that you can use ``gorge`` from the `system module <system.html>`_ to +embed parameters from an external command at compile time: + +.. code-block:: Nim + {.passC: gorge("pkg-config --cflags sdl").} + +PassL pragma +------------ +The ``passL`` pragma can be used to pass additional parameters to the linker +like you would using the commandline switch ``--passL``: + +.. code-block:: Nim + {.passL: "-lSDLmain -lSDL".} + +Note that you can use ``gorge`` from the `system module <system.html>`_ to +embed parameters from an external command at compile time: + +.. code-block:: Nim + {.passL: gorge("pkg-config --libs sdl").} + + +Emit pragma +----------- +The ``emit`` pragma can be used to directly affect the output of the +compiler's code generator. So it makes your code unportable to other code +generators/backends. Its usage is highly discouraged! However, it can be +extremely useful for interfacing with `C++`:idx: or `Objective C`:idx: code. + +Example: + +.. code-block:: Nim + {.emit: """ + static int cvariable = 420; + """.} + + {.push stackTrace:off.} + proc embedsC() = + var nimVar = 89 + # access Nim symbols within an emit section outside of string literals: + {.emit: ["""fprintf(stdout, "%d\n", cvariable + (int)""", nimVar, ");"].} + {.pop.} + + embedsC() + +For backwards compatibility, if the argument to the ``emit`` statement +is a single string literal, Nim symbols can be referred to via backticks. +This usage is however deprecated. + +For a toplevel emit statement the section where in the generated C/C++ file +the code should be emitted can be influenced via the +prefixes ``/*TYPESECTION*/`` or ``/*VARSECTION*/`` or ``/*INCLUDESECTION*/``: + +.. code-block:: Nim + {.emit: """/*TYPESECTION*/ + struct Vector3 { + public: + Vector3(): x(5) {} + Vector3(float x_): x(x_) {} + float x; + }; + """.} + + type Vector3 {.importcpp: "Vector3", nodecl} = object + x: cfloat + + proc constructVector3(a: cfloat): Vector3 {.importcpp: "Vector3(@)", nodecl} + + +ImportCpp pragma +---------------- + +**Note**: `c2nim <c2nim.html>`_ can parse a large subset of C++ and knows +about the ``importcpp`` pragma pattern language. It is not necessary +to know all the details described here. + + +Similar to the `importc pragma for C +<#foreign-function-interface-importc-pragma>`_, the +``importcpp`` pragma can be used to import `C++`:idx: methods or C++ symbols +in general. The generated code then uses the C++ method calling +syntax: ``obj->method(arg)``. In combination with the ``header`` and ``emit`` +pragmas this allows *sloppy* interfacing with libraries written in C++: + +.. code-block:: Nim + # Horrible example of how to interface with a C++ engine ... ;-) + + {.link: "/usr/lib/libIrrlicht.so".} + + {.emit: """ + using namespace irr; + using namespace core; + using namespace scene; + using namespace video; + using namespace io; + using namespace gui; + """.} + + const + irr = "<irrlicht/irrlicht.h>" + + type + IrrlichtDeviceObj {.header: irr, + importcpp: "IrrlichtDevice".} = object + IrrlichtDevice = ptr IrrlichtDeviceObj + + proc createDevice(): IrrlichtDevice {. + header: irr, importcpp: "createDevice(@)".} + proc run(device: IrrlichtDevice): bool {. + header: irr, importcpp: "#.run(@)".} + +The compiler needs to be told to generate C++ (command ``cpp``) for +this to work. The conditional symbol ``cpp`` is defined when the compiler +emits C++ code. + + +Namespaces +~~~~~~~~~~ + +The *sloppy interfacing* example uses ``.emit`` to produce ``using namespace`` +declarations. It is usually much better to instead refer to the imported name +via the ``namespace::identifier`` notation: + +.. code-block:: nim + type + IrrlichtDeviceObj {.header: irr, + importcpp: "irr::IrrlichtDevice".} = object + + +Importcpp for enums +~~~~~~~~~~~~~~~~~~~ + +When ``importcpp`` is applied to an enum type the numerical enum values are +annotated with the C++ enum type, like in this example: ``((TheCppEnum)(3))``. +(This turned out to be the simplest way to implement it.) + + +Importcpp for procs +~~~~~~~~~~~~~~~~~~~ + +Note that the ``importcpp`` variant for procs uses a somewhat cryptic pattern +language for maximum flexibility: + +- A hash ``#`` symbol is replaced by the first or next argument. +- A dot following the hash ``#.`` indicates that the call should use C++'s dot + or arrow notation. +- An at symbol ``@`` is replaced by the remaining arguments, separated by + commas. + +For example: + +.. code-block:: nim + proc cppMethod(this: CppObj, a, b, c: cint) {.importcpp: "#.CppMethod(@)".} + var x: ptr CppObj + cppMethod(x[], 1, 2, 3) + +Produces: + +.. code-block:: C + x->CppMethod(1, 2, 3) + +As a special rule to keep backwards compatibility with older versions of the +``importcpp`` pragma, if there is no special pattern +character (any of ``# ' @``) at all, C++'s +dot or arrow notation is assumed, so the above example can also be written as: + +.. code-block:: nim + proc cppMethod(this: CppObj, a, b, c: cint) {.importcpp: "CppMethod".} + +Note that the pattern language naturally also covers C++'s operator overloading +capabilities: + +.. code-block:: nim + proc vectorAddition(a, b: Vec3): Vec3 {.importcpp: "# + #".} + proc dictLookup(a: Dict, k: Key): Value {.importcpp: "#[#]".} + + +- An apostrophe ``'`` followed by an integer ``i`` in the range 0..9 + is replaced by the i'th parameter *type*. The 0th position is the result + type. This can be used to pass types to C++ function templates. Between + the ``'`` and the digit an asterisk can be used to get to the base type + of the type. (So it "takes away a star" from the type; ``T*`` becomes ``T``.) + Two stars can be used to get to the element type of the element type etc. + +For example: + +.. code-block:: nim + + type Input {.importcpp: "System::Input".} = object + proc getSubsystem*[T](): ptr T {.importcpp: "SystemManager::getSubsystem<'*0>()", nodecl.} + + let x: ptr Input = getSubsystem[Input]() + +Produces: + +.. code-block:: C + x = SystemManager::getSubsystem<System::Input>() + + +- ``#@`` is a special case to support a ``cnew`` operation. It is required so + that the call expression is inlined directly, without going through a + temporary location. This is only required to circumvent a limitation of the + current code generator. + +For example C++'s ``new`` operator can be "imported" like this: + +.. code-block:: nim + proc cnew*[T](x: T): ptr T {.importcpp: "(new '*0#@)", nodecl.} + + # constructor of 'Foo': + proc constructFoo(a, b: cint): Foo {.importcpp: "Foo(@)".} + + let x = cnew constructFoo(3, 4) + +Produces: + +.. code-block:: C + x = new Foo(3, 4) + +However, depending on the use case ``new Foo`` can also be wrapped like this +instead: + +.. code-block:: nim + proc newFoo(a, b: cint): ptr Foo {.importcpp: "new Foo(@)".} + + let x = newFoo(3, 4) + + +Wrapping constructors +~~~~~~~~~~~~~~~~~~~~~ + +Sometimes a C++ class has a private copy constructor and so code like +``Class c = Class(1,2);`` must not be generated but instead ``Class c(1,2);``. +For this purpose the Nim proc that wraps a C++ constructor needs to be +annotated with the `constructor`:idx: pragma. This pragma also helps to generate +faster C++ code since construction then doesn't invoke the copy constructor: + +.. code-block:: nim + # a better constructor of 'Foo': + proc constructFoo(a, b: cint): Foo {.importcpp: "Foo(@)", constructor.} + + +Wrapping destructors +~~~~~~~~~~~~~~~~~~~~ + +Since Nim generates C++ directly, any destructor is called implicitly by the +C++ compiler at the scope exits. This means that often one can get away with +not wrapping the destructor at all! However when it needs to be invoked +explicitly, it needs to be wrapped. The pattern language provides +everything that is required: + +.. code-block:: nim + proc destroyFoo(this: var Foo) {.importcpp: "#.~Foo()".} + + +Importcpp for objects +~~~~~~~~~~~~~~~~~~~~~ + +Generic ``importcpp``'ed objects are mapped to C++ templates. This means that +you can import C++'s templates rather easily without the need for a pattern +language for object types: + +.. code-block:: nim + type + StdMap {.importcpp: "std::map", header: "<map>".} [K, V] = object + proc `[]=`[K, V](this: var StdMap[K, V]; key: K; val: V) {. + importcpp: "#[#] = #", header: "<map>".} + + var x: StdMap[cint, cdouble] + x[6] = 91.4 + + +Produces: + +.. code-block:: C + std::map<int, double> x; + x[6] = 91.4; + + +- If more precise control is needed, the apostrophe ``'`` can be used in the + supplied pattern to denote the concrete type parameters of the generic type. + See the usage of the apostrophe operator in proc patterns for more details. + +.. code-block:: nim + + type + VectorIterator {.importcpp: "std::vector<'0>::iterator".} [T] = object + + var x: VectorIterator[cint] + + +Produces: + +.. code-block:: C + + std::vector<int>::iterator x; + + +ImportObjC pragma +----------------- +Similar to the `importc pragma for C +<#foreign-function-interface-importc-pragma>`_, the ``importobjc`` pragma can +be used to import `Objective C`:idx: methods. The generated code then uses the +Objective C method calling syntax: ``[obj method param1: arg]``. +In addition with the ``header`` and ``emit`` pragmas this +allows *sloppy* interfacing with libraries written in Objective C: + +.. code-block:: Nim + # horrible example of how to interface with GNUStep ... + + {.passL: "-lobjc".} + {.emit: """ + #include <objc/Object.h> + @interface Greeter:Object + { + } + + - (void)greet:(long)x y:(long)dummy; + @end + + #include <stdio.h> + @implementation Greeter + + - (void)greet:(long)x y:(long)dummy + { + printf("Hello, World!\n"); + } + @end + + #include <stdlib.h> + """.} + + type + Id {.importc: "id", header: "<objc/Object.h>", final.} = distinct int + + proc newGreeter: Id {.importobjc: "Greeter new", nodecl.} + proc greet(self: Id, x, y: int) {.importobjc: "greet", nodecl.} + proc free(self: Id) {.importobjc: "free", nodecl.} + + var g = newGreeter() + g.greet(12, 34) + g.free() + +The compiler needs to be told to generate Objective C (command ``objc``) for +this to work. The conditional symbol ``objc`` is defined when the compiler +emits Objective C code. + + +CodegenDecl pragma +------------------ + +The ``codegenDecl`` pragma can be used to directly influence Nim's code +generator. It receives a format string that determines how the variable +or proc is declared in the generated code. + +For variables $1 in the format string represents the type of the variable +and $2 is the name of the variable. + +The following Nim code: + +.. code-block:: nim + var + a {.codegenDecl: "$# progmem $#".}: int + +will generate this C code: + +.. code-block:: c + int progmem a + +For procedures $1 is the return type of the procedure, $2 is the name of +the procedure and $3 is the parameter list. + +The following nim code: + +.. code-block:: nim + proc myinterrupt() {.codegenDecl: "__interrupt $# $#$#".} = + echo "realistic interrupt handler" + +will generate this code: + +.. code-block:: c + __interrupt void myinterrupt() + + +InjectStmt pragma +----------------- + +The ``injectStmt`` pragma can be used to inject a statement before every +other statement in the current module. It is only supposed to be used for +debugging: + +.. code-block:: nim + {.injectStmt: gcInvariants().} + + # ... complex code here that produces crashes ... + +compile time define pragmas +--------------------------- + +The pragmas listed here can be used to optionally accept values from +the -d/--define option at compile time. + +The implementation currently provides the following possible options (various +others may be added later). + +================= ============================================ +pragma description +================= ============================================ +`intdefine`:idx: Reads in a build-time define as an integer +`strdefine`:idx: Reads in a build-time define as a string +================= ============================================ + +.. code-block:: nim + const FooBar {.intdefine.}: int = 5 + echo FooBar + +:: + nim c -d:FooBar=42 foobar.c + +In the above example, providing the -d flag causes the symbol +``FooBar`` to be overwritten at compile time, printing out 42. If the +``-d:FooBar=42`` were to be omitted, the default value of 5 would be +used. + + +Custom annotations +------------------ +It is possible to define custom typed pragmas. Custom pragmas do not effect +code generation directly, but their presence can be detected by macros. +Custom pragmas are defined using templates annotated with pragma ``pragma``: + +.. code-block:: nim + template dbTable(name: string, table_space: string = "") {.pragma.} + template dbKey(name: string = "", primary_key: bool = false) {.pragma.} + template dbForeignKey(t: type) {.pragma.} + template dbIgnore {.pragma.} + + +Consider stylized example of possible Object Relation Mapping (ORM) implementation: + +.. code-block:: nim + const tblspace {.strdefine.} = "dev" # switch for dev, test and prod environments + + type + User {.dbTable("users", tblspace).} = object + id {.dbKey(primary_key = true).}: int + name {.dbKey"full_name".}: string + is_cached {.dbIgnore.}: bool + age: int + + UserProfile {.dbTable("profiles", tblspace).} = object + id {.dbKey(primary_key = true).}: int + user_id {.dbForeignKey: User.}: int + read_access: bool + write_access: bool + admin_acess: bool + +In this example custom pragmas are used to describe how Nim objects are +mapped to the schema of the relational database. Custom pragmas can have +zero or more arguments. In order to pass multiple arguments use one of +template call syntaxes. All arguments are typed and follow standard +overload resolution rules for templates. Therefore, it is possible to have +default values for arguments, pass by name, varargs, etc. + +Custom pragmas can be used in all locations where ordinary pragmas can be +specified. It is possible to annotate procs, templates, type and variable +definitions, statements, etc. + +Macros module includes helpers which can be used to simplify custom pragma +access `hasCustomPragma`, `getCustomPragmaVal`. Please consult macros module +documentation for details. These macros are no magic, they don't do anything +you cannot do yourself by walking AST object representation. + +More examples with custom pragmas: + - Better serialization/deserialization control: + + .. code-block:: nim + type MyObj = object + a {.dontSerialize.}: int + b {.defaultDeserialize: 5.}: int + c {.serializationKey: "_c".}: string + + - Adopting type for gui inspector in a game engine: + + .. code-block:: nim + type MyComponent = object + position {.editable, animatable.}: Vector3 + alpha {.editRange: [0.0..1.0], animatable.}: float32 + + + + +Foreign function interface +========================== + +Nim's `FFI`:idx: (foreign function interface) is extensive and only the +parts that scale to other future backends (like the LLVM/JavaScript backends) +are documented here. + + +Importc pragma +-------------- +The ``importc`` pragma provides a means to import a proc or a variable +from C. The optional argument is a string containing the C identifier. If +the argument is missing, the C name is the Nim identifier *exactly as +spelled*: + +.. code-block:: + proc printf(formatstr: cstring) {.header: "<stdio.h>", importc: "printf", varargs.} + +Note that this pragma is somewhat of a misnomer: Other backends do provide +the same feature under the same name. Also, if one is interfacing with C++ +the `ImportCpp pragma <manual.html#implementation-specific-pragmas-importcpp-pragma>`_ and +interfacing with Objective-C the `ImportObjC pragma +<manual.html#implementation-specific-pragmas-importobjc-pragma>`_ can be used. + +The string literal passed to ``importc`` can be a format string: + +.. code-block:: Nim + proc p(s: cstring) {.importc: "prefix$1".} + +In the example the external name of ``p`` is set to ``prefixp``. Only ``$1`` +is available and a literal dollar sign must be written as ``$$``. + + +Exportc pragma +-------------- +The ``exportc`` pragma provides a means to export a type, a variable, or a +procedure to C. Enums and constants can't be exported. The optional argument +is a string containing the C identifier. If the argument is missing, the C +name is the Nim identifier *exactly as spelled*: + +.. code-block:: Nim + proc callme(formatstr: cstring) {.exportc: "callMe", varargs.} + +Note that this pragma is somewhat of a misnomer: Other backends do provide +the same feature under the same name. + +The string literal passed to ``exportc`` can be a format string: + +.. code-block:: Nim + proc p(s: string) {.exportc: "prefix$1".} = + echo s + +In the example the external name of ``p`` is set to ``prefixp``. Only ``$1`` +is available and a literal dollar sign must be written as ``$$``. + + + +Extern pragma +------------- +Like ``exportc`` or ``importc``, the ``extern`` pragma affects name +mangling. The string literal passed to ``extern`` can be a format string: + +.. code-block:: Nim + proc p(s: string) {.extern: "prefix$1".} = + echo s + +In the example the external name of ``p`` is set to ``prefixp``. Only ``$1`` +is available and a literal dollar sign must be written as ``$$``. + + + +Bycopy pragma +------------- + +The ``bycopy`` pragma can be applied to an object or tuple type and +instructs the compiler to pass the type by value to procs: + +.. code-block:: nim + type + Vector {.bycopy.} = object + x, y, z: float + + +Byref pragma +------------ + +The ``byref`` pragma can be applied to an object or tuple type and instructs +the compiler to pass the type by reference (hidden pointer) to procs. + + +Varargs pragma +-------------- +The ``varargs`` pragma can be applied to procedures only (and procedure +types). It tells Nim that the proc can take a variable number of parameters +after the last specified parameter. Nim string values will be converted to C +strings automatically: + +.. code-block:: Nim + proc printf(formatstr: cstring) {.nodecl, varargs.} + + printf("hallo %s", "world") # "world" will be passed as C string + + +Union pragma +------------ +The ``union`` pragma can be applied to any ``object`` type. It means all +of the object's fields are overlaid in memory. This produces a ``union`` +instead of a ``struct`` in the generated C/C++ code. The object declaration +then must not use inheritance or any GC'ed memory but this is currently not +checked. + +**Future directions**: GC'ed memory should be allowed in unions and the GC +should scan unions conservatively. + +Packed pragma +------------- +The ``packed`` pragma can be applied to any ``object`` type. It ensures +that the fields of an object are packed back-to-back in memory. It is useful +to store packets or messages from/to network or hardware drivers, and for +interoperability with C. Combining packed pragma with inheritance is not +defined, and it should not be used with GC'ed memory (ref's). + +**Future directions**: Using GC'ed memory in packed pragma will result in +compile-time error. Usage with inheritance should be defined and documented. + +Unchecked pragma +---------------- +The ``unchecked`` pragma can be used to mark a named array as ``unchecked`` +meaning its bounds are not checked. This is often useful to +implement customized flexibly sized arrays. Additionally an unchecked array is +translated into a C array of undetermined size: + +.. code-block:: nim + type + ArrayPart{.unchecked.} = array[0, int] + MySeq = object + len, cap: int + data: ArrayPart + +Produces roughly this C code: + +.. code-block:: C + typedef struct { + NI len; + NI cap; + NI data[]; + } MySeq; + +The base type of the unchecked array may not contain any GC'ed memory but this +is currently not checked. + +**Future directions**: GC'ed memory should be allowed in unchecked arrays and +there should be an explicit annotation of how the GC is to determine the +runtime size of the array. + + +Dynlib pragma for import +------------------------ +With the ``dynlib`` pragma a procedure or a variable can be imported from +a dynamic library (``.dll`` files for Windows, ``lib*.so`` files for UNIX). +The non-optional argument has to be the name of the dynamic library: + +.. code-block:: Nim + proc gtk_image_new(): PGtkWidget + {.cdecl, dynlib: "libgtk-x11-2.0.so", importc.} + +In general, importing a dynamic library does not require any special linker +options or linking with import libraries. This also implies that no *devel* +packages need to be installed. + +The ``dynlib`` import mechanism supports a versioning scheme: + +.. code-block:: nim + proc Tcl_Eval(interp: pTcl_Interp, script: cstring): int {.cdecl, + importc, dynlib: "libtcl(|8.5|8.4|8.3).so.(1|0)".} + +At runtime the dynamic library is searched for (in this order):: + + libtcl.so.1 + libtcl.so.0 + libtcl8.5.so.1 + libtcl8.5.so.0 + libtcl8.4.so.1 + libtcl8.4.so.0 + libtcl8.3.so.1 + libtcl8.3.so.0 + +The ``dynlib`` pragma supports not only constant strings as argument but also +string expressions in general: + +.. code-block:: nim + import os + + proc getDllName: string = + result = "mylib.dll" + if existsFile(result): return + result = "mylib2.dll" + if existsFile(result): return + quit("could not load dynamic library") + + proc myImport(s: cstring) {.cdecl, importc, dynlib: getDllName().} + +**Note**: Patterns like ``libtcl(|8.5|8.4).so`` are only supported in constant +strings, because they are precompiled. + +**Note**: Passing variables to the ``dynlib`` pragma will fail at runtime +because of order of initialization problems. + +**Note**: A ``dynlib`` import can be overridden with +the ``--dynlibOverride:name`` command line option. The Compiler User Guide +contains further information. + + +Dynlib pragma for export +------------------------ + +With the ``dynlib`` pragma a procedure can also be exported to +a dynamic library. The pragma then has no argument and has to be used in +conjunction with the ``exportc`` pragma: + +.. code-block:: Nim + proc exportme(): int {.cdecl, exportc, dynlib.} + +This is only useful if the program is compiled as a dynamic library via the +``--app:lib`` command line option. This pragma only has an effect for the code +generation on the Windows target, so when this pragma is forgotten and the dynamic +library is only tested on Mac and/or Linux, there won't be an error. On Windows +this pragma adds ``__declspec(dllexport)`` to the function declaration. + + + +Threads +======= + +To enable thread support the ``--threads:on`` command line switch needs to +be used. The ``system`` module then contains several threading primitives. +See the `threads <threads.html>`_ and `channels <channels.html>`_ modules +for the low level thread API. There are also high level parallelism constructs +available. See `spawn <#parallel-spawn>`_ for further details. + +Nim's memory model for threads is quite different than that of other common +programming languages (C, Pascal, Java): Each thread has its own (garbage +collected) heap and sharing of memory is restricted to global variables. This +helps to prevent race conditions. GC efficiency is improved quite a lot, +because the GC never has to stop other threads and see what they reference. +Memory allocation requires no lock at all! This design easily scales to massive +multicore processors that are becoming the norm. + + +Thread pragma +------------- + +A proc that is executed as a new thread of execution should be marked by the +``thread`` pragma for reasons of readability. The compiler checks for +violations of the `no heap sharing restriction`:idx:\: This restriction implies +that it is invalid to construct a data structure that consists of memory +allocated from different (thread local) heaps. + +A thread proc is passed to ``createThread`` or ``spawn`` and invoked +indirectly; so the ``thread`` pragma implies ``procvar``. + + +GC safety +--------- + +We call a proc ``p`` `GC safe`:idx: when it doesn't access any global variable +that contains GC'ed memory (``string``, ``seq``, ``ref`` or a closure) either +directly or indirectly through a call to a GC unsafe proc. + +The `gcsafe`:idx: annotation can be used to mark a proc to be gcsafe, +otherwise this property is inferred by the compiler. Note that ``noSideEffect`` +implies ``gcsafe``. The only way to create a thread is via ``spawn`` or +``createThread``. ``spawn`` is usually the preferable method. Either way +the invoked proc must not use ``var`` parameters nor must any of its parameters +contain a ``ref`` or ``closure`` type. This enforces +the *no heap sharing restriction*. + +Routines that are imported from C are always assumed to be ``gcsafe``. +To disable the GC-safety checking the ``--threadAnalysis:off`` command line +switch can be used. This is a temporary workaround to ease the porting effort +from old code to the new threading model. + +To override the compiler's gcsafety analysis a ``{.gcsafe.}`` pragma block can +be used: + +.. code-block:: nim + + var + someGlobal: string = "some string here" + perThread {.threadvar.}: string + + proc setPerThread() = + {.gcsafe.}: + deepCopy(perThread, someGlobal) + + +Future directions: + +- A shared GC'ed heap might be provided. + + +Threadvar pragma +---------------- + +A variable can be marked with the ``threadvar`` pragma, which makes it a +`thread-local`:idx: variable; Additionally, this implies all the effects +of the ``global`` pragma. + +.. code-block:: nim + var checkpoints* {.threadvar.}: seq[string] + +Due to implementation restrictions thread local variables cannot be +initialized within the ``var`` section. (Every thread local variable needs to +be replicated at thread creation.) + + +Threads and exceptions +---------------------- + +The interaction between threads and exceptions is simple: A *handled* exception +in one thread cannot affect any other thread. However, an *unhandled* exception +in one thread terminates the whole *process*! + + + +Parallel & Spawn +================ + +Nim has two flavors of parallelism: +1) `Structured`:idx: parallelism via the ``parallel`` statement. +2) `Unstructured`:idx: parallelism via the standalone ``spawn`` statement. + +Nim has a builtin thread pool that can be used for CPU intensive tasks. For +IO intensive tasks the ``async`` and ``await`` features should be +used instead. Both parallel and spawn need the `threadpool <threadpool.html>`_ +module to work. + +Somewhat confusingly, ``spawn`` is also used in the ``parallel`` statement +with slightly different semantics. ``spawn`` always takes a call expression of +the form ``f(a, ...)``. Let ``T`` be ``f``'s return type. If ``T`` is ``void`` +then ``spawn``'s return type is also ``void`` otherwise it is ``FlowVar[T]``. + +Within a ``parallel`` section sometimes the ``FlowVar[T]`` is eliminated +to ``T``. This happens when ``T`` does not contain any GC'ed memory. +The compiler can ensure the location in ``location = spawn f(...)`` is not +read prematurely within a ``parallel`` section and so there is no need for +the overhead of an indirection via ``FlowVar[T]`` to ensure correctness. + +**Note**: Currently exceptions are not propagated between ``spawn``'ed tasks! + + +Spawn statement +--------------- + +`spawn`:idx: can be used to pass a task to the thread pool: + +.. code-block:: nim + import threadpool + + proc processLine(line: string) = + discard "do some heavy lifting here" + + for x in lines("myinput.txt"): + spawn processLine(x) + sync() + +For reasons of type safety and implementation simplicity the expression +that ``spawn`` takes is restricted: + +* It must be a call expression ``f(a, ...)``. +* ``f`` must be ``gcsafe``. +* ``f`` must not have the calling convention ``closure``. +* ``f``'s parameters may not be of type ``var``. + This means one has to use raw ``ptr``'s for data passing reminding the + programmer to be careful. +* ``ref`` parameters are deeply copied which is a subtle semantic change and + can cause performance problems but ensures memory safety. This deep copy + is performed via ``system.deepCopy`` and so can be overridden. +* For *safe* data exchange between ``f`` and the caller a global ``TChannel`` + needs to be used. However, since spawn can return a result, often no further + communication is required. + + +``spawn`` executes the passed expression on the thread pool and returns +a `data flow variable`:idx: ``FlowVar[T]`` that can be read from. The reading +with the ``^`` operator is **blocking**. However, one can use ``blockUntilAny`` to +wait on multiple flow variables at the same time: + +.. code-block:: nim + import threadpool, ... + + # wait until 2 out of 3 servers received the update: + proc main = + var responses = newSeq[FlowVarBase](3) + for i in 0..2: + responses[i] = spawn tellServer(Update, "key", "value") + var index = blockUntilAny(responses) + assert index >= 0 + responses.del(index) + discard blockUntilAny(responses) + +Data flow variables ensure that no data races +are possible. Due to technical limitations not every type ``T`` is possible in +a data flow variable: ``T`` has to be of the type ``ref``, ``string``, ``seq`` +or of a type that doesn't contain a type that is garbage collected. This +restriction is not hard to work-around in practice. + + + +Parallel statement +------------------ + +Example: + +.. code-block:: nim + :test: "nim c --threads:on $1" + + # Compute PI in an inefficient way + import strutils, math, threadpool + {.experimental: "parallel".} + + proc term(k: float): float = 4 * math.pow(-1, k) / (2*k + 1) + + proc pi(n: int): float = + var ch = newSeq[float](n+1) + parallel: + for k in 0..ch.high: + ch[k] = spawn term(float(k)) + for k in 0..ch.high: + result += ch[k] + + echo formatFloat(pi(5000)) + + +The parallel statement is the preferred mechanism to introduce parallelism +in a Nim program. A subset of the Nim language is valid within a +``parallel`` section. This subset is checked to be free of data races at +compile time. A sophisticated `disjoint checker`:idx: ensures that no data +races are possible even though shared memory is extensively supported! + +The subset is in fact the full language with the following +restrictions / changes: + +* ``spawn`` within a ``parallel`` section has special semantics. +* Every location of the form ``a[i]`` and ``a[i..j]`` and ``dest`` where + ``dest`` is part of the pattern ``dest = spawn f(...)`` has to be + provably disjoint. This is called the *disjoint check*. +* Every other complex location ``loc`` that is used in a spawned + proc (``spawn f(loc)``) has to be immutable for the duration of + the ``parallel`` section. This is called the *immutability check*. Currently + it is not specified what exactly "complex location" means. We need to make + this an optimization! +* Every array access has to be provably within bounds. This is called + the *bounds check*. +* Slices are optimized so that no copy is performed. This optimization is not + yet performed for ordinary slices outside of a ``parallel`` section. + + +Guards and locks +================ + +Apart from ``spawn`` and ``parallel`` Nim also provides all the common low level +concurrency mechanisms like locks, atomic intrinsics or condition variables. + +Nim significantly improves on the safety of these features via additional +pragmas: + +1) A `guard`:idx: annotation is introduced to prevent data races. +2) Every access of a guarded memory location needs to happen in an + appropriate `locks`:idx: statement. +3) Locks and routines can be annotated with `lock levels`:idx: to prevent + deadlocks at compile time. + + +Guards and the locks section +---------------------------- + +Protecting global variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Object fields and global variables can be annotated via a ``guard`` pragma: + +.. code-block:: nim + var glock: TLock + var gdata {.guard: glock.}: int + +The compiler then ensures that every access of ``gdata`` is within a ``locks`` +section: + +.. code-block:: nim + proc invalid = + # invalid: unguarded access: + echo gdata + + proc valid = + # valid access: + {.locks: [glock].}: + echo gdata + +Top level accesses to ``gdata`` are always allowed so that it can be initialized +conveniently. It is *assumed* (but not enforced) that every top level statement +is executed before any concurrent action happens. + +The ``locks`` section deliberately looks ugly because it has no runtime +semantics and should not be used directly! It should only be used in templates +that also implement some form of locking at runtime: + +.. code-block:: nim + template lock(a: TLock; body: untyped) = + pthread_mutex_lock(a) + {.locks: [a].}: + try: + body + finally: + pthread_mutex_unlock(a) + + +The guard does not need to be of any particular type. It is flexible enough to +model low level lockfree mechanisms: + +.. code-block:: nim + var dummyLock {.compileTime.}: int + var atomicCounter {.guard: dummyLock.}: int + + template atomicRead(x): untyped = + {.locks: [dummyLock].}: + memoryReadBarrier() + x + + echo atomicRead(atomicCounter) + + +The ``locks`` pragma takes a list of lock expressions ``locks: [a, b, ...]`` +in order to support *multi lock* statements. Why these are essential is +explained in the `lock levels <#guards-and-locks-lock-levels>`_ section. + + +Protecting general locations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``guard`` annotation can also be used to protect fields within an object. +The guard then needs to be another field within the same object or a +global variable. + +Since objects can reside on the heap or on the stack this greatly enhances the +expressivity of the language: + +.. code-block:: nim + type + ProtectedCounter = object + v {.guard: L.}: int + L: TLock + + proc incCounters(counters: var openArray[ProtectedCounter]) = + for i in 0..counters.high: + lock counters[i].L: + inc counters[i].v + +The access to field ``x.v`` is allowed since its guard ``x.L`` is active. +After template expansion, this amounts to: + +.. code-block:: nim + proc incCounters(counters: var openArray[ProtectedCounter]) = + for i in 0..counters.high: + pthread_mutex_lock(counters[i].L) + {.locks: [counters[i].L].}: + try: + inc counters[i].v + finally: + pthread_mutex_unlock(counters[i].L) + +There is an analysis that checks that ``counters[i].L`` is the lock that +corresponds to the protected location ``counters[i].v``. This analysis is called +`path analysis`:idx: because it deals with paths to locations +like ``obj.field[i].fieldB[j]``. + +The path analysis is **currently unsound**, but that doesn't make it useless. +Two paths are considered equivalent if they are syntactically the same. + +This means the following compiles (for now) even though it really should not: + +.. code-block:: nim + {.locks: [a[i].L].}: + inc i + access a[i].v + + + +Lock levels +----------- + +Lock levels are used to enforce a global locking order in order to prevent +deadlocks at compile-time. A lock level is an constant integer in the range +0..1_000. Lock level 0 means that no lock is acquired at all. + +If a section of code holds a lock of level ``M`` than it can also acquire any +lock of level ``N < M``. Another lock of level ``M`` cannot be acquired. Locks +of the same level can only be acquired *at the same time* within a +single ``locks`` section: + +.. code-block:: nim + var a, b: TLock[2] + var x: TLock[1] + # invalid locking order: TLock[1] cannot be acquired before TLock[2]: + {.locks: [x].}: + {.locks: [a].}: + ... + # valid locking order: TLock[2] acquired before TLock[1]: + {.locks: [a].}: + {.locks: [x].}: + ... + + # invalid locking order: TLock[2] acquired before TLock[2]: + {.locks: [a].}: + {.locks: [b].}: + ... + + # valid locking order, locks of the same level acquired at the same time: + {.locks: [a, b].}: + ... + + +Here is how a typical multilock statement can be implemented in Nim. Note how +the runtime check is required to ensure a global ordering for two locks ``a`` +and ``b`` of the same lock level: + +.. code-block:: nim + template multilock(a, b: ptr TLock; body: untyped) = + if cast[ByteAddress](a) < cast[ByteAddress](b): + pthread_mutex_lock(a) + pthread_mutex_lock(b) + else: + pthread_mutex_lock(b) + pthread_mutex_lock(a) + {.locks: [a, b].}: + try: + body + finally: + pthread_mutex_unlock(a) + pthread_mutex_unlock(b) + + +Whole routines can also be annotated with a ``locks`` pragma that takes a lock +level. This then means that the routine may acquire locks of up to this level. +This is essential so that procs can be called within a ``locks`` section: + +.. code-block:: nim + proc p() {.locks: 3.} = discard + + var a: TLock[4] + {.locks: [a].}: + # p's locklevel (3) is strictly less than a's (4) so the call is allowed: + p() + + +As usual ``locks`` is an inferred effect and there is a subtype +relation: ``proc () {.locks: N.}`` is a subtype of ``proc () {.locks: M.}`` +iff (M <= N). + +The ``locks`` pragma can also take the special value ``"unknown"``. This +is useful in the context of dynamic method dispatching. In the following +example, the compiler can infer a lock level of 0 for the ``base`` case. +However, one of the overloaded methods calls a procvar which is +potentially locking. Thus, the lock level of calling ``g.testMethod`` +cannot be inferred statically, leading to compiler warnings. By using +``{.locks: "unknown".}``, the base method can be marked explicitly as +having unknown lock level as well: + +.. code-block:: nim + type SomeBase* = ref object of RootObj + type SomeDerived* = ref object of SomeBase + memberProc*: proc () + + method testMethod(g: SomeBase) {.base, locks: "unknown".} = discard + method testMethod(g: SomeDerived) = + if g.memberProc != nil: + g.memberProc() + + +Taint mode +========== + +The Nim compiler and most parts of the standard library support +a taint mode. Input strings are declared with the `TaintedString`:idx: +string type declared in the ``system`` module. + +If the taint mode is turned on (via the ``--taintMode:on`` command line +option) it is a distinct string type which helps to detect input +validation errors: + +.. code-block:: nim + echo "your name: " + var name: TaintedString = stdin.readline + # it is safe here to output the name without any input validation, so + # we simply convert `name` to string to make the compiler happy: + echo "hi, ", name.string + +If the taint mode is turned off, ``TaintedString`` is simply an alias for +``string``. diff --git a/doc/manual/about.txt b/doc/manual/about.txt deleted file mode 100644 index 58e3baa85..000000000 --- a/doc/manual/about.txt +++ /dev/null @@ -1,37 +0,0 @@ -About this document -=================== - -**Note**: This document is a draft! Several of Nim's features may need more -precise wording. This manual is constantly evolving until the 1.0 release and is -not to be considered as the final proper specification. - -This document describes the lexis, the syntax, and the semantics of Nim. - -The language constructs are explained using an extended BNF, in which ``(a)*`` -means 0 or more ``a``'s, ``a+`` means 1 or more ``a``'s, and ``(a)?`` means an -optional *a*. Parentheses may be used to group elements. - -``&`` is the lookahead operator; ``&a`` means that an ``a`` is expected but -not consumed. It will be consumed in the following rule. - -The ``|``, ``/`` symbols are used to mark alternatives and have the lowest -precedence. ``/`` is the ordered choice that requires the parser to try the -alternatives in the given order. ``/`` is often used to ensure the grammar -is not ambiguous. - -Non-terminals start with a lowercase letter, abstract terminal symbols are in -UPPERCASE. Verbatim terminal symbols (including keywords) are quoted -with ``'``. An example:: - - ifStmt = 'if' expr ':' stmts ('elif' expr ':' stmts)* ('else' stmts)? - -The binary ``^*`` operator is used as a shorthand for 0 or more occurrences -separated by its second argument; likewise ``^+`` means 1 or more -occurrences: ``a ^+ b`` is short for ``a (b a)*`` -and ``a ^* b`` is short for ``(a (b a)*)?``. Example:: - - arrayConstructor = '[' expr ^* ',' ']' - -Other parts of Nim - like scoping rules or runtime semantics are only -described in the, more easily comprehensible, informal manner for now. - diff --git a/doc/manual/compiler_msgs.txt b/doc/manual/compiler_msgs.txt deleted file mode 100644 index 3cf8417b0..000000000 --- a/doc/manual/compiler_msgs.txt +++ /dev/null @@ -1,7 +0,0 @@ -Compiler Messages -================= - -The Nim compiler emits different kinds of messages: `hint`:idx:, -`warning`:idx:, and `error`:idx: messages. An *error* message is emitted if -the compiler encounters any static error. - diff --git a/doc/manual/definitions.txt b/doc/manual/definitions.txt deleted file mode 100644 index 21954fb78..000000000 --- a/doc/manual/definitions.txt +++ /dev/null @@ -1,49 +0,0 @@ - -Definitions -=========== - -A Nim program specifies a computation that acts on a memory consisting of -components called `locations`:idx:. A variable is basically a name for a -location. Each variable and location is of a certain `type`:idx:. The -variable's type is called `static type`:idx:, the location's type is called -`dynamic type`:idx:. If the static type is not the same as the dynamic type, -it is a super-type or subtype of the dynamic type. - -An `identifier`:idx: is a symbol declared as a name for a variable, type, -procedure, etc. The region of the program over which a declaration applies is -called the `scope`:idx: of the declaration. Scopes can be nested. The meaning -of an identifier is determined by the smallest enclosing scope in which the -identifier is declared unless overloading resolution rules suggest otherwise. - -An expression specifies a computation that produces a value or location. -Expressions that produce locations are called `l-values`:idx:. An l-value -can denote either a location or the value the location contains, depending on -the context. Expressions whose values can be determined statically are called -`constant expressions`:idx:; they are never l-values. - -A `static error`:idx: is an error that the implementation detects before -program execution. Unless explicitly classified, an error is a static error. - -A `checked runtime error`:idx: is an error that the implementation detects -and reports at runtime. The method for reporting such errors is via *raising -exceptions* or *dying with a fatal error*. However, the implementation -provides a means to disable these runtime checks. See the section pragmas_ -for details. - -Whether a checked runtime error results in an exception or in a fatal error at -runtime is implementation specific. Thus the following program is always -invalid: - -.. code-block:: nim - var a: array[0..1, char] - let i = 5 - try: - a[i] = 'N' - except IndexError: - echo "invalid index" - -An `unchecked runtime error`:idx: is an error that is not guaranteed to be -detected, and can cause the subsequent behavior of the computation to -be arbitrary. Unchecked runtime errors cannot occur if only `safe`:idx: -language features are used. - diff --git a/doc/manual/effects.txt b/doc/manual/effects.txt deleted file mode 100644 index 254a43fbb..000000000 --- a/doc/manual/effects.txt +++ /dev/null @@ -1,129 +0,0 @@ -Effect system -============= - -Exception tracking ------------------- - -Nim supports exception tracking. The `raises`:idx: pragma can be used -to explicitly define which exceptions a proc/iterator/method/converter is -allowed to raise. The compiler verifies this: - -.. code-block:: nim - proc p(what: bool) {.raises: [IOError, OSError].} = - if what: raise newException(IOError, "IO") - else: raise newException(OSError, "OS") - -An empty ``raises`` list (``raises: []``) means that no exception may be raised: - -.. code-block:: nim - proc p(): bool {.raises: [].} = - try: - unsafeCall() - result = true - except: - result = false - - -A ``raises`` list can also be attached to a proc type. This affects type -compatibility: - -.. code-block:: nim - type - Callback = proc (s: string) {.raises: [IOError].} - var - c: Callback - - proc p(x: string) = - raise newException(OSError, "OS") - - c = p # type error - - -For a routine ``p`` the compiler uses inference rules to determine the set of -possibly raised exceptions; the algorithm operates on ``p``'s call graph: - -1. Every indirect call via some proc type ``T`` is assumed to - raise ``system.Exception`` (the base type of the exception hierarchy) and - thus any exception unless ``T`` has an explicit ``raises`` list. - However if the call is of the form ``f(...)`` where ``f`` is a parameter - of the currently analysed routine it is ignored. The call is optimistically - assumed to have no effect. Rule 2 compensates for this case. -2. Every expression of some proc type within a call that is not a call - itself (and not nil) is assumed to be called indirectly somehow and thus - its raises list is added to ``p``'s raises list. -3. Every call to a proc ``q`` which has an unknown body (due to a forward - declaration or an ``importc`` pragma) is assumed to - raise ``system.Exception`` unless ``q`` has an explicit ``raises`` list. -4. Every call to a method ``m`` is assumed to - raise ``system.Exception`` unless ``m`` has an explicit ``raises`` list. -5. For every other call the analysis can determine an exact ``raises`` list. -6. For determining a ``raises`` list, the ``raise`` and ``try`` statements - of ``p`` are taken into consideration. - -Rules 1-2 ensure the following works: - -.. code-block:: nim - proc noRaise(x: proc()) {.raises: [].} = - # unknown call that might raise anything, but valid: - x() - - proc doRaise() {.raises: [IOError].} = - raise newException(IOError, "IO") - - proc use() {.raises: [].} = - # doesn't compile! Can raise IOError! - noRaise(doRaise) - -So in many cases a callback does not cause the compiler to be overly -conservative in its effect analysis. - - -Tag tracking ------------- - -The exception tracking is part of Nim's `effect system`:idx:. Raising an -exception is an *effect*. Other effects can also be defined. A user defined -effect is a means to *tag* a routine and to perform checks against this tag: - -.. code-block:: nim - type IO = object ## input/output effect - proc readLine(): string {.tags: [IO].} - - proc no_IO_please() {.tags: [].} = - # the compiler prevents this: - let x = readLine() - -A tag has to be a type name. A ``tags`` list - like a ``raises`` list - can -also be attached to a proc type. This affects type compatibility. - -The inference for tag tracking is analogous to the inference for -exception tracking. - - -Read/Write tracking -------------------- - -**Note**: Read/write tracking is not yet implemented! - -The inference for read/write tracking is analogous to the inference for -exception tracking. - - -Effects pragma --------------- - -The ``effects`` pragma has been designed to assist the programmer with the -effects analysis. It is a statement that makes the compiler output all inferred -effects up to the ``effects``'s position: - -.. code-block:: nim - proc p(what: bool) = - if what: - raise newException(IOError, "IO") - {.effects.} - else: - raise newException(OSError, "OS") - -The compiler produces a hint message that ``IOError`` can be raised. ``OSError`` -is not listed as it cannot be raised in the branch the ``effects`` pragma -appears in. diff --git a/doc/manual/exceptions.txt b/doc/manual/exceptions.txt deleted file mode 100644 index 0f1240a4a..000000000 --- a/doc/manual/exceptions.txt +++ /dev/null @@ -1,158 +0,0 @@ -Exception handling -================== - -Try statement -------------- - -Example: - -.. code-block:: nim - # read the first two lines of a text file that should contain numbers - # and tries to add them - var - f: File - if open(f, "numbers.txt"): - try: - var a = readLine(f) - var b = readLine(f) - echo "sum: " & $(parseInt(a) + parseInt(b)) - except OverflowError: - echo "overflow!" - except ValueError: - echo "could not convert string to integer" - except IOError: - echo "IO error!" - except: - echo "Unknown exception!" - finally: - close(f) - - -The statements after the ``try`` are executed in sequential order unless -an exception ``e`` is raised. If the exception type of ``e`` matches any -listed in an ``except`` clause the corresponding statements are executed. -The statements following the ``except`` clauses are called -`exception handlers`:idx:. - -The empty `except`:idx: clause is executed if there is an exception that is -not listed otherwise. It is similar to an ``else`` clause in ``if`` statements. - -If there is a `finally`:idx: clause, it is always executed after the -exception handlers. - -The exception is *consumed* in an exception handler. However, an -exception handler may raise another exception. If the exception is not -handled, it is propagated through the call stack. This means that often -the rest of the procedure - that is not within a ``finally`` clause - -is not executed (if an exception occurs). - - -Try expression --------------- - -Try can also be used as an expression; the type of the ``try`` branch then -needs to fit the types of ``except`` branches, but the type of the ``finally`` -branch always has to be ``void``: - -.. code-block:: nim - let x = try: parseInt("133a") - except: -1 - finally: echo "hi" - - -To prevent confusing code there is a parsing limitation; if the ``try`` -follows a ``(`` it has to be written as a one liner: - -.. code-block:: nim - let x = (try: parseInt("133a") except: -1) - - -Except clauses --------------- - -Within an ``except`` clause, it is possible to use -``getCurrentException`` to retrieve the exception that has been -raised: - -.. code-block:: nim - try: - # ... - except IOError: - let e = getCurrentException() - # Now use "e" - -Note that ``getCurrentException`` always returns a ``ref Exception`` -type. If a variable of the proper type is needed (in the example -above, ``IOError``), one must convert it explicitly: - -.. code-block:: nim - try: - # ... - except IOError: - let e = (ref IOError)(getCurrentException()) - # "e" is now of the proper type - -However, this is seldom needed. The most common case is to extract an -error message from ``e``, and for such situations it is enough to use -``getCurrentExceptionMsg``: - -.. code-block:: nim - try: - # ... - except IOError: - echo "I/O error: " & getCurrentExceptionMsg() - - -Defer statement ---------------- - -Instead of a ``try finally`` statement a ``defer`` statement can be used. - -Any statements following the ``defer`` in the current block will be considered -to be in an implicit try block: - -.. code-block:: nim - var f = open("numbers.txt") - defer: close(f) - f.write "abc" - f.write "def" - -Is rewritten to: - -.. code-block:: nim - var f = open("numbers.txt") - try: - f.write "abc" - f.write "def" - finally: - close(f) - -Top level ``defer`` statements are not supported -since it's unclear what such a statement should refer to. - - -Raise statement ---------------- - -Example: - -.. code-block:: nim - raise newEOS("operating system failed") - -Apart from built-in operations like array indexing, memory allocation, etc. -the ``raise`` statement is the only way to raise an exception. - -.. XXX document this better! - -If no exception name is given, the current exception is `re-raised`:idx:. The -`ReraiseError`:idx: exception is raised if there is no exception to -re-raise. It follows that the ``raise`` statement *always* raises an -exception. - - -Exception hierarchy -------------------- - -The exception tree is defined in the `system <system.html>`_ module: - -.. include:: ../exception_hierarchy_fragment.txt diff --git a/doc/manual/ffi.txt b/doc/manual/ffi.txt deleted file mode 100644 index 67a4c7caa..000000000 --- a/doc/manual/ffi.txt +++ /dev/null @@ -1,229 +0,0 @@ -Foreign function interface -========================== - -Nim's `FFI`:idx: (foreign function interface) is extensive and only the -parts that scale to other future backends (like the LLVM/JavaScript backends) -are documented here. - - -Importc pragma --------------- -The ``importc`` pragma provides a means to import a proc or a variable -from C. The optional argument is a string containing the C identifier. If -the argument is missing, the C name is the Nim identifier *exactly as -spelled*: - -.. code-block:: - proc printf(formatstr: cstring) {.header: "<stdio.h>", importc: "printf", varargs.} - -Note that this pragma is somewhat of a misnomer: Other backends do provide -the same feature under the same name. Also, if one is interfacing with C++ -the `ImportCpp pragma <manual.html#implementation-specific-pragmas-importcpp-pragma>`_ and -interfacing with Objective-C the `ImportObjC pragma -<manual.html#implementation-specific-pragmas-importobjc-pragma>`_ can be used. - -The string literal passed to ``importc`` can be a format string: - -.. code-block:: Nim - proc p(s: cstring) {.importc: "prefix$1".} - -In the example the external name of ``p`` is set to ``prefixp``. Only ``$1`` -is available and a literal dollar sign must be written as ``$$``. - - -Exportc pragma --------------- -The ``exportc`` pragma provides a means to export a type, a variable, or a -procedure to C. Enums and constants can't be exported. The optional argument -is a string containing the C identifier. If the argument is missing, the C -name is the Nim identifier *exactly as spelled*: - -.. code-block:: Nim - proc callme(formatstr: cstring) {.exportc: "callMe", varargs.} - -Note that this pragma is somewhat of a misnomer: Other backends do provide -the same feature under the same name. - -The string literal passed to ``exportc`` can be a format string: - -.. code-block:: Nim - proc p(s: string) {.exportc: "prefix$1".} = - echo s - -In the example the external name of ``p`` is set to ``prefixp``. Only ``$1`` -is available and a literal dollar sign must be written as ``$$``. - - - -Extern pragma -------------- -Like ``exportc`` or ``importc``, the ``extern`` pragma affects name -mangling. The string literal passed to ``extern`` can be a format string: - -.. code-block:: Nim - proc p(s: string) {.extern: "prefix$1".} = - echo s - -In the example the external name of ``p`` is set to ``prefixp``. Only ``$1`` -is available and a literal dollar sign must be written as ``$$``. - - - -Bycopy pragma -------------- - -The ``bycopy`` pragma can be applied to an object or tuple type and -instructs the compiler to pass the type by value to procs: - -.. code-block:: nim - type - Vector {.bycopy, pure.} = object - x, y, z: float - - -Byref pragma ------------- - -The ``byref`` pragma can be applied to an object or tuple type and instructs -the compiler to pass the type by reference (hidden pointer) to procs. - - -Varargs pragma --------------- -The ``varargs`` pragma can be applied to procedures only (and procedure -types). It tells Nim that the proc can take a variable number of parameters -after the last specified parameter. Nim string values will be converted to C -strings automatically: - -.. code-block:: Nim - proc printf(formatstr: cstring) {.nodecl, varargs.} - - printf("hallo %s", "world") # "world" will be passed as C string - - -Union pragma ------------- -The ``union`` pragma can be applied to any ``object`` type. It means all -of the object's fields are overlaid in memory. This produces a ``union`` -instead of a ``struct`` in the generated C/C++ code. The object declaration -then must not use inheritance or any GC'ed memory but this is currently not -checked. - -**Future directions**: GC'ed memory should be allowed in unions and the GC -should scan unions conservatively. - -Packed pragma -------------- -The ``packed`` pragma can be applied to any ``object`` type. It ensures -that the fields of an object are packed back-to-back in memory. It is useful -to store packets or messages from/to network or hardware drivers, and for -interoperability with C. Combining packed pragma with inheritance is not -defined, and it should not be used with GC'ed memory (ref's). - -**Future directions**: Using GC'ed memory in packed pragma will result in -compile-time error. Usage with inheritance should be defined and documented. - -Unchecked pragma ----------------- -The ``unchecked`` pragma can be used to mark a named array as ``unchecked`` -meaning its bounds are not checked. This is often useful to -implement customized flexibly sized arrays. Additionally an unchecked array is -translated into a C array of undetermined size: - -.. code-block:: nim - type - ArrayPart{.unchecked.} = array[0, int] - MySeq = object - len, cap: int - data: ArrayPart - -Produces roughly this C code: - -.. code-block:: C - typedef struct { - NI len; - NI cap; - NI data[]; - } MySeq; - -The base type of the unchecked array may not contain any GC'ed memory but this -is currently not checked. - -**Future directions**: GC'ed memory should be allowed in unchecked arrays and -there should be an explicit annotation of how the GC is to determine the -runtime size of the array. - - -Dynlib pragma for import ------------------------- -With the ``dynlib`` pragma a procedure or a variable can be imported from -a dynamic library (``.dll`` files for Windows, ``lib*.so`` files for UNIX). -The non-optional argument has to be the name of the dynamic library: - -.. code-block:: Nim - proc gtk_image_new(): PGtkWidget - {.cdecl, dynlib: "libgtk-x11-2.0.so", importc.} - -In general, importing a dynamic library does not require any special linker -options or linking with import libraries. This also implies that no *devel* -packages need to be installed. - -The ``dynlib`` import mechanism supports a versioning scheme: - -.. code-block:: nim - proc Tcl_Eval(interp: pTcl_Interp, script: cstring): int {.cdecl, - importc, dynlib: "libtcl(|8.5|8.4|8.3).so.(1|0)".} - -At runtime the dynamic library is searched for (in this order):: - - libtcl.so.1 - libtcl.so.0 - libtcl8.5.so.1 - libtcl8.5.so.0 - libtcl8.4.so.1 - libtcl8.4.so.0 - libtcl8.3.so.1 - libtcl8.3.so.0 - -The ``dynlib`` pragma supports not only constant strings as argument but also -string expressions in general: - -.. code-block:: nim - import os - - proc getDllName: string = - result = "mylib.dll" - if existsFile(result): return - result = "mylib2.dll" - if existsFile(result): return - quit("could not load dynamic library") - - proc myImport(s: cstring) {.cdecl, importc, dynlib: getDllName().} - -**Note**: Patterns like ``libtcl(|8.5|8.4).so`` are only supported in constant -strings, because they are precompiled. - -**Note**: Passing variables to the ``dynlib`` pragma will fail at runtime -because of order of initialization problems. - -**Note**: A ``dynlib`` import can be overridden with -the ``--dynlibOverride:name`` command line option. The Compiler User Guide -contains further information. - - -Dynlib pragma for export ------------------------- - -With the ``dynlib`` pragma a procedure can also be exported to -a dynamic library. The pragma then has no argument and has to be used in -conjunction with the ``exportc`` pragma: - -.. code-block:: Nim - proc exportme(): int {.cdecl, exportc, dynlib.} - -This is only useful if the program is compiled as a dynamic library via the -``--app:lib`` command line option. This pragma only has an effect for the code -generation on the Windows target, so when this pragma is forgotten and the dynamic -library is only tested on Mac and/or Linux, there won't be an error. On Windows -this pragma adds ``__declspec(dllexport)`` to the function declaration. - diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt deleted file mode 100644 index 4c908fefe..000000000 --- a/doc/manual/generics.txt +++ /dev/null @@ -1,715 +0,0 @@ -Generics -======== - -Generics are Nim's means to parametrize procs, iterators or types with -`type parameters`:idx:. Depending on context, the brackets are used either to -introduce type parameters or to instantiate a generic proc, iterator or type. - -The following example shows a generic binary tree can be modelled: - -.. code-block:: nim - type - BinaryTree*[T] = ref object # BinaryTree is a generic type with - # generic param ``T`` - le, ri: BinaryTree[T] # left and right subtrees; may be nil - data: T # the data stored in a node - - proc newNode*[T](data: T): BinaryTree[T] = - # constructor for a node - new(result) - result.data = data - - proc add*[T](root: var BinaryTree[T], n: BinaryTree[T]) = - # insert a node into the tree - if root == nil: - root = n - else: - var it = root - while it != nil: - # compare the data items; uses the generic ``cmp`` proc - # that works for any type that has a ``==`` and ``<`` operator - var c = cmp(it.data, n.data) - if c < 0: - if it.le == nil: - it.le = n - return - it = it.le - else: - if it.ri == nil: - it.ri = n - return - it = it.ri - - proc add*[T](root: var BinaryTree[T], data: T) = - # convenience proc: - add(root, newNode(data)) - - iterator preorder*[T](root: BinaryTree[T]): T = - # Preorder traversal of a binary tree. - # Since recursive iterators are not yet implemented, - # this uses an explicit stack (which is more efficient anyway): - var stack: seq[BinaryTree[T]] = @[root] - while stack.len > 0: - var n = stack.pop() - while n != nil: - yield n.data - add(stack, n.ri) # push right subtree onto the stack - n = n.le # and follow the left pointer - - var - root: BinaryTree[string] # instantiate a BinaryTree with ``string`` - add(root, newNode("hello")) # instantiates ``newNode`` and ``add`` - add(root, "world") # instantiates the second ``add`` proc - for str in preorder(root): - stdout.writeLine(str) - -The ``T`` is called a `generic type parameter`:idx:. - - -Is operator ------------ - -The ``is`` operator checks for type equivalence at compile time. It is -therefore very useful for type specialization within generic code: - -.. code-block:: nim - type - Table[Key, Value] = object - keys: seq[Key] - values: seq[Value] - when not (Key is string): # nil value for strings used for optimization - deletedKeys: seq[bool] - - -Type operator -------------- - -The ``type`` (in many other languages called `typeof`:idx:) operator can -be used to get the type of an expression: - -.. code-block:: nim - var x = 0 - var y: type(x) # y has type int - -If ``type`` is used to determine the result type of a proc/iterator/converter -call ``c(X)`` (where ``X`` stands for a possibly empty list of arguments), the -interpretation where ``c`` is an iterator is preferred over the -other interpretations: - -.. code-block:: nim - import strutils - - # strutils contains both a ``split`` proc and iterator, but since an - # an iterator is the preferred interpretation, `y` has the type ``string``: - var y: type("a b c".split) - - -Type Classes ------------- - -A type class is a special pseudo-type that can be used to match against -types in the context of overload resolution or the ``is`` operator. -Nim supports the following built-in type classes: - -================== =================================================== -type class matches -================== =================================================== -``object`` any object type -``tuple`` any tuple type - -``enum`` any enumeration -``proc`` any proc type -``ref`` any ``ref`` type -``ptr`` any ``ptr`` type -``var`` any ``var`` type -``distinct`` any distinct type -``array`` any array type -``set`` any set type -``seq`` any seq type -``auto`` any type -``any`` distinct auto (see below) -================== =================================================== - -Furthermore, every generic type automatically creates a type class of the same -name that will match any instantiation of the generic type. - -Type classes can be combined using the standard boolean operators to form -more complex type classes: - -.. code-block:: nim - # create a type class that will match all tuple and object types - type RecordType = tuple or object - - proc printFields(rec: RecordType) = - for key, value in fieldPairs(rec): - echo key, " = ", value - -Procedures utilizing type classes in such manner are considered to be -`implicitly generic`:idx:. They will be instantiated once for each unique -combination of param types used within the program. - -Nim also allows for type classes and regular types to be specified -as `type constraints`:idx: of the generic type parameter: - -.. code-block:: nim - proc onlyIntOrString[T: int|string](x, y: T) = discard - - onlyIntOrString(450, 616) # valid - onlyIntOrString(5.0, 0.0) # type mismatch - onlyIntOrString("xy", 50) # invalid as 'T' cannot be both at the same time - -By default, during overload resolution each named type class will bind to -exactly one concrete type. We call such type classes `bind once`:idx: types. -Here is an example taken directly from the system module to illustrate this: - -.. code-block:: nim - proc `==`*(x, y: tuple): bool = - ## requires `x` and `y` to be of the same tuple type - ## generic ``==`` operator for tuples that is lifted from the components - ## of `x` and `y`. - result = true - for a, b in fields(x, y): - if a != b: result = false - -Alternatively, the ``distinct`` type modifier can be applied to the type class -to allow each param matching the type class to bind to a different type. Such -type classes are called `bind many`:idx: types. - -Procs written with the implicitly generic style will often need to refer to the -type parameters of the matched generic type. They can be easily accessed using -the dot syntax: - -.. code-block:: nim - type Matrix[T, Rows, Columns] = object - ... - - proc `[]`(m: Matrix, row, col: int): Matrix.T = - m.data[col * high(Matrix.Columns) + row] - -Alternatively, the `type` operator can be used over the proc params for similar -effect when anonymous or distinct type classes are used. - -When a generic type is instantiated with a type class instead of a concrete -type, this results in another more specific type class: - -.. code-block:: nim - seq[ref object] # Any sequence storing references to any object type - - type T1 = auto - proc foo(s: seq[T1], e: T1) - # seq[T1] is the same as just `seq`, but T1 will be allowed to bind - # to a single type, while the signature is being matched - - Matrix[Ordinal] # Any Matrix instantiation using integer values - -As seen in the previous example, in such instantiations, it's not necessary to -supply all type parameters of the generic type, because any missing ones will -be inferred to have the equivalent of the `any` type class and thus they will -match anything without discrimination. - - -Concepts --------- - -**Note**: Concepts are still in development. - -Concepts, also known as "user-defined type classes", are used to specify an -arbitrary set of requirements that the matched type must satisfy. - -Concepts are written in the following form: - -.. code-block:: nim - type - Comparable = concept x, y - (x < y) is bool - - Stack[T] = concept s, var v - s.pop() is T - v.push(T) - - s.len is Ordinal - - for value in s: - value is T - -The concept is a match if: - -a) all of the expressions within the body can be compiled for the tested type -b) all statically evaluable boolean expressions in the body must be true - -The identifiers following the ``concept`` keyword represent instances of the -currently matched type. You can apply any of the standard type modifiers such -as ``var``, ``ref``, ``ptr`` and ``static`` to denote a more specific type of -instance. You can also apply the `type` modifier to create a named instance of -the type itself: - -.. code-block:: nim - type - MyConcept = concept x, var v, ref r, ptr p, static s, type T - ... - -Within the concept body, types can appear in positions where ordinary values -and parameters are expected. This provides a more convenient way to check for -the presence of callable symbols with specific signatures: - -.. code-block:: nim - type - OutputStream = concept var s - s.write(string) - -In order to check for symbols accepting ``typedesc`` params, you must prefix -the type with an explicit ``type`` modifier. The named instance of the type, -following the ``concept`` keyword is also considered an explicit ``typedesc`` -value that will be matched only as a type. - -.. code-block:: nim - type - # Let's imagine a user-defined casting framework with operators - # such as `val.to(string)` and `val.to(JSonValue)`. We can test - # for these with the following concept: - MyCastables = concept x - x.to(type string) - x.to(type JSonValue) - - # Let's define a couple of concepts, known from Algebra: - AdditiveMonoid* = concept x, y, type T - x + y is T - T.zero is T # require a proc such as `int.zero` or 'Position.zero' - - AdditiveGroup* = concept x, y, type T - x is AdditiveMonoid - -x is T - x - y is T - -Please note that the ``is`` operator allows one to easily verify the precise -type signatures of the required operations, but since type inference and -default parameters are still applied in the concept body, it's also possible -to describe usage protocols that do not reveal implementation details. - -Much like generics, concepts are instantiated exactly once for each tested type -and any static code included within the body is executed only once. - - -Concept diagnostics -------------------- - -By default, the compiler will report the matching errors in concepts only when -no other overload can be selected and a normal compilation error is produced. -When you need to understand why the compiler is not matching a particular -concept and, as a result, a wrong overload is selected, you can apply the -``explain`` pragma to either the concept body or a particular call-site. - -.. code-block:: nim - type - MyConcept {.explain.} = concept ... - - overloadedProc(x, y, z) {.explain.} - -This will provide Hints in the compiler output either every time the concept is -not matched or only on the particular call-site. - - -Generic concepts and type binding rules ---------------------------------------- - -The concept types can be parametric just like the regular generic types: - -.. code-block:: nim - ### matrixalgo.nim - - import typetraits - - type - AnyMatrix*[R, C: static[int]; T] = concept m, var mvar, type M - M.ValueType is T - M.Rows == R - M.Cols == C - - m[int, int] is T - mvar[int, int] = T - - type TransposedType = stripGenericParams(M)[C, R, T] - - AnySquareMatrix*[N: static[int], T] = AnyMatrix[N, N, T] - - AnyTransform3D* = AnyMatrix[4, 4, float] - - proc transposed*(m: AnyMatrix): m.TransposedType = - for r in 0 .. <m.R: - for c in 0 .. <m.C: - result[r, c] = m[c, r] - - proc determinant*(m: AnySquareMatrix): int = - ... - - proc setPerspectiveProjection*(m: AnyTransform3D) = - ... - - -------------- - ### matrix.nim - - type - Matrix*[M, N: static[int]; T] = object - data: array[M*N, T] - - proc `[]`*(M: Matrix; m, n: int): M.T = - M.data[m * M.N + n] - - proc `[]=`*(M: var Matrix; m, n: int; v: M.T) = - M.data[m * M.N + n] = v - - # Adapt the Matrix type to the concept's requirements - template Rows*(M: type Matrix): expr = M.M - template Cols*(M: type Matrix): expr = M.N - template ValueType*(M: type Matrix): typedesc = M.T - - ------------- - ### usage.nim - - import matrix, matrixalgo - - var - m: Matrix[3, 3, int] - projectionMatrix: Matrix[4, 4, float] - - echo m.transposed.determinant - setPerspectiveProjection projectionMatrix - -When the concept type is matched against a concrete type, the unbound type -parameters are inferred from the body of the concept in a way that closely -resembles the way generic parameters of callable symbols are inferred on -call sites. - -Unbound types can appear both as params to calls such as `s.push(T)` and -on the right-hand side of the ``is`` operator in cases such as `x.pop is T` -and `x.data is seq[T]`. - -Unbound static params will be inferred from expressions involving the `==` -operator and also when types dependent on them are being matched: - -.. code-block:: nim - type - MatrixReducer[M, N: static[int]; T] = concept x - x.reduce(SquareMatrix[N, T]) is array[M, int] - -The Nim compiler includes a simple linear equation solver, allowing it to -infer static params in some situations where integer arithmetic is involved. - -Just like in regular type classes, Nim discriminates between ``bind once`` -and ``bind many`` types when matching the concept. You can add the ``distinct`` -modifier to any of the otherwise inferable types to get a type that will be -matched without permanently inferring it. This may be useful when you need -to match several procs accepting the same wide class of types: - -.. code-block:: nim - type - Enumerable[T] = concept e - for v in e: - v is T - - type - MyConcept = concept o - # this could be inferred to a type such as Enumerable[int] - o.foo is distinct Enumerable - - # this could be inferred to a different type such as Enumerable[float] - o.bar is distinct Enumerable - - # it's also possible to give an alias name to a `bind many` type class - type Enum = distinct Enumerable - o.baz is Enum - -On the other hand, using ``bind once`` types allows you to test for equivalent -types used in multiple signatures, without actually requiring any concrete -types, thus allowing you to encode implementation-defined types: - -.. code-block:: nim - type - MyConcept = concept x - type T1 = auto - x.foo(T1) - x.bar(T1) # both procs must accept the same type - - type T2 = seq[SomeNumber] - x.alpha(T2) - x.omega(T2) # both procs must accept the same type - # and it must be a numeric sequence - -As seen in the previous examples, you can refer to generic concepts such as -`Enumerable[T]` just by their short name. Much like the regular generic types, -the concept will be automatically instantiated with the bind once auto type -in the place of each missing generic param. - -Please note that generic concepts such as `Enumerable[T]` can be matched -against concrete types such as `string`. Nim doesn't require the concept -type to have the same number of parameters as the type being matched. -If you wish to express a requirement towards the generic parameters of -the matched type, you can use a type mapping operator such as `genericHead` -or `stripGenericParams` within the body of the concept to obtain the -uninstantiated version of the type, which you can then try to instantiate -in any required way. For example, here is how one might define the classic -`Functor` concept from Haskell and then demonstrate that Nim's `Option[T]` -type is an instance of it: - -.. code-block:: nim - import future, typetraits - - type - Functor[A] = concept f - type MatchedGenericType = genericHead(f.type) - # `f` will be a value of a type such as `Option[T]` - # `MatchedGenericType` will become the `Option` type - - f.val is A - # The Functor should provide a way to obtain - # a value stored inside it - - type T = auto - map(f, A -> T) is MatchedGenericType[T] - # And it should provide a way to map one instance of - # the Functor to a instance of a different type, given - # a suitable `map` operation for the enclosed values - - import options - echo Option[int] is Functor # prints true - - -Concept derived values ----------------------- - -All top level constants or types appearing within the concept body are -accessible through the dot operator in procs where the concept was successfully -matched to a concrete type: - -.. code-block:: nim - type - DateTime = concept t1, t2, type T - const Min = T.MinDate - T.Now is T - - t1 < t2 is bool - - type TimeSpan = type(t1 - t2) - TimeSpan * int is TimeSpan - TimeSpan + TimeSpan is TimeSpan - - t1 + TimeSpan is T - - proc eventsJitter(events: Enumerable[DateTime]): float = - var - # this variable will have the inferred TimeSpan type for - # the concrete Date-like value the proc was called with: - averageInterval: DateTime.TimeSpan - - deviation: float - ... - - -Concept refinement ------------------- - -When the matched type within a concept is directly tested against a different -concept, we say that the outer concept is a refinement of the inner concept and -thus it is more-specific. When both concepts are matched in a call during -overload resolution, Nim will assign a higher precedence to the most specific -one. As an alternative way of defining concept refinements, you can use the -object inheritance syntax involving the ``of`` keyword: - -.. code-block:: nim - type - Graph = concept g, type G of EqualyComparable, Copyable - type - VertexType = G.VertexType - EdgeType = G.EdgeType - - VertexType is Copyable - EdgeType is Copyable - - var - v: VertexType - e: EdgeType - - IncidendeGraph = concept of Graph - # symbols such as variables and types from the refined - # concept are automatically in scope: - - g.source(e) is VertexType - g.target(e) is VertexType - - g.outgoingEdges(v) is Enumerable[EdgeType] - - BidirectionalGraph = concept g, type G - # The following will also turn the concept into a refinement when it - # comes to overload resolution, but it doesn't provide the convenient - # symbol inheritance - g is IncidendeGraph - - g.incomingEdges(G.VertexType) is Enumerable[G.EdgeType] - - proc f(g: IncidendeGraph) - proc f(g: BidirectionalGraph) # this one will be preferred if we pass a type - # matching the BidirectionalGraph concept - - -Converter type classes ----------------------- - -Concepts can also be used to convert a whole range of types to a single type or -a small set of simpler types. This is achieved with a `return` statement within -the concept body: - -.. code-block:: nim - type - Stringable = concept x - $x is string - return $x - - StringRefValue[CharType] = object - base: ptr CharType - len: int - - StringRef = concept x - # the following would be an overloaded proc for cstring, string, seq and - # other user-defined types, returning either a StringRefValue[char] or - # StringRefValue[wchar] - return makeStringRefValue(x) - - # the varargs param will here be converted to an array of StringRefValues - # the proc will have only two instantiations for the two character types - proc log(format: static[string], varargs[StringRef]) - - # this proc will allow char and wchar values to be mixed in - # the same call at the cost of additional instantiations - # the varargs param will be converted to a tuple - proc log(format: static[string], varargs[distinct StringRef]) - - -.. - VTable types - ------------ - - Concepts allow Nim to define a great number of algorithms, using only - static polymorphism and without erasing any type information or sacrificing - any execution speed. But when polymorphic collections of objects are required, - the user must use one of the provided type erasure techniques - either common - base types or VTable types. - - VTable types are represented as "fat pointers" storing a reference to an - object together with a reference to a table of procs implementing a set of - required operations (the so called vtable). - - In contrast to other programming languages, the vtable in Nim is stored - externally to the object, allowing you to create multiple different vtable - views for the same object. Thus, the polymorphism in Nim is unbounded - - any type can implement an unlimited number of protocols or interfaces not - originally envisioned by the type's author. - - Any concept type can be turned into a VTable type by using the ``vtref`` - or the ``vtptr`` compiler magics. Under the hood, these magics generate - a converter type class, which converts the regular instances of the matching - types to the corresponding VTable type. - - .. code-block:: nim - type - IntEnumerable = vtref Enumerable[int] - - MyObject = object - enumerables: seq[IntEnumerable] - streams: seq[OutputStream.vtref] - - proc addEnumerable(o: var MyObject, e: IntEnumerable) = - o.enumerables.add e - - proc addStream(o: var MyObject, e: OutputStream.vtref) = - o.streams.add e - - The procs that will be included in the vtable are derived from the concept - body and include all proc calls for which all param types were specified as - concrete types. All such calls should include exactly one param of the type - matched against the concept (not necessarily in the first position), which - will be considered the value bound to the vtable. - - Overloads will be created for all captured procs, accepting the vtable type - in the position of the captured underlying object. - - Under these rules, it's possible to obtain a vtable type for a concept with - unbound type parameters or one instantiated with metatypes (type classes), - but it will include a smaller number of captured procs. A completely empty - vtable will be reported as an error. - - The ``vtref`` magic produces types which can be bound to ``ref`` types and - the ``vtptr`` magic produced types bound to ``ptr`` types. - - -Symbol lookup in generics -------------------------- - -Open and Closed symbols -~~~~~~~~~~~~~~~~~~~~~~~ - -The symbol binding rules in generics are slightly subtle: There are "open" and -"closed" symbols. A "closed" symbol cannot be re-bound in the instantiation -context, an "open" symbol can. Per default overloaded symbols are open -and every other symbol is closed. - -Open symbols are looked up in two different contexts: Both the context -at definition and the context at instantiation are considered: - -.. code-block:: nim - type - Index = distinct int - - proc `==` (a, b: Index): bool {.borrow.} - - var a = (0, 0.Index) - var b = (0, 0.Index) - - echo a == b # works! - -In the example the generic ``==`` for tuples (as defined in the system module) -uses the ``==`` operators of the tuple's components. However, the ``==`` for -the ``Index`` type is defined *after* the ``==`` for tuples; yet the example -compiles as the instantiation takes the currently defined symbols into account -too. - -Mixin statement ---------------- - -A symbol can be forced to be open by a `mixin`:idx: declaration: - -.. code-block:: nim - proc create*[T](): ref T = - # there is no overloaded 'init' here, so we need to state that it's an - # open symbol explicitly: - mixin init - new result - init result - - -Bind statement --------------- - -The ``bind`` statement is the counterpart to the ``mixin`` statement. It -can be used to explicitly declare identifiers that should be bound early (i.e. -the identifiers should be looked up in the scope of the template/generic -definition): - -.. code-block:: nim - # Module A - var - lastId = 0 - - template genId*: untyped = - bind lastId - inc(lastId) - lastId - -.. code-block:: nim - # Module B - import A - - echo genId() - -But a ``bind`` is rarely useful because symbol binding from the definition -scope is the default. - diff --git a/doc/manual/lexing.txt b/doc/manual/lexing.txt deleted file mode 100644 index 915e87971..000000000 --- a/doc/manual/lexing.txt +++ /dev/null @@ -1,418 +0,0 @@ -Lexical Analysis -================ - -Encoding --------- - -All Nim source files are in the UTF-8 encoding (or its ASCII subset). Other -encodings are not supported. Any of the standard platform line termination -sequences can be used - the Unix form using ASCII LF (linefeed), the Windows -form using the ASCII sequence CR LF (return followed by linefeed), or the old -Macintosh form using the ASCII CR (return) character. All of these forms can be -used equally, regardless of platform. - - -Indentation ------------ - -Nim's standard grammar describes an `indentation sensitive`:idx: language. -This means that all the control structures are recognized by indentation. -Indentation consists only of spaces; tabulators are not allowed. - -The indentation handling is implemented as follows: The lexer annotates the -following token with the preceding number of spaces; indentation is not -a separate token. This trick allows parsing of Nim with only 1 token of -lookahead. - -The parser uses a stack of indentation levels: the stack consists of integers -counting the spaces. The indentation information is queried at strategic -places in the parser but ignored otherwise: The pseudo terminal ``IND{>}`` -denotes an indentation that consists of more spaces than the entry at the top -of the stack; ``IND{=}`` an indentation that has the same number of spaces. ``DED`` -is another pseudo terminal that describes the *action* of popping a value -from the stack, ``IND{>}`` then implies to push onto the stack. - -With this notation we can now easily define the core of the grammar: A block of -statements (simplified example):: - - ifStmt = 'if' expr ':' stmt - (IND{=} 'elif' expr ':' stmt)* - (IND{=} 'else' ':' stmt)? - - simpleStmt = ifStmt / ... - - stmt = IND{>} stmt ^+ IND{=} DED # list of statements - / simpleStmt # or a simple statement - - - -Comments --------- - -Comments start anywhere outside a string or character literal with the -hash character ``#``. -Comments consist of a concatenation of `comment pieces`:idx:. A comment piece -starts with ``#`` and runs until the end of the line. The end of line characters -belong to the piece. If the next line only consists of a comment piece with -no other tokens between it and the preceding one, it does not start a new -comment: - - -.. code-block:: nim - i = 0 # This is a single comment over multiple lines. - # The scanner merges these two pieces. - # The comment continues here. - - -`Documentation comments`:idx: are comments that start with two ``##``. -Documentation comments are tokens; they are only allowed at certain places in -the input file as they belong to the syntax tree! - - -Multiline comments ------------------- - -Starting with version 0.13.0 of the language Nim supports multiline comments. -They look like: - -.. code-block:: nim - #[Comment here. - Multiple lines - are not a problem.]# - -Multiline comments support nesting: - -.. code-block:: nim - #[ #[ Multiline comment in already - commented out code. ]# - proc p[T](x: T) = discard - ]# - -Multiline documentation comments also exist and support nesting too: - -.. code-block:: nim - proc foo = - ##[Long documentation comment - here. - ]## - - -Identifiers & Keywords ----------------------- - -Identifiers in Nim can be any string of letters, digits -and underscores, beginning with a letter. Two immediate following -underscores ``__`` are not allowed:: - - letter ::= 'A'..'Z' | 'a'..'z' | '\x80'..'\xff' - digit ::= '0'..'9' - IDENTIFIER ::= letter ( ['_'] (letter | digit) )* - -Currently any Unicode character with an ordinal value > 127 (non ASCII) is -classified as a ``letter`` and may thus be part of an identifier but later -versions of the language may assign some Unicode characters to belong to the -operator characters instead. - -The following keywords are reserved and cannot be used as identifiers: - -.. code-block:: nim - :file: ../keywords.txt - -Some keywords are unused; they are reserved for future developments of the -language. - - -Identifier equality -------------------- - -Two identifiers are considered equal if the following algorithm returns true: - -.. code-block:: nim - proc sameIdentifier(a, b: string): bool = - a[0] == b[0] and - a.replace("_", "").toLowerAscii == b.replace("_", "").toLowerAscii - -That means only the first letters are compared in a case sensitive manner. Other -letters are compared case insensitively within the ASCII range and underscores are ignored. - -This rather unorthodox way to do identifier comparisons is called -`partial case insensitivity`:idx: and has some advantages over the conventional -case sensitivity: - -It allows programmers to mostly use their own preferred -spelling style, be it humpStyle or snake_style, and libraries written -by different programmers cannot use incompatible conventions. -A Nim-aware editor or IDE can show the identifiers as preferred. -Another advantage is that it frees the programmer from remembering -the exact spelling of an identifier. The exception with respect to the first -letter allows common code like ``var foo: Foo`` to be parsed unambiguously. - -Historically, Nim was a fully `style-insensitive`:idx: language. This meant that -it was not case-sensitive and underscores were ignored and there was no even a -distinction between ``foo`` and ``Foo``. - - -String literals ---------------- - -Terminal symbol in the grammar: ``STR_LIT``. - -String literals can be delimited by matching double quotes, and can -contain the following `escape sequences`:idx:\ : - -================== =================================================== - Escape sequence Meaning -================== =================================================== - ``\p`` platform specific newline: CRLF on Windows, - LF on Unix - ``\r``, ``\c`` `carriage return`:idx: - ``\n``, ``\l`` `line feed`:idx: (often called `newline`:idx:) - ``\f`` `form feed`:idx: - ``\t`` `tabulator`:idx: - ``\v`` `vertical tabulator`:idx: - ``\\`` `backslash`:idx: - ``\"`` `quotation mark`:idx: - ``\'`` `apostrophe`:idx: - ``\`` '0'..'9'+ `character with decimal value d`:idx:; - all decimal digits directly - following are used for the character - ``\a`` `alert`:idx: - ``\b`` `backspace`:idx: - ``\e`` `escape`:idx: `[ESC]`:idx: - ``\x`` HH `character with hex value HH`:idx:; - exactly two hex digits are allowed -================== =================================================== - - -Strings in Nim may contain any 8-bit value, even embedded zeros. However -some operations may interpret the first binary zero as a terminator. - - -Triple quoted string literals ------------------------------ - -Terminal symbol in the grammar: ``TRIPLESTR_LIT``. - -String literals can also be delimited by three double quotes -``"""`` ... ``"""``. -Literals in this form may run for several lines, may contain ``"`` and do not -interpret any escape sequences. -For convenience, when the opening ``"""`` is followed by a newline (there may -be whitespace between the opening ``"""`` and the newline), -the newline (and the preceding whitespace) is not included in the string. The -ending of the string literal is defined by the pattern ``"""[^"]``, so this: - -.. code-block:: nim - """"long string within quotes"""" - -Produces:: - - "long string within quotes" - - -Raw string literals -------------------- - -Terminal symbol in the grammar: ``RSTR_LIT``. - -There are also raw string literals that are preceded with the -letter ``r`` (or ``R``) and are delimited by matching double quotes (just -like ordinary string literals) and do not interpret the escape sequences. -This is especially convenient for regular expressions or Windows paths: - -.. code-block:: nim - - var f = openFile(r"C:\texts\text.txt") # a raw string, so ``\t`` is no tab - -To produce a single ``"`` within a raw string literal, it has to be doubled: - -.. code-block:: nim - - r"a""b" - -Produces:: - - a"b - -``r""""`` is not possible with this notation, because the three leading -quotes introduce a triple quoted string literal. ``r"""`` is the same -as ``"""`` since triple quoted string literals do not interpret escape -sequences either. - - -Generalized raw string literals -------------------------------- - -Terminal symbols in the grammar: ``GENERALIZED_STR_LIT``, -``GENERALIZED_TRIPLESTR_LIT``. - -The construct ``identifier"string literal"`` (without whitespace between the -identifier and the opening quotation mark) is a -generalized raw string literal. It is a shortcut for the construct -``identifier(r"string literal")``, so it denotes a procedure call with a -raw string literal as its only argument. Generalized raw string literals -are especially convenient for embedding mini languages directly into Nim -(for example regular expressions). - -The construct ``identifier"""string literal"""`` exists too. It is a shortcut -for ``identifier("""string literal""")``. - - -Character literals ------------------- - -Character literals are enclosed in single quotes ``''`` and can contain the -same escape sequences as strings - with one exception: the platform -dependent `newline`:idx: (``\p``) -is not allowed as it may be wider than one character (often it is the pair -CR/LF for example). Here are the valid `escape sequences`:idx: for character -literals: - -================== =================================================== - Escape sequence Meaning -================== =================================================== - ``\r``, ``\c`` `carriage return`:idx: - ``\n``, ``\l`` `line feed`:idx: - ``\f`` `form feed`:idx: - ``\t`` `tabulator`:idx: - ``\v`` `vertical tabulator`:idx: - ``\\`` `backslash`:idx: - ``\"`` `quotation mark`:idx: - ``\'`` `apostrophe`:idx: - ``\`` '0'..'9'+ `character with decimal value d`:idx:; - all decimal digits directly - following are used for the character - ``\a`` `alert`:idx: - ``\b`` `backspace`:idx: - ``\e`` `escape`:idx: `[ESC]`:idx: - ``\x`` HH `character with hex value HH`:idx:; - exactly two hex digits are allowed -================== =================================================== - -A character is not an Unicode character but a single byte. The reason for this -is efficiency: for the overwhelming majority of use-cases, the resulting -programs will still handle UTF-8 properly as UTF-8 was specially designed for -this. Another reason is that Nim can thus support ``array[char, int]`` or -``set[char]`` efficiently as many algorithms rely on this feature. The `Rune` -type is used for Unicode characters, it can represent any Unicode character. -``Rune`` is declared in the `unicode module <unicode.html>`_. - - -Numerical constants -------------------- - -Numerical constants are of a single type and have the form:: - - hexdigit = digit | 'A'..'F' | 'a'..'f' - octdigit = '0'..'7' - bindigit = '0'..'1' - HEX_LIT = '0' ('x' | 'X' ) hexdigit ( ['_'] hexdigit )* - DEC_LIT = digit ( ['_'] digit )* - OCT_LIT = '0' ('o' | 'c' | 'C') octdigit ( ['_'] octdigit )* - BIN_LIT = '0' ('b' | 'B' ) bindigit ( ['_'] bindigit )* - - INT_LIT = HEX_LIT - | DEC_LIT - | OCT_LIT - | BIN_LIT - - INT8_LIT = INT_LIT ['\''] ('i' | 'I') '8' - INT16_LIT = INT_LIT ['\''] ('i' | 'I') '16' - INT32_LIT = INT_LIT ['\''] ('i' | 'I') '32' - INT64_LIT = INT_LIT ['\''] ('i' | 'I') '64' - - UINT_LIT = INT_LIT ['\''] ('u' | 'U') - UINT8_LIT = INT_LIT ['\''] ('u' | 'U') '8' - UINT16_LIT = INT_LIT ['\''] ('u' | 'U') '16' - UINT32_LIT = INT_LIT ['\''] ('u' | 'U') '32' - UINT64_LIT = INT_LIT ['\''] ('u' | 'U') '64' - - exponent = ('e' | 'E' ) ['+' | '-'] digit ( ['_'] digit )* - FLOAT_LIT = digit (['_'] digit)* (('.' (['_'] digit)* [exponent]) |exponent) - FLOAT32_SUFFIX = ('f' | 'F') ['32'] - FLOAT32_LIT = HEX_LIT '\'' FLOAT32_SUFFIX - | (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] FLOAT32_SUFFIX - FLOAT64_SUFFIX = ( ('f' | 'F') '64' ) | 'd' | 'D' - FLOAT64_LIT = HEX_LIT '\'' FLOAT64_SUFFIX - | (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] FLOAT64_SUFFIX - - -As can be seen in the productions, numerical constants can contain underscores -for readability. Integer and floating point literals may be given in decimal (no -prefix), binary (prefix ``0b``), octal (prefix ``0o`` or ``0c``) and hexadecimal -(prefix ``0x``) notation. - -There exists a literal for each numerical type that is -defined. The suffix starting with an apostrophe ('\'') is called a -`type suffix`:idx:. Literals without a type suffix are of the type ``int``, -unless the literal contains a dot or ``E|e`` in which case it is of -type ``float``. For notational convenience the apostrophe of a type suffix -is optional if it is not ambiguous (only hexadecimal floating point literals -with a type suffix can be ambiguous). - - -The type suffixes are: - -================= ========================= - Type Suffix Resulting type of literal -================= ========================= - ``'i8`` int8 - ``'i16`` int16 - ``'i32`` int32 - ``'i64`` int64 - ``'u`` uint - ``'u8`` uint8 - ``'u16`` uint16 - ``'u32`` uint32 - ``'u64`` uint64 - ``'f`` float32 - ``'d`` float64 - ``'f32`` float32 - ``'f64`` float64 - ``'f128`` float128 -================= ========================= - -Floating point literals may also be in binary, octal or hexadecimal -notation: -``0B0_10001110100_0000101001000111101011101111111011000101001101001001'f64`` -is approximately 1.72826e35 according to the IEEE floating point standard. - -Literals are bounds checked so that they fit the datatype. Non base-10 -literals are used mainly for flags and bit pattern representations, therefore -bounds checking is done on bit width, not value range. If the literal fits in -the bit width of the datatype, it is accepted. -Hence: 0b10000000'u8 == 0x80'u8 == 128, but, 0b10000000'i8 == 0x80'i8 == -1 -instead of causing an overflow error. - -Operators ---------- - -Nim allows user defined operators. An operator is any combination of the -following characters:: - - = + - * / < > - @ $ ~ & % | - ! ? ^ . : \ - -These keywords are also operators: -``and or not xor shl shr div mod in notin is isnot of``. - -`=`:tok:, `:`:tok:, `::`:tok: are not available as general operators; they -are used for other notational purposes. - -``*:`` is as a special case treated as the two tokens `*`:tok: and `:`:tok: -(to support ``var v*: T``). - - -Other tokens ------------- - -The following strings denote other tokens:: - - ` ( ) { } [ ] , ; [. .] {. .} (. .) - - -The `slice`:idx: operator `..`:tok: takes precedence over other tokens that -contain a dot: `{..}`:tok: are the three tokens `{`:tok:, `..`:tok:, `}`:tok: -and not the two tokens `{.`:tok:, `.}`:tok:. - diff --git a/doc/manual/locking.txt b/doc/manual/locking.txt deleted file mode 100644 index 99ffe8970..000000000 --- a/doc/manual/locking.txt +++ /dev/null @@ -1,219 +0,0 @@ -Guards and locks -================ - -Apart from ``spawn`` and ``parallel`` Nim also provides all the common low level -concurrency mechanisms like locks, atomic intrinsics or condition variables. - -Nim significantly improves on the safety of these features via additional -pragmas: - -1) A `guard`:idx: annotation is introduced to prevent data races. -2) Every access of a guarded memory location needs to happen in an - appropriate `locks`:idx: statement. -3) Locks and routines can be annotated with `lock levels`:idx: to prevent - deadlocks at compile time. - - -Guards and the locks section ----------------------------- - -Protecting global variables -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Object fields and global variables can be annotated via a ``guard`` pragma: - -.. code-block:: nim - var glock: TLock - var gdata {.guard: glock.}: int - -The compiler then ensures that every access of ``gdata`` is within a ``locks`` -section: - -.. code-block:: nim - proc invalid = - # invalid: unguarded access: - echo gdata - - proc valid = - # valid access: - {.locks: [glock].}: - echo gdata - -Top level accesses to ``gdata`` are always allowed so that it can be initialized -conveniently. It is *assumed* (but not enforced) that every top level statement -is executed before any concurrent action happens. - -The ``locks`` section deliberately looks ugly because it has no runtime -semantics and should not be used directly! It should only be used in templates -that also implement some form of locking at runtime: - -.. code-block:: nim - template lock(a: TLock; body: untyped) = - pthread_mutex_lock(a) - {.locks: [a].}: - try: - body - finally: - pthread_mutex_unlock(a) - - -The guard does not need to be of any particular type. It is flexible enough to -model low level lockfree mechanisms: - -.. code-block:: nim - var dummyLock {.compileTime.}: int - var atomicCounter {.guard: dummyLock.}: int - - template atomicRead(x): untyped = - {.locks: [dummyLock].}: - memoryReadBarrier() - x - - echo atomicRead(atomicCounter) - - -The ``locks`` pragma takes a list of lock expressions ``locks: [a, b, ...]`` -in order to support *multi lock* statements. Why these are essential is -explained in the `lock levels <#guards-and-locks-lock-levels>`_ section. - - -Protecting general locations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``guard`` annotation can also be used to protect fields within an object. -The guard then needs to be another field within the same object or a -global variable. - -Since objects can reside on the heap or on the stack this greatly enhances the -expressivity of the language: - -.. code-block:: nim - type - ProtectedCounter = object - v {.guard: L.}: int - L: TLock - - proc incCounters(counters: var openArray[ProtectedCounter]) = - for i in 0..counters.high: - lock counters[i].L: - inc counters[i].v - -The access to field ``x.v`` is allowed since its guard ``x.L`` is active. -After template expansion, this amounts to: - -.. code-block:: nim - proc incCounters(counters: var openArray[ProtectedCounter]) = - for i in 0..counters.high: - pthread_mutex_lock(counters[i].L) - {.locks: [counters[i].L].}: - try: - inc counters[i].v - finally: - pthread_mutex_unlock(counters[i].L) - -There is an analysis that checks that ``counters[i].L`` is the lock that -corresponds to the protected location ``counters[i].v``. This analysis is called -`path analysis`:idx: because it deals with paths to locations -like ``obj.field[i].fieldB[j]``. - -The path analysis is **currently unsound**, but that doesn't make it useless. -Two paths are considered equivalent if they are syntactically the same. - -This means the following compiles (for now) even though it really should not: - -.. code-block:: nim - {.locks: [a[i].L].}: - inc i - access a[i].v - - - -Lock levels ------------ - -Lock levels are used to enforce a global locking order in order to prevent -deadlocks at compile-time. A lock level is an constant integer in the range -0..1_000. Lock level 0 means that no lock is acquired at all. - -If a section of code holds a lock of level ``M`` than it can also acquire any -lock of level ``N < M``. Another lock of level ``M`` cannot be acquired. Locks -of the same level can only be acquired *at the same time* within a -single ``locks`` section: - -.. code-block:: nim - var a, b: TLock[2] - var x: TLock[1] - # invalid locking order: TLock[1] cannot be acquired before TLock[2]: - {.locks: [x].}: - {.locks: [a].}: - ... - # valid locking order: TLock[2] acquired before TLock[1]: - {.locks: [a].}: - {.locks: [x].}: - ... - - # invalid locking order: TLock[2] acquired before TLock[2]: - {.locks: [a].}: - {.locks: [b].}: - ... - - # valid locking order, locks of the same level acquired at the same time: - {.locks: [a, b].}: - ... - - -Here is how a typical multilock statement can be implemented in Nim. Note how -the runtime check is required to ensure a global ordering for two locks ``a`` -and ``b`` of the same lock level: - -.. code-block:: nim - template multilock(a, b: ptr TLock; body: untyped) = - if cast[ByteAddress](a) < cast[ByteAddress](b): - pthread_mutex_lock(a) - pthread_mutex_lock(b) - else: - pthread_mutex_lock(b) - pthread_mutex_lock(a) - {.locks: [a, b].}: - try: - body - finally: - pthread_mutex_unlock(a) - pthread_mutex_unlock(b) - - -Whole routines can also be annotated with a ``locks`` pragma that takes a lock -level. This then means that the routine may acquire locks of up to this level. -This is essential so that procs can be called within a ``locks`` section: - -.. code-block:: nim - proc p() {.locks: 3.} = discard - - var a: TLock[4] - {.locks: [a].}: - # p's locklevel (3) is strictly less than a's (4) so the call is allowed: - p() - - -As usual ``locks`` is an inferred effect and there is a subtype -relation: ``proc () {.locks: N.}`` is a subtype of ``proc () {.locks: M.}`` -iff (M <= N). - -The ``locks`` pragma can also take the special value ``"unknown"``. This -is useful in the context of dynamic method dispatching. In the following -example, the compiler can infer a lock level of 0 for the ``base`` case. -However, one of the overloaded methods calls a procvar which is -potentially locking. Thus, the lock level of calling ``g.testMethod`` -cannot be inferred statically, leading to compiler warnings. By using -``{.locks: "unknown".}``, the base method can be marked explicitly as -having unknown lock level as well: - -.. code-block:: nim - type SomeBase* = ref object of RootObj - type SomeDerived* = ref object of SomeBase - memberProc*: proc () - - method testMethod(g: SomeBase) {.base, locks: "unknown".} = discard - method testMethod(g: SomeDerived) = - if g.memberProc != nil: - g.memberProc() diff --git a/doc/manual/modules.txt b/doc/manual/modules.txt deleted file mode 100644 index 4ef1bfd99..000000000 --- a/doc/manual/modules.txt +++ /dev/null @@ -1,212 +0,0 @@ -Modules -======= -Nim supports splitting a program into pieces by a module concept. -Each module needs to be in its own file and has its own `namespace`:idx:. -Modules enable `information hiding`:idx: and `separate compilation`:idx:. -A module may gain access to symbols of another module by the `import`:idx: -statement. `Recursive module dependencies`:idx: are allowed, but slightly -subtle. Only top-level symbols that are marked with an asterisk (``*``) are -exported. A valid module name can only be a valid Nim identifier (and thus its -filename is ``identifier.nim``). - -The algorithm for compiling modules is: - -- compile the whole module as usual, following import statements recursively - -- if there is a cycle only import the already parsed symbols (that are - exported); if an unknown identifier occurs then abort - -This is best illustrated by an example: - -.. code-block:: nim - # Module A - type - T1* = int # Module A exports the type ``T1`` - import B # the compiler starts parsing B - - proc main() = - var i = p(3) # works because B has been parsed completely here - - main() - - -.. code-block:: nim - # Module B - import A # A is not parsed here! Only the already known symbols - # of A are imported. - - proc p*(x: A.T1): A.T1 = - # this works because the compiler has already - # added T1 to A's interface symbol table - result = x + 1 - - -Import statement -~~~~~~~~~~~~~~~~ - -After the ``import`` statement a list of module names can follow or a single -module name followed by an ``except`` list to prevent some symbols to be -imported: - -.. code-block:: nim - import strutils except `%`, toUpper - - # doesn't work then: - echo "$1" % "abc".toUpper - - -It is not checked that the ``except`` list is really exported from the module. -This feature allows to compile against an older version of the module that -does not export these identifiers. - - -Include statement -~~~~~~~~~~~~~~~~~ -The ``include`` statement does something fundamentally different than -importing a module: it merely includes the contents of a file. The ``include`` -statement is useful to split up a large module into several files: - -.. code-block:: nim - include fileA, fileB, fileC - - - -Module names in imports -~~~~~~~~~~~~~~~~~~~~~~~ - -A module alias can be introduced via the ``as`` keyword: - -.. code-block:: nim - import strutils as su, sequtils as qu - - echo su.format("$1", "lalelu") - -The original module name is then not accessible. The -notations ``path/to/module`` or ``path.to.module`` or ``"path/to/module"`` -can be used to refer to a module in subdirectories: - -.. code-block:: nim - import lib.pure.strutils, lib/pure/os, "lib/pure/times" - -Note that the module name is still ``strutils`` and not ``lib.pure.strutils`` -and so one **cannot** do: - -.. code-block:: nim - import lib.pure.strutils - echo lib.pure.strutils - -Likewise the following does not make sense as the name is ``strutils`` already: - -.. code-block:: nim - import lib.pure.strutils as strutils - - -From import statement -~~~~~~~~~~~~~~~~~~~~~ - -After the ``from`` statement a module name follows followed by -an ``import`` to list the symbols one likes to use without explicit -full qualification: - -.. code-block:: nim - from strutils import `%` - - echo "$1" % "abc" - # always possible: full qualification: - echo strutils.replace("abc", "a", "z") - -It's also possible to use ``from module import nil`` if one wants to import -the module but wants to enforce fully qualified access to every symbol -in ``module``. - - -Export statement -~~~~~~~~~~~~~~~~ - -An ``export`` statement can be used for symbol forwarding so that client -modules don't need to import a module's dependencies: - -.. code-block:: nim - # module B - type MyObject* = object - -.. code-block:: nim - # module A - import B - export B.MyObject - - proc `$`*(x: MyObject): string = "my object" - - -.. code-block:: nim - # module C - import A - - # B.MyObject has been imported implicitly here: - var x: MyObject - echo $x - -Note on paths ------------ -In module related statements, if any part of the module name / -path begins with a number, you may have to quote it in double quotes. -In the following example, it would be seen as a literal number '3.0' of type -'float64' if not quoted, if uncertain - quote it: - -.. code-block:: nim - import "gfx/3d/somemodule" - - -Scope rules ------------ -Identifiers are valid from the point of their declaration until the end of -the block in which the declaration occurred. The range where the identifier -is known is the scope of the identifier. The exact scope of an -identifier depends on the way it was declared. - -Block scope -~~~~~~~~~~~ -The *scope* of a variable declared in the declaration part of a block -is valid from the point of declaration until the end of the block. If a -block contains a second block, in which the identifier is redeclared, -then inside this block, the second declaration will be valid. Upon -leaving the inner block, the first declaration is valid again. An -identifier cannot be redefined in the same block, except if valid for -procedure or iterator overloading purposes. - - -Tuple or object scope -~~~~~~~~~~~~~~~~~~~~~ -The field identifiers inside a tuple or object definition are valid in the -following places: - -* To the end of the tuple/object definition. -* Field designators of a variable of the given tuple/object type. -* In all descendant types of the object type. - -Module scope -~~~~~~~~~~~~ -All identifiers of a module are valid from the point of declaration until -the end of the module. Identifiers from indirectly dependent modules are *not* -available. The `system`:idx: module is automatically imported in every module. - -If a module imports an identifier by two different modules, each occurrence of -the identifier has to be qualified, unless it is an overloaded procedure or -iterator in which case the overloading resolution takes place: - -.. code-block:: nim - # Module A - var x*: string - -.. code-block:: nim - # Module B - var x*: int - -.. code-block:: nim - # Module C - import A, B - write(stdout, x) # error: x is ambiguous - write(stdout, A.x) # no error: qualifier used - - var x = 4 - write(stdout, x) # not ambiguous: uses the module C's x diff --git a/doc/manual/pragmas.txt b/doc/manual/pragmas.txt deleted file mode 100644 index cd26a9448..000000000 --- a/doc/manual/pragmas.txt +++ /dev/null @@ -1,1156 +0,0 @@ -Pragmas -======= - -Pragmas are Nim's method to give the compiler additional information / -commands without introducing a massive number of new keywords. Pragmas are -processed on the fly during semantic checking. Pragmas are enclosed in the -special ``{.`` and ``.}`` curly brackets. Pragmas are also often used as a -first implementation to play with a language feature before a nicer syntax -to access the feature becomes available. - - -deprecated pragma ------------------ - -The deprecated pragma is used to mark a symbol as deprecated: - -.. code-block:: nim - proc p() {.deprecated.} - var x {.deprecated.}: char - -It can also be used as a statement, in that case it takes a list of *renamings*. - -.. code-block:: nim - type - File = object - Stream = ref object - {.deprecated: [TFile: File, PStream: Stream].} - - -noSideEffect pragma -------------------- -The ``noSideEffect`` pragma is used to mark a proc/iterator to have no side -effects. This means that the proc/iterator only changes locations that are -reachable from its parameters and the return value only depends on the -arguments. If none of its parameters have the type ``var T`` -or ``ref T`` or ``ptr T`` this means no locations are modified. It is a static -error to mark a proc/iterator to have no side effect if the compiler cannot -verify this. - -As a special semantic rule, the built-in `debugEcho <system.html#debugEcho>`_ -pretends to be free of side effects, so that it can be used for debugging -routines marked as ``noSideEffect``. - -**Future directions**: ``func`` may become a keyword and syntactic sugar for a -proc with no side effects: - -.. code-block:: nim - func `+` (x, y: int): int - - -destructor pragma ------------------ - -The ``destructor`` pragma is used to mark a proc to act as a type destructor. -Its usage is deprecated, see `type bound operations`_ instead. - -override pragma ---------------- - -See `type bound operations`_ instead. - -procvar pragma --------------- -The ``procvar`` pragma is used to mark a proc that it can be passed to a -procedural variable. - - -compileTime pragma ------------------- -The ``compileTime`` pragma is used to mark a proc or variable to be used at -compile time only. No code will be generated for it. Compile time procs are -useful as helpers for macros. Since version 0.12.0 of the language, a proc -that uses ``system.NimNode`` within its parameter types is implicitly declared -``compileTime``: - -.. code-block:: nim - proc astHelper(n: NimNode): NimNode = - result = n - -Is the same as: - -.. code-block:: nim - proc astHelper(n: NimNode): NimNode {.compileTime.} = - result = n - - -noReturn pragma ---------------- -The ``noreturn`` pragma is used to mark a proc that never returns. - - -acyclic pragma --------------- -The ``acyclic`` pragma can be used for object types to mark them as acyclic -even though they seem to be cyclic. This is an **optimization** for the garbage -collector to not consider objects of this type as part of a cycle: - -.. code-block:: nim - type - Node = ref NodeObj - NodeObj {.acyclic, final.} = object - left, right: Node - data: string - -Or if we directly use a ref object: - -.. code-block:: nim - type - Node = ref object {.acyclic, final.} - left, right: Node - data: string - -In the example a tree structure is declared with the ``Node`` type. Note that -the type definition is recursive and the GC has to assume that objects of -this type may form a cyclic graph. The ``acyclic`` pragma passes the -information that this cannot happen to the GC. If the programmer uses the -``acyclic`` pragma for data types that are in reality cyclic, the GC may leak -memory, but nothing worse happens. - -**Future directions**: The ``acyclic`` pragma may become a property of a -``ref`` type: - -.. code-block:: nim - type - Node = acyclic ref NodeObj - NodeObj = object - left, right: Node - data: string - - -final pragma ------------- -The ``final`` pragma can be used for an object type to specify that it -cannot be inherited from. - - -shallow pragma --------------- -The ``shallow`` pragma affects the semantics of a type: The compiler is -allowed to make a shallow copy. This can cause serious semantic issues and -break memory safety! However, it can speed up assignments considerably, -because the semantics of Nim require deep copying of sequences and strings. -This can be expensive, especially if sequences are used to build a tree -structure: - -.. code-block:: nim - type - NodeKind = enum nkLeaf, nkInner - Node {.final, shallow.} = object - case kind: NodeKind - of nkLeaf: - strVal: string - of nkInner: - children: seq[Node] - - -pure pragma ------------ -An object type can be marked with the ``pure`` pragma so that its type -field which is used for runtime type identification is omitted. This used to be -necessary for binary compatibility with other compiled languages. - -An enum type can be marked as ``pure``. Then access of its fields always -requires full qualification. - - -asmNoStackFrame pragma ----------------------- -A proc can be marked with the ``asmNoStackFrame`` pragma to tell the compiler -it should not generate a stack frame for the proc. There are also no exit -statements like ``return result;`` generated and the generated C function is -declared as ``__declspec(naked)`` or ``__attribute__((naked))`` (depending on -the used C compiler). - -**Note**: This pragma should only be used by procs which consist solely of -assembler statements. - -error pragma ------------- -The ``error`` pragma is used to make the compiler output an error message -with the given content. Compilation does not necessarily abort after an error -though. - -The ``error`` pragma can also be used to -annotate a symbol (like an iterator or proc). The *usage* of the symbol then -triggers a compile-time error. This is especially useful to rule out that some -operation is valid due to overloading and type conversions: - -.. code-block:: nim - ## check that underlying int values are compared and not the pointers: - proc `==`(x, y: ptr int): bool {.error.} - - -fatal pragma ------------- -The ``fatal`` pragma is used to make the compiler output an error message -with the given content. In contrast to the ``error`` pragma, compilation -is guaranteed to be aborted by this pragma. Example: - -.. code-block:: nim - when not defined(objc): - {.fatal: "Compile this program with the objc command!".} - -warning pragma --------------- -The ``warning`` pragma is used to make the compiler output a warning message -with the given content. Compilation continues after the warning. - -hint pragma ------------ -The ``hint`` pragma is used to make the compiler output a hint message with -the given content. Compilation continues after the hint. - -line pragma ------------ -The ``line`` pragma can be used to affect line information of the annotated -statement as seen in stack backtraces: - -.. code-block:: nim - - template myassert*(cond: untyped, msg = "") = - if not cond: - # change run-time line information of the 'raise' statement: - {.line: InstantiationInfo().}: - raise newException(EAssertionFailed, msg) - -If the ``line`` pragma is used with a parameter, the parameter needs be a -``tuple[filename: string, line: int]``. If it is used without a parameter, -``system.InstantiationInfo()`` is used. - - -linearScanEnd pragma --------------------- -The ``linearScanEnd`` pragma can be used to tell the compiler how to -compile a Nim `case`:idx: statement. Syntactically it has to be used as a -statement: - -.. code-block:: nim - case myInt - of 0: - echo "most common case" - of 1: - {.linearScanEnd.} - echo "second most common case" - of 2: echo "unlikely: use branch table" - else: echo "unlikely too: use branch table for ", myInt - -In the example, the case branches ``0`` and ``1`` are much more common than -the other cases. Therefore the generated assembler code should test for these -values first, so that the CPU's branch predictor has a good chance to succeed -(avoiding an expensive CPU pipeline stall). The other cases might be put into a -jump table for O(1) overhead, but at the cost of a (very likely) pipeline -stall. - -The ``linearScanEnd`` pragma should be put into the last branch that should be -tested against via linear scanning. If put into the last branch of the -whole ``case`` statement, the whole ``case`` statement uses linear scanning. - - -computedGoto pragma -------------------- -The ``computedGoto`` pragma can be used to tell the compiler how to -compile a Nim `case`:idx: in a ``while true`` statement. -Syntactically it has to be used as a statement inside the loop: - -.. code-block:: nim - - type - MyEnum = enum - enumA, enumB, enumC, enumD, enumE - - proc vm() = - var instructions: array [0..100, MyEnum] - instructions[2] = enumC - instructions[3] = enumD - instructions[4] = enumA - instructions[5] = enumD - instructions[6] = enumC - instructions[7] = enumA - instructions[8] = enumB - - instructions[12] = enumE - var pc = 0 - while true: - {.computedGoto.} - let instr = instructions[pc] - case instr - of enumA: - echo "yeah A" - of enumC, enumD: - echo "yeah CD" - of enumB: - echo "yeah B" - of enumE: - break - inc(pc) - - vm() - -As the example shows ``computedGoto`` is mostly useful for interpreters. If -the underlying backend (C compiler) does not support the computed goto -extension the pragma is simply ignored. - - -unroll pragma -------------- -The ``unroll`` pragma can be used to tell the compiler that it should unroll -a `for`:idx: or `while`:idx: loop for runtime efficiency: - -.. code-block:: nim - proc searchChar(s: string, c: char): int = - for i in 0 .. s.high: - {.unroll: 4.} - if s[i] == c: return i - result = -1 - -In the above example, the search loop is unrolled by a factor 4. The unroll -factor can be left out too; the compiler then chooses an appropriate unroll -factor. - -**Note**: Currently the compiler recognizes but ignores this pragma. - - -immediate pragma ----------------- - -See `Typed vs untyped parameters`_. - - -compilation option pragmas --------------------------- -The listed pragmas here can be used to override the code generation options -for a proc/method/converter. - -The implementation currently provides the following possible options (various -others may be added later). - -=============== =============== ============================================ -pragma allowed values description -=============== =============== ============================================ -checks on|off Turns the code generation for all runtime - checks on or off. -boundChecks on|off Turns the code generation for array bound - checks on or off. -overflowChecks on|off Turns the code generation for over- or - underflow checks on or off. -nilChecks on|off Turns the code generation for nil pointer - checks on or off. -assertions on|off Turns the code generation for assertions - on or off. -warnings on|off Turns the warning messages of the compiler - on or off. -hints on|off Turns the hint messages of the compiler - on or off. -optimization none|speed|size Optimize the code for speed or size, or - disable optimization. -patterns on|off Turns the term rewriting templates/macros - on or off. -callconv cdecl|... Specifies the default calling convention for - all procedures (and procedure types) that - follow. -=============== =============== ============================================ - -Example: - -.. code-block:: nim - {.checks: off, optimization: speed.} - # compile without runtime checks and optimize for speed - - -push and pop pragmas --------------------- -The `push/pop`:idx: pragmas are very similar to the option directive, -but are used to override the settings temporarily. Example: - -.. code-block:: nim - {.push checks: off.} - # compile this section without runtime checks as it is - # speed critical - # ... some code ... - {.pop.} # restore old settings - - -register pragma ---------------- -The ``register`` pragma is for variables only. It declares the variable as -``register``, giving the compiler a hint that the variable should be placed -in a hardware register for faster access. C compilers usually ignore this -though and for good reasons: Often they do a better job without it anyway. - -In highly specific cases (a dispatch loop of a bytecode interpreter for -example) it may provide benefits, though. - - -global pragma -------------- -The ``global`` pragma can be applied to a variable within a proc to instruct -the compiler to store it in a global location and initialize it once at program -startup. - -.. code-block:: nim - proc isHexNumber(s: string): bool = - var pattern {.global.} = re"[0-9a-fA-F]+" - result = s.match(pattern) - -When used within a generic proc, a separate unique global variable will be -created for each instantiation of the proc. The order of initialization of -the created global variables within a module is not defined, but all of them -will be initialized after any top-level variables in their originating module -and before any variable in a module that imports it. - -deadCodeElim pragma -------------------- -The ``deadCodeElim`` pragma only applies to whole modules: It tells the -compiler to activate (or deactivate) dead code elimination for the module the -pragma appears in. - -The ``--deadCodeElim:on`` command line switch has the same effect as marking -every module with ``{.deadCodeElim:on}``. However, for some modules such as -the GTK wrapper it makes sense to *always* turn on dead code elimination - -no matter if it is globally active or not. - -Example: - -.. code-block:: nim - {.deadCodeElim: on.} - - -.. - NoForward pragma - ---------------- - The ``noforward`` pragma can be used to turn on and off a special compilation - mode that to large extent eliminates the need for forward declarations. In this - mode, the proc definitions may appear out of order and the compiler will postpone - their semantic analysis and compilation until it actually needs to generate code - using the definitions. In this regard, this mode is similar to the modus operandi - of dynamic scripting languages, where the function calls are not resolved until - the code is executed. Here is the detailed algorithm taken by the compiler: - - 1. When a callable symbol is first encountered, the compiler will only note the - symbol callable name and it will add it to the appropriate overload set in the - current scope. At this step, it won't try to resolve any of the type expressions - used in the signature of the symbol (so they can refer to other not yet defined - symbols). - - 2. When a top level call is encountered (usually at the very end of the module), - the compiler will try to determine the actual types of all of the symbols in the - matching overload set. This is a potentially recursive process as the signatures - of the symbols may include other call expressions, whose types will be resolved - at this point too. - - 3. Finally, after the best overload is picked, the compiler will start - compiling the body of the respective symbol. This in turn will lead the - compiler to discover more call expressions that need to be resolved and steps - 2 and 3 will be repeated as necessary. - - Please note that if a callable symbol is never used in this scenario, its body - will never be compiled. This is the default behavior leading to best compilation - times, but if exhaustive compilation of all definitions is required, using - ``nim check`` provides this option as well. - - Example: - - .. code-block:: nim - - {.noforward: on.} - - proc foo(x: int) = - bar x - - proc bar(x: int) = - echo x - - foo(10) - - -pragma pragma -------------- - -The ``pragma`` pragma can be used to declare user defined pragmas. This is -useful because Nim's templates and macros do not affect pragmas. User -defined pragmas are in a different module-wide scope than all other symbols. -They cannot be imported from a module. - -Example: - -.. code-block:: nim - when appType == "lib": - {.pragma: rtl, exportc, dynlib, cdecl.} - else: - {.pragma: rtl, importc, dynlib: "client.dll", cdecl.} - - proc p*(a, b: int): int {.rtl.} = - result = a+b - -In the example a new pragma named ``rtl`` is introduced that either imports -a symbol from a dynamic library or exports the symbol for dynamic library -generation. - - -Disabling certain messages --------------------------- -Nim generates some warnings and hints ("line too long") that may annoy the -user. A mechanism for disabling certain messages is provided: Each hint -and warning message contains a symbol in brackets. This is the message's -identifier that can be used to enable or disable it: - -.. code-block:: Nim - {.hint[LineTooLong]: off.} # turn off the hint about too long lines - -This is often better than disabling all warnings at once. - - -used pragma ------------ - -Nim produces a warning for symbols that are not exported and not used either. -The ``used`` pragma can be attached to a symbol to suppress this warning. This -is particularly useful when the symbol was generated by a macro: - -.. code-block:: nim - template implementArithOps(T) = - proc echoAdd(a, b: T) {.used.} = - echo a + b - proc echoSub(a, b: T) {.used.} = - echo a - b - - # no warning produced for the unused 'echoSub' - implementArithOps(int) - echoAdd 3, 5 - - - -experimental pragma -------------------- - -The ``experimental`` pragma enables experimental language features. Depending -on the concrete feature this means that the feature is either considered -too unstable for an otherwise stable release or that the future of the feature -is uncertain (it may be removed any time). - -Example: - -.. code-block:: nim - {.experimental.} - type - FooId = distinct int - BarId = distinct int - using - foo: FooId - bar: BarId - - proc useUsing(bar, foo) = - echo "bar is of type BarId" - echo "foo is of type FooId" - - -Implementation Specific Pragmas -=============================== - -This section describes additional pragmas that the current Nim implementation -supports but which should not be seen as part of the language specification. - -Bitsize pragma --------------- - -The ``bitsize`` pragma is for object field members. It declares the field as -a bitfield in C/C++. - -.. code-block:: Nim - type - mybitfield = object - flag {.bitsize:1.}: cuint - -generates: - -.. code-block:: C - struct mybitfield { - unsigned int flag:1; - }; - - -Volatile pragma ---------------- -The ``volatile`` pragma is for variables only. It declares the variable as -``volatile``, whatever that means in C/C++ (its semantics are not well defined -in C/C++). - -**Note**: This pragma will not exist for the LLVM backend. - - -NoDecl pragma -------------- -The ``noDecl`` pragma can be applied to almost any symbol (variable, proc, -type, etc.) and is sometimes useful for interoperability with C: -It tells Nim that it should not generate a declaration for the symbol in -the C code. For example: - -.. code-block:: Nim - var - EACCES {.importc, noDecl.}: cint # pretend EACCES was a variable, as - # Nim does not know its value - -However, the ``header`` pragma is often the better alternative. - -**Note**: This will not work for the LLVM backend. - - -Header pragma -------------- -The ``header`` pragma is very similar to the ``noDecl`` pragma: It can be -applied to almost any symbol and specifies that it should not be declared -and instead the generated code should contain an ``#include``: - -.. code-block:: Nim - type - PFile {.importc: "FILE*", header: "<stdio.h>".} = distinct pointer - # import C's FILE* type; Nim will treat it as a new pointer type - -The ``header`` pragma always expects a string constant. The string contant -contains the header file: As usual for C, a system header file is enclosed -in angle brackets: ``<>``. If no angle brackets are given, Nim -encloses the header file in ``""`` in the generated C code. - -**Note**: This will not work for the LLVM backend. - - -IncompleteStruct pragma ------------------------ -The ``incompleteStruct`` pragma tells the compiler to not use the -underlying C ``struct`` in a ``sizeof`` expression: - -.. code-block:: Nim - type - DIR* {.importc: "DIR", header: "<dirent.h>", - final, pure, incompleteStruct.} = object - - -Compile pragma --------------- -The ``compile`` pragma can be used to compile and link a C/C++ source file -with the project: - -.. code-block:: Nim - {.compile: "myfile.cpp".} - -**Note**: Nim computes a SHA1 checksum and only recompiles the file if it -has changed. You can use the ``-f`` command line option to force recompilation -of the file. - - -Link pragma ------------ -The ``link`` pragma can be used to link an additional file with the project: - -.. code-block:: Nim - {.link: "myfile.o".} - - -PassC pragma ------------- -The ``passC`` pragma can be used to pass additional parameters to the C -compiler like you would using the commandline switch ``--passC``: - -.. code-block:: Nim - {.passC: "-Wall -Werror".} - -Note that you can use ``gorge`` from the `system module <system.html>`_ to -embed parameters from an external command at compile time: - -.. code-block:: Nim - {.passC: gorge("pkg-config --cflags sdl").} - -PassL pragma ------------- -The ``passL`` pragma can be used to pass additional parameters to the linker -like you would using the commandline switch ``--passL``: - -.. code-block:: Nim - {.passL: "-lSDLmain -lSDL".} - -Note that you can use ``gorge`` from the `system module <system.html>`_ to -embed parameters from an external command at compile time: - -.. code-block:: Nim - {.passL: gorge("pkg-config --libs sdl").} - - -Emit pragma ------------ -The ``emit`` pragma can be used to directly affect the output of the -compiler's code generator. So it makes your code unportable to other code -generators/backends. Its usage is highly discouraged! However, it can be -extremely useful for interfacing with `C++`:idx: or `Objective C`:idx: code. - -Example: - -.. code-block:: Nim - {.emit: """ - static int cvariable = 420; - """.} - - {.push stackTrace:off.} - proc embedsC() = - var nimVar = 89 - # access Nim symbols within an emit section outside of string literals: - {.emit: ["""fprintf(stdout, "%d\n", cvariable + (int)""", nimVar, ");"].} - {.pop.} - - embedsC() - -For backwards compatibility, if the argument to the ``emit`` statement -is a single string literal, Nim symbols can be referred to via backticks. -This usage is however deprecated. - -For a toplevel emit statement the section where in the generated C/C++ file -the code should be emitted can be influenced via the -prefixes ``/*TYPESECTION*/`` or ``/*VARSECTION*/`` or ``/*INCLUDESECTION*/``: - -.. code-block:: Nim - {.emit: """/*TYPESECTION*/ - struct Vector3 { - public: - Vector3(): x(5) {} - Vector3(float x_): x(x_) {} - float x; - }; - """.} - - type Vector3 {.importcpp: "Vector3", nodecl} = object - x: cfloat - - proc constructVector3(a: cfloat): Vector3 {.importcpp: "Vector3(@)", nodecl} - - -ImportCpp pragma ----------------- - -**Note**: `c2nim <c2nim.html>`_ can parse a large subset of C++ and knows -about the ``importcpp`` pragma pattern language. It is not necessary -to know all the details described here. - - -Similar to the `importc pragma for C -<#foreign-function-interface-importc-pragma>`_, the -``importcpp`` pragma can be used to import `C++`:idx: methods or C++ symbols -in general. The generated code then uses the C++ method calling -syntax: ``obj->method(arg)``. In combination with the ``header`` and ``emit`` -pragmas this allows *sloppy* interfacing with libraries written in C++: - -.. code-block:: Nim - # Horrible example of how to interface with a C++ engine ... ;-) - - {.link: "/usr/lib/libIrrlicht.so".} - - {.emit: """ - using namespace irr; - using namespace core; - using namespace scene; - using namespace video; - using namespace io; - using namespace gui; - """.} - - const - irr = "<irrlicht/irrlicht.h>" - - type - IrrlichtDeviceObj {.final, header: irr, - importcpp: "IrrlichtDevice".} = object - IrrlichtDevice = ptr IrrlichtDeviceObj - - proc createDevice(): IrrlichtDevice {. - header: irr, importcpp: "createDevice(@)".} - proc run(device: IrrlichtDevice): bool {. - header: irr, importcpp: "#.run(@)".} - -The compiler needs to be told to generate C++ (command ``cpp``) for -this to work. The conditional symbol ``cpp`` is defined when the compiler -emits C++ code. - - -Namespaces -~~~~~~~~~~ - -The *sloppy interfacing* example uses ``.emit`` to produce ``using namespace`` -declarations. It is usually much better to instead refer to the imported name -via the ``namespace::identifier`` notation: - -.. code-block:: nim - type - IrrlichtDeviceObj {.final, header: irr, - importcpp: "irr::IrrlichtDevice".} = object - - -Importcpp for enums -~~~~~~~~~~~~~~~~~~~ - -When ``importcpp`` is applied to an enum type the numerical enum values are -annotated with the C++ enum type, like in this example: ``((TheCppEnum)(3))``. -(This turned out to be the simplest way to implement it.) - - -Importcpp for procs -~~~~~~~~~~~~~~~~~~~ - -Note that the ``importcpp`` variant for procs uses a somewhat cryptic pattern -language for maximum flexibility: - -- A hash ``#`` symbol is replaced by the first or next argument. -- A dot following the hash ``#.`` indicates that the call should use C++'s dot - or arrow notation. -- An at symbol ``@`` is replaced by the remaining arguments, separated by - commas. - -For example: - -.. code-block:: nim - proc cppMethod(this: CppObj, a, b, c: cint) {.importcpp: "#.CppMethod(@)".} - var x: ptr CppObj - cppMethod(x[], 1, 2, 3) - -Produces: - -.. code-block:: C - x->CppMethod(1, 2, 3) - -As a special rule to keep backwards compatibility with older versions of the -``importcpp`` pragma, if there is no special pattern -character (any of ``# ' @``) at all, C++'s -dot or arrow notation is assumed, so the above example can also be written as: - -.. code-block:: nim - proc cppMethod(this: CppObj, a, b, c: cint) {.importcpp: "CppMethod".} - -Note that the pattern language naturally also covers C++'s operator overloading -capabilities: - -.. code-block:: nim - proc vectorAddition(a, b: Vec3): Vec3 {.importcpp: "# + #".} - proc dictLookup(a: Dict, k: Key): Value {.importcpp: "#[#]".} - - -- An apostrophe ``'`` followed by an integer ``i`` in the range 0..9 - is replaced by the i'th parameter *type*. The 0th position is the result - type. This can be used to pass types to C++ function templates. Between - the ``'`` and the digit an asterisk can be used to get to the base type - of the type. (So it "takes away a star" from the type; ``T*`` becomes ``T``.) - Two stars can be used to get to the element type of the element type etc. - -For example: - -.. code-block:: nim - - type Input {.importcpp: "System::Input".} = object - proc getSubsystem*[T](): ptr T {.importcpp: "SystemManager::getSubsystem<'*0>()", nodecl.} - - let x: ptr Input = getSubsystem[Input]() - -Produces: - -.. code-block:: C - x = SystemManager::getSubsystem<System::Input>() - - -- ``#@`` is a special case to support a ``cnew`` operation. It is required so - that the call expression is inlined directly, without going through a - temporary location. This is only required to circumvent a limitation of the - current code generator. - -For example C++'s ``new`` operator can be "imported" like this: - -.. code-block:: nim - proc cnew*[T](x: T): ptr T {.importcpp: "(new '*0#@)", nodecl.} - - # constructor of 'Foo': - proc constructFoo(a, b: cint): Foo {.importcpp: "Foo(@)".} - - let x = cnew constructFoo(3, 4) - -Produces: - -.. code-block:: C - x = new Foo(3, 4) - -However, depending on the use case ``new Foo`` can also be wrapped like this -instead: - -.. code-block:: nim - proc newFoo(a, b: cint): ptr Foo {.importcpp: "new Foo(@)".} - - let x = newFoo(3, 4) - - -Wrapping constructors -~~~~~~~~~~~~~~~~~~~~~ - -Sometimes a C++ class has a private copy constructor and so code like -``Class c = Class(1,2);`` must not be generated but instead ``Class c(1,2);``. -For this purpose the Nim proc that wraps a C++ constructor needs to be -annotated with the `constructor`:idx: pragma. This pragma also helps to generate -faster C++ code since construction then doesn't invoke the copy constructor: - -.. code-block:: nim - # a better constructor of 'Foo': - proc constructFoo(a, b: cint): Foo {.importcpp: "Foo(@)", constructor.} - - -Wrapping destructors -~~~~~~~~~~~~~~~~~~~~ - -Since Nim generates C++ directly, any destructor is called implicitly by the -C++ compiler at the scope exits. This means that often one can get away with -not wrapping the destructor at all! However when it needs to be invoked -explicitly, it needs to be wrapped. But the pattern language already provides -everything that is required for that: - -.. code-block:: nim - proc destroyFoo(this: var Foo) {.importcpp: "#.~Foo()".} - - -Importcpp for objects -~~~~~~~~~~~~~~~~~~~~~ - -Generic ``importcpp``'ed objects are mapped to C++ templates. This means that -you can import C++'s templates rather easily without the need for a pattern -language for object types: - -.. code-block:: nim - type - StdMap {.importcpp: "std::map", header: "<map>".} [K, V] = object - proc `[]=`[K, V](this: var StdMap[K, V]; key: K; val: V) {. - importcpp: "#[#] = #", header: "<map>".} - - var x: StdMap[cint, cdouble] - x[6] = 91.4 - - -Produces: - -.. code-block:: C - std::map<int, double> x; - x[6] = 91.4; - - -- If more precise control is needed, the apostrophe ``'`` can be used in the - supplied pattern to denote the concrete type parameters of the generic type. - See the usage of the apostrophe operator in proc patterns for more details. - -.. code-block:: nim - - type - VectorIterator {.importcpp: "std::vector<'0>::iterator".} [T] = object - - var x: VectorIterator[cint] - - -Produces: - -.. code-block:: C - - std::vector<int>::iterator x; - - -ImportObjC pragma ------------------ -Similar to the `importc pragma for C -<#foreign-function-interface-importc-pragma>`_, the ``importobjc`` pragma can -be used to import `Objective C`:idx: methods. The generated code then uses the -Objective C method calling syntax: ``[obj method param1: arg]``. -In addition with the ``header`` and ``emit`` pragmas this -allows *sloppy* interfacing with libraries written in Objective C: - -.. code-block:: Nim - # horrible example of how to interface with GNUStep ... - - {.passL: "-lobjc".} - {.emit: """ - #include <objc/Object.h> - @interface Greeter:Object - { - } - - - (void)greet:(long)x y:(long)dummy; - @end - - #include <stdio.h> - @implementation Greeter - - - (void)greet:(long)x y:(long)dummy - { - printf("Hello, World!\n"); - } - @end - - #include <stdlib.h> - """.} - - type - Id {.importc: "id", header: "<objc/Object.h>", final.} = distinct int - - proc newGreeter: Id {.importobjc: "Greeter new", nodecl.} - proc greet(self: Id, x, y: int) {.importobjc: "greet", nodecl.} - proc free(self: Id) {.importobjc: "free", nodecl.} - - var g = newGreeter() - g.greet(12, 34) - g.free() - -The compiler needs to be told to generate Objective C (command ``objc``) for -this to work. The conditional symbol ``objc`` is defined when the compiler -emits Objective C code. - - -CodegenDecl pragma ------------------- - -The ``codegenDecl`` pragma can be used to directly influence Nim's code -generator. It receives a format string that determines how the variable -or proc is declared in the generated code. - -For variables $1 in the format string represents the type of the variable -and $2 is the name of the variable. - -The following Nim code: - -.. code-block:: nim - var - a {.codegenDecl: "$# progmem $#".}: int - -will generate this C code: - -.. code-block:: c - int progmem a - -For procedures $1 is the return type of the procedure, $2 is the name of -the procedure and $3 is the parameter list. - -The following nim code: - -.. code-block:: nim - proc myinterrupt() {.codegenDecl: "__interrupt $# $#$#".} = - echo "realistic interrupt handler" - -will generate this code: - -.. code-block:: c - __interrupt void myinterrupt() - - -InjectStmt pragma ------------------ - -The ``injectStmt`` pragma can be used to inject a statement before every -other statement in the current module. It is only supposed to be used for -debugging: - -.. code-block:: nim - {.injectStmt: gcInvariants().} - - # ... complex code here that produces crashes ... - -compile time define pragmas ---------------------------- - -The pragmas listed here can be used to optionally accept values from -the -d/--define option at compile time. - -The implementation currently provides the following possible options (various -others may be added later). - -================= ============================================ -pragma description -================= ============================================ -`intdefine`:idx: Reads in a build-time define as an integer -`strdefine`:idx: Reads in a build-time define as a string -================= ============================================ - -.. code-block:: nim - const FooBar {.intdefine.}: int = 5 - echo FooBar - -.. code-block:: bash - nim c -d:FooBar=42 foobar.c - -In the above example, providing the -d flag causes the symbol -``FooBar`` to be overwritten at compile time, printing out 42. If the -``-d:FooBar=42`` were to be omitted, the default value of 5 would be -used. - - -Custom annotations ------------------- -It is possible to define custom typed pragmas. Custom pragmas do not effect -code generation directly, but their presence can be detected by macros. -Custom pragmas are defined using templates annotated with pragma ``pragma``: - -.. code-block:: nim - template dbTable(name: string, table_space: string = nil) {.pragma.} - template dbKey(name: string = nil, primary_key: bool = false) {.pragma.} - template dbForeignKey(t: typedesc) {.pragma.} - template dbIgnore {.pragma.} - - -Consider stylized example of possible Object Relation Mapping (ORM) implementation: - -.. code-block:: nim - const tblspace {.strdefine.} = "dev" # switch for dev, test and prod environments - - type - User {.dbTable("users", tblspace).} = object - id {.dbKey(primary_key = true).}: int - name {.dbKey"full_name".}: string - is_cached {.dbIgnore.}: bool - age: int - - UserProfile {.dbTable("profiles", tblspace).} = object - id {.dbKey(primary_key = true).}: int - user_id {.dbForeignKey: User.}: int - read_access: bool - write_access: bool - admin_acess: bool - -In this example custom pragmas are used to describe how Nim objects are -mapped to the schema of the relational database. Custom pragmas can have -zero or more arguments. In order to pass multiple arguments use one of -template call syntaxes. All arguments are typed and follow standard -overload resolution rules for templates. Therefore, it is possible to have -default values for arguments, pass by name, varargs, etc. - -Custom pragmas can be used in all locations where ordinary pragmas can be -specified. It is possible to annotate procs, templates, type and variable -definitions, statements, etc. - -Macros module includes helpers which can be used to simplify custom pragma -access `hasCustomPragma`, `getCustomPragmaVal`. Please consult macros module -documentation for details. These macros are no magic, they don't do anything -you cannot do yourself by walking AST object representation. - -More examples with custom pragmas: - - Better serialization/deserialization control: - - .. code-block:: nim - type MyObj = object - a {.dontSerialize.}: int - b {.defaultDeserialize: 5.}: int - c {.serializationKey: "_c".}: string - - - Adopting type for gui inspector in a game engine: - - .. code-block:: nim - type MyComponent = object - position {.editable, animatable.}: Vector3 - alpha {.editRange: [0.0..1.0], animatable.}: float32 - - diff --git a/doc/manual/procs.txt b/doc/manual/procs.txt deleted file mode 100644 index 10dd39a52..000000000 --- a/doc/manual/procs.txt +++ /dev/null @@ -1,697 +0,0 @@ -Procedures -========== - -What most programming languages call `methods`:idx: or `functions`:idx: are -called `procedures`:idx: in Nim. A procedure -declaration consists of an identifier, zero or more formal parameters, a return -value type and a block of code. Formal parameters are declared as a list of -identifiers separated by either comma or semicolon. A parameter is given a type -by ``: typename``. The type applies to all parameters immediately before it, -until either the beginning of the parameter list, a semicolon separator or an -already typed parameter, is reached. The semicolon can be used to make -separation of types and subsequent identifiers more distinct. - -.. code-block:: nim - # Using only commas - proc foo(a, b: int, c, d: bool): int - - # Using semicolon for visual distinction - proc foo(a, b: int; c, d: bool): int - - # Will fail: a is untyped since ';' stops type propagation. - proc foo(a; b: int; c, d: bool): int - -A parameter may be declared with a default value which is used if the caller -does not provide a value for the argument. - -.. code-block:: nim - # b is optional with 47 as its default value - proc foo(a: int, b: int = 47): int - -Parameters can be declared mutable and so allow the proc to modify those -arguments, by using the type modifier `var`. - -.. code-block:: nim - # "returning" a value to the caller through the 2nd argument - # Notice that the function uses no actual return value at all (ie void) - proc foo(inp: int, outp: var int) = - outp = inp + 47 - -If the proc declaration has no body, it is a `forward`:idx: declaration. If the -proc returns a value, the procedure body can access an implicitly declared -variable named `result`:idx: that represents the return value. Procs can be -overloaded. The overloading resolution algorithm determines which proc is the -best match for the arguments. Example: - -.. code-block:: nim - - proc toLower(c: char): char = # toLower for characters - if c in {'A'..'Z'}: - result = chr(ord(c) + (ord('a') - ord('A'))) - else: - result = c - - proc toLower(s: string): string = # toLower for strings - result = newString(len(s)) - for i in 0..len(s) - 1: - result[i] = toLower(s[i]) # calls toLower for characters; no recursion! - -Calling a procedure can be done in many different ways: - -.. code-block:: nim - proc callme(x, y: int, s: string = "", c: char, b: bool = false) = ... - - # call with positional arguments # parameter bindings: - callme(0, 1, "abc", '\t', true) # (x=0, y=1, s="abc", c='\t', b=true) - # call with named and positional arguments: - callme(y=1, x=0, "abd", '\t') # (x=0, y=1, s="abd", c='\t', b=false) - # call with named arguments (order is not relevant): - callme(c='\t', y=1, x=0) # (x=0, y=1, s="", c='\t', b=false) - # call as a command statement: no () needed: - callme 0, 1, "abc", '\t' # (x=0, y=1, s="abc", c='\t', b=false) - -A procedure may call itself recursively. - - -`Operators`:idx: are procedures with a special operator symbol as identifier: - -.. code-block:: nim - proc `$` (x: int): string = - # converts an integer to a string; this is a prefix operator. - result = intToStr(x) - -Operators with one parameter are prefix operators, operators with two -parameters are infix operators. (However, the parser distinguishes these from -the operator's position within an expression.) There is no way to declare -postfix operators: all postfix operators are built-in and handled by the -grammar explicitly. - -Any operator can be called like an ordinary proc with the '`opr`' -notation. (Thus an operator can have more than two parameters): - -.. code-block:: nim - proc `*+` (a, b, c: int): int = - # Multiply and add - result = a * b + c - - assert `*+`(3, 4, 6) == `*`(a, `+`(b, c)) - - -Export marker -------------- - -If a declared symbol is marked with an `asterisk`:idx: it is exported from the -current module: - -.. code-block:: nim - - proc exportedEcho*(s: string) = echo s - proc `*`*(a: string; b: int): string = - result = newStringOfCap(a.len * b) - for i in 1..b: result.add a - - var exportedVar*: int - const exportedConst* = 78 - type - ExportedType* = object - exportedField*: int - - -Method call syntax ------------------- - -For object oriented programming, the syntax ``obj.method(args)`` can be used -instead of ``method(obj, args)``. The parentheses can be omitted if there are no -remaining arguments: ``obj.len`` (instead of ``len(obj)``). - -This method call syntax is not restricted to objects, it can be used -to supply any type of first argument for procedures: - -.. code-block:: nim - - echo "abc".len # is the same as echo len "abc" - echo "abc".toUpper() - echo {'a', 'b', 'c'}.card - stdout.writeLine("Hallo") # the same as writeLine(stdout, "Hallo") - -Another way to look at the method call syntax is that it provides the missing -postfix notation. - -The method call syntax conflicts with explicit generic instantiations: -``p[T](x)`` cannot be written as ``x.p[T]`` because ``x.p[T]`` is always -parsed as ``(x.p)[T]``. - -**Future directions**: ``p[.T.]`` might be introduced as an alternative syntax -to pass explicit types to a generic and then ``x.p[.T.]`` can be parsed as -``x.(p[.T.])``. - -See also: `Limitations of the method call syntax -<#templates-limitations-of-the-method-call-syntax>`_. - - -Properties ----------- -Nim has no need for *get-properties*: Ordinary get-procedures that are called -with the *method call syntax* achieve the same. But setting a value is -different; for this a special setter syntax is needed: - -.. code-block:: nim - - type - Socket* = ref object of RootObj - FHost: int # cannot be accessed from the outside of the module - # the `F` prefix is a convention to avoid clashes since - # the accessors are named `host` - - proc `host=`*(s: var Socket, value: int) {.inline.} = - ## setter of hostAddr - s.FHost = value - - proc host*(s: Socket): int {.inline.} = - ## getter of hostAddr - s.FHost - - var s: Socket - new s - s.host = 34 # same as `host=`(s, 34) - - -Command invocation syntax -------------------------- - -Routines can be invoked without the ``()`` if the call is syntactically -a statement. This command invocation syntax also works for -expressions, but then only a single argument may follow. This restriction -means ``echo f 1, f 2`` is parsed as ``echo(f(1), f(2))`` and not as -``echo(f(1, f(2)))``. The method call syntax may be used to provide one -more argument in this case: - -.. code-block:: nim - proc optarg(x: int, y: int = 0): int = x + y - proc singlearg(x: int): int = 20*x - - echo optarg 1, " ", singlearg 2 # prints "1 40" - - let fail = optarg 1, optarg 8 # Wrong. Too many arguments for a command call - let x = optarg(1, optarg 8) # traditional procedure call with 2 arguments - let y = 1.optarg optarg 8 # same thing as above, w/o the parenthesis - assert x == y - -The command invocation syntax also can't have complex expressions as arguments. -For example: (`anonymous procs`_), ``if``, ``case`` or ``try``. The (`do -notation`_) is limited, but usable for a single proc (see the example in the -corresponding section). Function calls with no arguments still needs () to -distinguish between a call and the function itself as a first class value. - - -Closures --------- - -Procedures can appear at the top level in a module as well as inside other -scopes, in which case they are called nested procs. A nested proc can access -local variables from its enclosing scope and if it does so it becomes a -closure. Any captured variables are stored in a hidden additional argument -to the closure (its environment) and they are accessed by reference by both -the closure and its enclosing scope (i.e. any modifications made to them are -visible in both places). The closure environment may be allocated on the heap -or on the stack if the compiler determines that this would be safe. - -Creating closures in loops -~~~~~~~~~~~~~~~~ - -Since closures capture local variables by reference it is often not wanted -behavior inside loop bodies. See `closureScope <system.html#closureScope>`_ -for details on how to change this behavior. - -Anonymous Procs ---------------- - -Procs can also be treated as expressions, in which case it's allowed to omit -the proc's name. - -.. code-block:: nim - var cities = @["Frankfurt", "Tokyo", "New York", "Kyiv"] - - cities.sort(proc (x,y: string): int = - cmp(x.len, y.len)) - - -Procs as expressions can appear both as nested procs and inside top level -executable code. - - -Do notation ------------ - -As a special more convenient notation, proc expressions involved in procedure -calls can use the ``do`` keyword: - -.. code-block:: nim - sort(cities) do (x,y: string) -> int: - cmp(x.len, y.len) - - # Less parenthesis using the method plus command syntax: - cities = cities.map do (x:string) -> string: - "City of " & x - - # In macros, the do notation is often used for quasi-quoting - macroResults.add quote do: - if not `ex`: - echo `info`, ": Check failed: ", `expString` - -``do`` is written after the parentheses enclosing the regular proc params. -The proc expression represented by the do block is appended to them. -In calls using the command syntax, the do block will bind to the immediately -preceeding expression, transforming it in a call. - -``do`` with parentheses is an anonymous ``proc``; however a ``do`` without -parentheses is just a block of code. The ``do`` notation can be used to -pass multiple blocks to a macro: - -.. code-block:: nim - macro performWithUndo(task, undo: untyped) = ... - - performWithUndo do: - # multiple-line block of code - # to perform the task - do: - # code to undo it - - -Nonoverloadable builtins ------------------------- - -The following builtin procs cannot be overloaded for reasons of implementation -simplicity (they require specialized semantic checking):: - - declared, defined, definedInScope, compiles, sizeOf, - is, shallowCopy, getAst, astToStr, spawn, procCall - -Thus they act more like keywords than like ordinary identifiers; unlike a -keyword however, a redefinition may `shadow`:idx: the definition in -the ``system`` module. From this list the following should not be written in dot -notation ``x.f`` since ``x`` cannot be type checked before it gets passed -to ``f``:: - - declared, defined, definedInScope, compiles, getAst, astToStr - - -Var parameters --------------- -The type of a parameter may be prefixed with the ``var`` keyword: - -.. code-block:: nim - proc divmod(a, b: int; res, remainder: var int) = - res = a div b - remainder = a mod b - - var - x, y: int - - divmod(8, 5, x, y) # modifies x and y - assert x == 1 - assert y == 3 - -In the example, ``res`` and ``remainder`` are `var parameters`. -Var parameters can be modified by the procedure and the changes are -visible to the caller. The argument passed to a var parameter has to be -an l-value. Var parameters are implemented as hidden pointers. The -above example is equivalent to: - -.. code-block:: nim - proc divmod(a, b: int; res, remainder: ptr int) = - res[] = a div b - remainder[] = a mod b - - var - x, y: int - divmod(8, 5, addr(x), addr(y)) - assert x == 1 - assert y == 3 - -In the examples, var parameters or pointers are used to provide two -return values. This can be done in a cleaner way by returning a tuple: - -.. code-block:: nim - proc divmod(a, b: int): tuple[res, remainder: int] = - (a div b, a mod b) - - var t = divmod(8, 5) - - assert t.res == 1 - assert t.remainder == 3 - -One can use `tuple unpacking`:idx: to access the tuple's fields: - -.. code-block:: nim - var (x, y) = divmod(8, 5) # tuple unpacking - assert x == 1 - assert y == 3 - - -**Note**: ``var`` parameters are never necessary for efficient parameter -passing. Since non-var parameters cannot be modified the compiler is always -free to pass arguments by reference if it considers it can speed up execution. - - -Var return type ---------------- - -A proc, converter or iterator may return a ``var`` type which means that the -returned value is an l-value and can be modified by the caller: - -.. code-block:: nim - var g = 0 - - proc WriteAccessToG(): var int = - result = g - - WriteAccessToG() = 6 - assert g == 6 - -It is a compile time error if the implicitly introduced pointer could be -used to access a location beyond its lifetime: - -.. code-block:: nim - proc WriteAccessToG(): var int = - var g = 0 - result = g # Error! - -For iterators, a component of a tuple return type can have a ``var`` type too: - -.. code-block:: nim - iterator mpairs(a: var seq[string]): tuple[key: int, val: var string] = - for i in 0..a.high: - yield (i, a[i]) - -In the standard library every name of a routine that returns a ``var`` type -starts with the prefix ``m`` per convention. - - -Overloading of the subscript operator -------------------------------------- - -The ``[]`` subscript operator for arrays/openarrays/sequences can be overloaded. - - -Multi-methods -============= - -Procedures always use static dispatch. Multi-methods use dynamic -dispatch. For dynamic dispatch to work on an object it should be a reference -type as well. - -.. code-block:: nim - type - Expression = ref object of RootObj ## abstract base class for an expression - Literal = ref object of Expression - x: int - PlusExpr = ref object of Expression - a, b: Expression - - method eval(e: Expression): int {.base.} = - # override this base method - quit "to override!" - - method eval(e: Literal): int = return e.x - - method eval(e: PlusExpr): int = - # watch out: relies on dynamic binding - result = eval(e.a) + eval(e.b) - - proc newLit(x: int): Literal = - new(result) - result.x = x - - proc newPlus(a, b: Expression): PlusExpr = - new(result) - result.a = a - result.b = b - - echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4))) - -In the example the constructors ``newLit`` and ``newPlus`` are procs -because they should use static binding, but ``eval`` is a method because it -requires dynamic binding. - -As can be seen in the example, base methods have to be annotated with -the `base`:idx: pragma. The ``base`` pragma also acts as a reminder for the -programmer that a base method ``m`` is used as the foundation to determine all -the effects that a call to ``m`` might cause. - -In a multi-method all parameters that have an object type are used for the -dispatching: - -.. code-block:: nim - type - Thing = ref object of RootObj - Unit = ref object of Thing - x: int - - method collide(a, b: Thing) {.base, inline.} = - quit "to override!" - - method collide(a: Thing, b: Unit) {.inline.} = - echo "1" - - method collide(a: Unit, b: Thing) {.inline.} = - echo "2" - - var a, b: Unit - new a - new b - collide(a, b) # output: 2 - - -Invocation of a multi-method cannot be ambiguous: collide 2 is preferred over -collide 1 because the resolution works from left to right. -In the example ``Unit, Thing`` is preferred over ``Thing, Unit``. - -**Performance note**: Nim does not produce a virtual method table, but -generates dispatch trees. This avoids the expensive indirect branch for method -calls and enables inlining. However, other optimizations like compile time -evaluation or dead code elimination do not work with methods. - - -Iterators and the for statement -=============================== - -The `for`:idx: statement is an abstract mechanism to iterate over the elements -of a container. It relies on an `iterator`:idx: to do so. Like ``while`` -statements, ``for`` statements open an `implicit block`:idx:, so that they -can be left with a ``break`` statement. - -The ``for`` loop declares iteration variables - their scope reaches until the -end of the loop body. The iteration variables' types are inferred by the -return type of the iterator. - -An iterator is similar to a procedure, except that it can be called in the -context of a ``for`` loop. Iterators provide a way to specify the iteration over -an abstract type. A key role in the execution of a ``for`` loop plays the -``yield`` statement in the called iterator. Whenever a ``yield`` statement is -reached the data is bound to the ``for`` loop variables and control continues -in the body of the ``for`` loop. The iterator's local variables and execution -state are automatically saved between calls. Example: - -.. code-block:: nim - # this definition exists in the system module - iterator items*(a: string): char {.inline.} = - var i = 0 - while i < len(a): - yield a[i] - inc(i) - - for ch in items("hello world"): # `ch` is an iteration variable - echo ch - -The compiler generates code as if the programmer would have written this: - -.. code-block:: nim - var i = 0 - while i < len(a): - var ch = a[i] - echo ch - inc(i) - -If the iterator yields a tuple, there can be as many iteration variables -as there are components in the tuple. The i'th iteration variable's type is -the type of the i'th component. In other words, implicit tuple unpacking in a -for loop context is supported. - -Implict items/pairs invocations -------------------------------- - -If the for loop expression ``e`` does not denote an iterator and the for loop -has exactly 1 variable, the for loop expression is rewritten to ``items(e)``; -ie. an ``items`` iterator is implicitly invoked: - -.. code-block:: nim - for x in [1,2,3]: echo x - -If the for loop has exactly 2 variables, a ``pairs`` iterator is implicitly -invoked. - -Symbol lookup of the identifiers ``items``/``pairs`` is performed after -the rewriting step, so that all overloads of ``items``/``pairs`` are taken -into account. - - -First class iterators ---------------------- - -There are 2 kinds of iterators in Nim: *inline* and *closure* iterators. -An `inline iterator`:idx: is an iterator that's always inlined by the compiler -leading to zero overhead for the abstraction, but may result in a heavy -increase in code size. Inline iterators are second class citizens; -They can be passed as parameters only to other inlining code facilities like -templates, macros and other inline iterators. - -In contrast to that, a `closure iterator`:idx: can be passed around more freely: - -.. code-block:: nim - iterator count0(): int {.closure.} = - yield 0 - - iterator count2(): int {.closure.} = - var x = 1 - yield x - inc x - yield x - - proc invoke(iter: iterator(): int {.closure.}) = - for x in iter(): echo x - - invoke(count0) - invoke(count2) - -Closure iterators have other restrictions than inline iterators: - -1. ``yield`` in a closure iterator can not occur in a ``try`` statement. -2. For now, a closure iterator cannot be evaluated at compile time. -3. ``return`` is allowed in a closure iterator (but rarely useful) and ends - iteration. -4. Neither inline nor closure iterators can be recursive. -5. Closure iterators are not supported by the js backend. - -Iterators that are neither marked ``{.closure.}`` nor ``{.inline.}`` explicitly -default to being inline, but this may change in future versions of the -implementation. - -The ``iterator`` type is always of the calling convention ``closure`` -implicitly; the following example shows how to use iterators to implement -a `collaborative tasking`:idx: system: - -.. code-block:: nim - # simple tasking: - type - Task = iterator (ticker: int) - - iterator a1(ticker: int) {.closure.} = - echo "a1: A" - yield - echo "a1: B" - yield - echo "a1: C" - yield - echo "a1: D" - - iterator a2(ticker: int) {.closure.} = - echo "a2: A" - yield - echo "a2: B" - yield - echo "a2: C" - - proc runTasks(t: varargs[Task]) = - var ticker = 0 - while true: - let x = t[ticker mod t.len] - if finished(x): break - x(ticker) - inc ticker - - runTasks(a1, a2) - -The builtin ``system.finished`` can be used to determine if an iterator has -finished its operation; no exception is raised on an attempt to invoke an -iterator that has already finished its work. - -Note that ``system.finished`` is error prone to use because it only returns -``true`` one iteration after the iterator has finished: - -.. code-block:: nim - iterator mycount(a, b: int): int {.closure.} = - var x = a - while x <= b: - yield x - inc x - - var c = mycount # instantiate the iterator - while not finished(c): - echo c(1, 3) - - # Produces - 1 - 2 - 3 - 0 - -Instead this code has to be used: - -.. code-block:: nim - var c = mycount # instantiate the iterator - while true: - let value = c(1, 3) - if finished(c): break # and discard 'value'! - echo value - -It helps to think that the iterator actually returns a -pair ``(value, done)`` and ``finished`` is used to access the hidden ``done`` -field. - - -Closure iterators are *resumable functions* and so one has to provide the -arguments to every call. To get around this limitation one can capture -parameters of an outer factory proc: - -.. code-block:: nim - proc mycount(a, b: int): iterator (): int = - result = iterator (): int = - var x = a - while x <= b: - yield x - inc x - - let foo = mycount(1, 4) - - for f in foo(): - echo f - -.. - Implicit return type - -------------------- - - Since inline iterators must always produce values that will be consumed in - a for loop, the compiler will implicitly use the ``auto`` return type if no - type is given by the user. In contrast, since closure iterators can be used - as a collaborative tasking system, ``void`` is a valid return type for them. - - -Converters -========== - -A converter is like an ordinary proc except that it enhances -the "implicitly convertible" type relation (see `Convertible relation`_): - -.. code-block:: nim - # bad style ahead: Nim is not C. - converter toBool(x: int): bool = x != 0 - - if 4: - echo "compiles" - - -A converter can also be explicitly invoked for improved readability. Note that -implicit converter chaining is not supported: If there is a converter from -type A to type B and from type B to type C the implicit conversion from A to C -is not provided. diff --git a/doc/manual/special_ops.txt b/doc/manual/special_ops.txt deleted file mode 100644 index 93977f81b..000000000 --- a/doc/manual/special_ops.txt +++ /dev/null @@ -1,57 +0,0 @@ -Special Operators -================= - -dot operators -------------- - -**Note**: Dot operators are still experimental and so need to be enabled -via ``{.experimental.}``. - -Nim offers a special family of dot operators that can be used to -intercept and rewrite proc call and field access attempts, referring -to previously undeclared symbol names. They can be used to provide a -fluent interface to objects lying outside the static confines of the -type system such as values from dynamic scripting languages -or dynamic file formats such as JSON or XML. - -When Nim encounters an expression that cannot be resolved by the -standard overload resolution rules, the current scope will be searched -for a dot operator that can be matched against a re-written form of -the expression, where the unknown field or proc name is passed to -an ``untyped`` parameter: - -.. code-block:: nim - a.b # becomes `.`(a, "b") - a.b(c, d) # becomes `.`(a, "b", c, d) - -The matched dot operators can be symbols of any callable kind (procs, -templates and macros), depending on the desired effect: - -.. code-block:: nim - template `.` (js: PJsonNode, field: untyped): JSON = js[astToStr(field)] - - var js = parseJson("{ x: 1, y: 2}") - echo js.x # outputs 1 - echo js.y # outputs 2 - -The following dot operators are available: - -operator `.` ------------- -This operator will be matched against both field accesses and method calls. - -operator `.()` ---------------- -This operator will be matched exclusively against method calls. It has higher -precedence than the `.` operator and this allows one to handle expressions like -`x.y` and `x.y()` differently if one is interfacing with a scripting language -for example. - -operator `.=` -------------- -This operator will be matched against assignments to missing fields. - -.. code-block:: nim - a.b = c # becomes `.=`(a, "b", c) - - diff --git a/doc/manual/stmts.txt b/doc/manual/stmts.txt deleted file mode 100644 index 721b5cff8..000000000 --- a/doc/manual/stmts.txt +++ /dev/null @@ -1,693 +0,0 @@ -Statements and expressions -========================== - -Nim uses the common statement/expression paradigm: Statements do not -produce a value in contrast to expressions. However, some expressions are -statements. - -Statements are separated into `simple statements`:idx: and -`complex statements`:idx:. -Simple statements are statements that cannot contain other statements like -assignments, calls or the ``return`` statement; complex statements can -contain other statements. To avoid the `dangling else problem`:idx:, complex -statements always have to be indented. The details can be found in the grammar. - - -Statement list expression -------------------------- - -Statements can also occur in an expression context that looks -like ``(stmt1; stmt2; ...; ex)``. This is called -an statement list expression or ``(;)``. The type -of ``(stmt1; stmt2; ...; ex)`` is the type of ``ex``. All the other statements -must be of type ``void``. (One can use ``discard`` to produce a ``void`` type.) -``(;)`` does not introduce a new scope. - - -Discard statement ------------------ - -Example: - -.. code-block:: nim - proc p(x, y: int): int = - result = x + y - - discard p(3, 4) # discard the return value of `p` - -The ``discard`` statement evaluates its expression for side-effects and -throws the expression's resulting value away. - -Ignoring the return value of a procedure without using a discard statement is -a static error. - -The return value can be ignored implicitly if the called proc/iterator has -been declared with the `discardable`:idx: pragma: - -.. code-block:: nim - proc p(x, y: int): int {.discardable.} = - result = x + y - - p(3, 4) # now valid - -An empty ``discard`` statement is often used as a null statement: - -.. code-block:: nim - proc classify(s: string) = - case s[0] - of SymChars, '_': echo "an identifier" - of '0'..'9': echo "a number" - else: discard - - -Void context ------------- - -In a list of statements every expression except the last one needs to have the -type ``void``. In addition to this rule an assignment to the builtin ``result`` -symbol also triggers a mandatory ``void`` context for the subsequent expressions: - -.. code-block:: nim - proc invalid*(): string = - result = "foo" - "invalid" # Error: value of type 'string' has to be discarded - -.. code-block:: nim - proc valid*(): string = - let x = 317 - "valid" - - -Var statement -------------- - -Var statements declare new local and global variables and -initialize them. A comma separated list of variables can be used to specify -variables of the same type: - -.. code-block:: nim - - var - a: int = 0 - x, y, z: int - -If an initializer is given the type can be omitted: the variable is then of the -same type as the initializing expression. Variables are always initialized -with a default value if there is no initializing expression. The default -value depends on the type and is always a zero in binary. - -============================ ============================================== -Type default value -============================ ============================================== -any integer type 0 -any float 0.0 -char '\\0' -bool false -ref or pointer type nil -procedural type nil -sequence nil (*not* ``@[]``) -string nil (*not* "") -tuple[x: A, y: B, ...] (default(A), default(B), ...) - (analogous for objects) -array[0..., T] [default(T), ...] -range[T] default(T); this may be out of the valid range -T = enum cast[T](0); this may be an invalid value -============================ ============================================== - - -The implicit initialization can be avoided for optimization reasons with the -`noinit`:idx: pragma: - -.. code-block:: nim - var - a {.noInit.}: array [0..1023, char] - -If a proc is annotated with the ``noinit`` pragma this refers to its implicit -``result`` variable: - -.. code-block:: nim - proc returnUndefinedValue: int {.noinit.} = discard - - -The implicit initialization can be also prevented by the `requiresInit`:idx: -type pragma. The compiler requires an explicit initialization for the object -and all of its fields. However it does a `control flow analysis`:idx: to prove -the variable has been initialized and does not rely on syntactic properties: - -.. code-block:: nim - type - MyObject = object {.requiresInit.} - - proc p() = - # the following is valid: - var x: MyObject - if someCondition(): - x = a() - else: - x = a() - use x - - -let statement -------------- - -A ``let`` statement declares new local and global `single assignment`:idx: -variables and binds a value to them. The syntax is the same as that of the ``var`` -statement, except that the keyword ``var`` is replaced by the keyword ``let``. -Let variables are not l-values and can thus not be passed to ``var`` parameters -nor can their address be taken. They cannot be assigned new values. - -For let variables the same pragmas are available as for ordinary variables. - - -Tuple unpacking ---------------- - -In a ``var`` or ``let`` statement tuple unpacking can be performed. The special -identifier ``_`` can be used to ignore some parts of the tuple: - -.. code-block:: nim - proc returnsTuple(): (int, int, int) = (4, 2, 3) - - let (x, _, z) = returnsTuple() - - - -Const section -------------- - -`Constants`:idx: are symbols which are bound to a value. The constant's value -cannot change. The compiler must be able to evaluate the expression in a -constant declaration at compile time. - -Nim contains a sophisticated compile-time evaluator, so procedures which -have no side-effect can be used in constant expressions too: - -.. code-block:: nim - import strutils - const - constEval = contains("abc", 'b') # computed at compile time! - - -The rules for compile-time computability are: - -1. Literals are compile-time computable. -2. Type conversions are compile-time computable. -3. Procedure calls of the form ``p(X)`` are compile-time computable if - ``p`` is a proc without side-effects (see the `noSideEffect pragma - <#pragmas-nosideeffect-pragma>`_ for details) and if ``X`` is a - (possibly empty) list of compile-time computable arguments. - - -Constants cannot be of type ``ptr``, ``ref`` or ``var``, nor can -they contain such a type. - - -Static statement/expression ---------------------------- - -A static statement/expression can be used to enforce compile -time evaluation explicitly. Enforced compile time evaluation can even evaluate -code that has side effects: - -.. code-block:: - - static: - echo "echo at compile time" - -It's a static error if the compiler cannot perform the evaluation at compile -time. - -The current implementation poses some restrictions for compile time -evaluation: Code which contains ``cast`` or makes use of the foreign function -interface cannot be evaluated at compile time. Later versions of Nim will -support the FFI at compile time. - - -If statement ------------- - -Example: - -.. code-block:: nim - - var name = readLine(stdin) - - if name == "Andreas": - echo "What a nice name!" - elif name == "": - echo "Don't you have a name?" - else: - echo "Boring name..." - -The ``if`` statement is a simple way to make a branch in the control flow: -The expression after the keyword ``if`` is evaluated, if it is true -the corresponding statements after the ``:`` are executed. Otherwise -the expression after the ``elif`` is evaluated (if there is an -``elif`` branch), if it is true the corresponding statements after -the ``:`` are executed. This goes on until the last ``elif``. If all -conditions fail, the ``else`` part is executed. If there is no ``else`` -part, execution continues with the next statement. - -In ``if`` statements new scopes begin immediately after the ``if``/``elif``/``else`` keywords and ends after the corresponding *then* block. -For visualization purposes the scopes have been enclosed in ``{| |}`` in the following example: - -.. code-block:: nim - if {| (let m = input =~ re"(\w+)=\w+"; m.isMatch): - echo "key ", m[0], " value ", m[1] |} - elif {| (let m = input =~ re""; m.isMatch): - echo "new m in this scope" |} - else: {| - echo "m not declared here" |} - -Case statement --------------- - -Example: - -.. code-block:: nim - - case readline(stdin) - of "delete-everything", "restart-computer": - echo "permission denied" - of "go-for-a-walk": echo "please yourself" - else: echo "unknown command" - - # indentation of the branches is also allowed; and so is an optional colon - # after the selecting expression: - case readline(stdin): - of "delete-everything", "restart-computer": - echo "permission denied" - of "go-for-a-walk": echo "please yourself" - else: echo "unknown command" - - -The ``case`` statement is similar to the if statement, but it represents -a multi-branch selection. The expression after the keyword ``case`` is -evaluated and if its value is in a *slicelist* the corresponding statements -(after the ``of`` keyword) are executed. If the value is not in any -given *slicelist* the ``else`` part is executed. If there is no ``else`` -part and not all possible values that ``expr`` can hold occur in a -``slicelist``, a static error occurs. This holds only for expressions of -ordinal types. "All possible values" of ``expr`` are determined by ``expr``'s -type. To suppress the static error an ``else`` part with an -empty ``discard`` statement should be used. - -For non ordinal types it is not possible to list every possible value and so -these always require an ``else`` part. - -As case statements perform compile-time exhaustiveness checks, the value in -every ``of`` branch must be known at compile time. This fact is also exploited -to generate more performant code. - -As a special semantic extension, an expression in an ``of`` branch of a case -statement may evaluate to a set or array constructor; the set or array is then -expanded into a list of its elements: - -.. code-block:: nim - const - SymChars: set[char] = {'a'..'z', 'A'..'Z', '\x80'..'\xFF'} - - proc classify(s: string) = - case s[0] - of SymChars, '_': echo "an identifier" - of '0'..'9': echo "a number" - else: echo "other" - - # is equivalent to: - proc classify(s: string) = - case s[0] - of 'a'..'z', 'A'..'Z', '\x80'..'\xFF', '_': echo "an identifier" - of '0'..'9': echo "a number" - else: echo "other" - - -When statement --------------- - -Example: - -.. code-block:: nim - - when sizeof(int) == 2: - echo "running on a 16 bit system!" - elif sizeof(int) == 4: - echo "running on a 32 bit system!" - elif sizeof(int) == 8: - echo "running on a 64 bit system!" - else: - echo "cannot happen!" - -The ``when`` statement is almost identical to the ``if`` statement with some -exceptions: - -* Each condition (``expr``) has to be a constant expression (of type ``bool``). -* The statements do not open a new scope. -* The statements that belong to the expression that evaluated to true are - translated by the compiler, the other statements are not checked for - semantics! However, each condition is checked for semantics. - -The ``when`` statement enables conditional compilation techniques. As -a special syntactic extension, the ``when`` construct is also available -within ``object`` definitions. - - -When nimvm statement --------------------- - -``nimvm`` is a special symbol, that may be used as expression of ``when nimvm`` -statement to differentiate execution path between runtime and compile time. - -Example: - -.. code-block:: nim - proc someProcThatMayRunInCompileTime(): bool = - when nimvm: - # This code runs in compile time - result = true - else: - # This code runs in runtime - result = false - const ctValue = someProcThatMayRunInCompileTime() - let rtValue = someProcThatMayRunInCompileTime() - assert(ctValue == true) - assert(rtValue == false) - -``when nimvm`` statement must meet the following requirements: - -* Its expression must always be ``nimvm``. More complex expressions are not - allowed. -* It must not contain ``elif`` branches. -* It must contain ``else`` branch. -* Code in branches must not affect semantics of the code that follows the - ``when nimvm`` statement. E.g. it must not define symbols that are used in - the following code. - -Return statement ----------------- - -Example: - -.. code-block:: nim - return 40+2 - -The ``return`` statement ends the execution of the current procedure. -It is only allowed in procedures. If there is an ``expr``, this is syntactic -sugar for: - -.. code-block:: nim - result = expr - return result - - -``return`` without an expression is a short notation for ``return result`` if -the proc has a return type. The `result`:idx: variable is always the return -value of the procedure. It is automatically declared by the compiler. As all -variables, ``result`` is initialized to (binary) zero: - -.. code-block:: nim - proc returnZero(): int = - # implicitly returns 0 - - -Yield statement ---------------- - -Example: - -.. code-block:: nim - yield (1, 2, 3) - -The ``yield`` statement is used instead of the ``return`` statement in -iterators. It is only valid in iterators. Execution is returned to the body -of the for loop that called the iterator. Yield does not end the iteration -process, but execution is passed back to the iterator if the next iteration -starts. See the section about iterators (`Iterators and the for statement`_) -for further information. - - -Block statement ---------------- - -Example: - -.. code-block:: nim - var found = false - block myblock: - for i in 0..3: - for j in 0..3: - if a[j][i] == 7: - found = true - break myblock # leave the block, in this case both for-loops - echo found - -The block statement is a means to group statements to a (named) ``block``. -Inside the block, the ``break`` statement is allowed to leave the block -immediately. A ``break`` statement can contain a name of a surrounding -block to specify which block is to leave. - - -Break statement ---------------- - -Example: - -.. code-block:: nim - break - -The ``break`` statement is used to leave a block immediately. If ``symbol`` -is given, it is the name of the enclosing block that is to leave. If it is -absent, the innermost block is left. - - -While statement ---------------- - -Example: - -.. code-block:: nim - echo "Please tell me your password:" - var pw = readLine(stdin) - while pw != "12345": - echo "Wrong password! Next try:" - pw = readLine(stdin) - - -The ``while`` statement is executed until the ``expr`` evaluates to false. -Endless loops are no error. ``while`` statements open an `implicit block`, -so that they can be left with a ``break`` statement. - - -Continue statement ------------------- - -A ``continue`` statement leads to the immediate next iteration of the -surrounding loop construct. It is only allowed within a loop. A continue -statement is syntactic sugar for a nested block: - -.. code-block:: nim - while expr1: - stmt1 - continue - stmt2 - -Is equivalent to: - -.. code-block:: nim - while expr1: - block myBlockName: - stmt1 - break myBlockName - stmt2 - - -Assembler statement -------------------- - -The direct embedding of assembler code into Nim code is supported -by the unsafe ``asm`` statement. Identifiers in the assembler code that refer to -Nim identifiers shall be enclosed in a special character which can be -specified in the statement's pragmas. The default special character is ``'`'``: - -.. code-block:: nim - {.push stackTrace:off.} - proc addInt(a, b: int): int = - # a in eax, and b in edx - asm """ - mov eax, `a` - add eax, `b` - jno theEnd - call `raiseOverflow` - theEnd: - """ - {.pop.} - -If the GNU assembler is used, quotes and newlines are inserted automatically: - -.. code-block:: nim - proc addInt(a, b: int): int = - asm """ - addl %%ecx, %%eax - jno 1 - call `raiseOverflow` - 1: - :"=a"(`result`) - :"a"(`a`), "c"(`b`) - """ - -Instead of: - -.. code-block:: nim - proc addInt(a, b: int): int = - asm """ - "addl %%ecx, %%eax\n" - "jno 1\n" - "call `raiseOverflow`\n" - "1: \n" - :"=a"(`result`) - :"a"(`a`), "c"(`b`) - """ - -Using statement ---------------- - -The using statement provides syntactic convenience in modules where -the same parameter names and types are used over and over. Instead of: - -.. code-block:: nim - proc foo(c: Context; n: Node) = ... - proc bar(c: Context; n: Node, counter: int) = ... - proc baz(c: Context; n: Node) = ... - -One can tell the compiler about the convention that a parameter of -name ``c`` should default to type ``Context``, ``n`` should default to -``Node`` etc.: - -.. code-block:: nim - using - c: Context - n: Node - counter: int - - proc foo(c, n) = ... - proc bar(c, n, counter) = ... - proc baz(c, n) = ... - - -The ``using`` section uses the same indentation based grouping syntax as -a ``var`` or ``let`` section. - -Note that ``using`` is not applied for ``template`` since untyped template -parameters default to the type ``system.untyped``. - - -If expression -------------- - -An `if expression` is almost like an if statement, but it is an expression. -Example: - -.. code-block:: nim - var y = if x > 8: 9 else: 10 - -An if expression always results in a value, so the ``else`` part is -required. ``Elif`` parts are also allowed. - -When expression ---------------- - -Just like an `if expression`, but corresponding to the when statement. - -Case expression ---------------- - -The `case expression` is again very similar to the case statement: - -.. code-block:: nim - var favoriteFood = case animal - of "dog": "bones" - of "cat": "mice" - elif animal.endsWith"whale": "plankton" - else: - echo "I'm not sure what to serve, but everybody loves ice cream" - "ice cream" - -As seen in the above example, the case expression can also introduce side -effects. When multiple statements are given for a branch, Nim will use -the last expression as the result value, much like in an `expr` template. - -Table constructor ------------------ - -A table constructor is syntactic sugar for an array constructor: - -.. code-block:: nim - {"key1": "value1", "key2", "key3": "value2"} - - # is the same as: - [("key1", "value1"), ("key2", "value2"), ("key3", "value2")] - - -The empty table can be written ``{:}`` (in contrast to the empty set -which is ``{}``) which is thus another way to write as the empty array -constructor ``[]``. This slightly unusual way of supporting tables -has lots of advantages: - -* The order of the (key,value)-pairs is preserved, thus it is easy to - support ordered dicts with for example ``{key: val}.newOrderedTable``. -* A table literal can be put into a ``const`` section and the compiler - can easily put it into the executable's data section just like it can - for arrays and the generated data section requires a minimal amount - of memory. -* Every table implementation is treated equal syntactically. -* Apart from the minimal syntactic sugar the language core does not need to - know about tables. - - -Type conversions ----------------- -Syntactically a `type conversion` is like a procedure call, but a -type name replaces the procedure name. A type conversion is always -safe in the sense that a failure to convert a type to another -results in an exception (if it cannot be determined statically). - -Ordinary procs are often preferred over type conversions in Nim: For instance, -``$`` is the ``toString`` operator by convention and ``toFloat`` and ``toInt`` -can be used to convert from floating point to integer or vice versa. - - -Type casts ----------- -Example: - -.. code-block:: nim - cast[int](x) - -Type casts are a crude mechanism to interpret the bit pattern of -an expression as if it would be of another type. Type casts are -only needed for low-level programming and are inherently unsafe. - - -The addr operator ------------------ -The ``addr`` operator returns the address of an l-value. If the type of the -location is ``T``, the `addr` operator result is of the type ``ptr T``. An -address is always an untraced reference. Taking the address of an object that -resides on the stack is **unsafe**, as the pointer may live longer than the -object on the stack and can thus reference a non-existing object. One can get -the address of variables, but one can't use it on variables declared through -``let`` statements: - -.. code-block:: nim - - let t1 = "Hello" - var - t2 = t1 - t3 : pointer = addr(t2) - echo repr(addr(t2)) - # --> ref 0x7fff6b71b670 --> 0x10bb81050"Hello" - echo cast[ptr string](t3)[] - # --> Hello - # The following line doesn't compile: - echo repr(addr(t1)) - # Error: expression has no address diff --git a/doc/manual/syntax.txt b/doc/manual/syntax.txt deleted file mode 100644 index f3ace9f56..000000000 --- a/doc/manual/syntax.txt +++ /dev/null @@ -1,81 +0,0 @@ -Syntax -====== - -This section lists Nim's standard syntax. How the parser handles -the indentation is already described in the `Lexical Analysis`_ section. - -Nim allows user-definable operators. -Binary operators have 11 different levels of precedence. - - - -Associativity -------------- - -Binary operators whose first character is ``^`` are right-associative, all -other binary operators are left-associative. - -.. code-block:: nim - proc `^/`(x, y: float): float = - # a right-associative division operator - result = x / y - echo 12 ^/ 4 ^/ 8 # 24.0 (4 / 8 = 0.5, then 12 / 0.5 = 24.0) - echo 12 / 4 / 8 # 0.375 (12 / 4 = 3.0, then 3 / 8 = 0.375) - -Precedence ----------- - -Unary operators always bind stronger than any binary -operator: ``$a + b`` is ``($a) + b`` and not ``$(a + b)``. - -If an unary operator's first character is ``@`` it is a `sigil-like`:idx: -operator which binds stronger than a ``primarySuffix``: ``@x.abc`` is parsed -as ``(@x).abc`` whereas ``$x.abc`` is parsed as ``$(x.abc)``. - - -For binary operators that are not keywords the precedence is determined by the -following rules: - -Operators ending in either ``->``, ``~>`` or ``=>`` are called -`arrow like`:idx:, and have the lowest precedence of all operators. - -If the operator ends with ``=`` and its first character is none of -``<``, ``>``, ``!``, ``=``, ``~``, ``?``, it is an *assignment operator* which -has the second lowest precedence. - -Otherwise precedence is determined by the first character. - -================ =============================================== ================== =============== -Precedence level Operators First character Terminal symbol -================ =============================================== ================== =============== - 10 (highest) ``$ ^`` OP10 - 9 ``* / div mod shl shr %`` ``* % \ /`` OP9 - 8 ``+ -`` ``+ - ~ |`` OP8 - 7 ``&`` ``&`` OP7 - 6 ``..`` ``.`` OP6 - 5 ``== <= < >= > != in notin is isnot not of`` ``= < > !`` OP5 - 4 ``and`` OP4 - 3 ``or xor`` OP3 - 2 ``@ : ?`` OP2 - 1 *assignment operator* (like ``+=``, ``*=``) OP1 - 0 (lowest) *arrow like operator* (like ``->``, ``=>``) OP0 -================ =============================================== ================== =============== - - -Whether an operator is used a prefix operator is also affected by preceding -whitespace (this parsing change was introduced with version 0.13.0): - -.. code-block:: nim - echo $foo - # is parsed as - echo($foo) - - -Grammar -------- - -The grammar's start symbol is ``module``. - -.. include:: ../grammar.txt - :literal: - diff --git a/doc/manual/taint.txt b/doc/manual/taint.txt deleted file mode 100644 index 492686f31..000000000 --- a/doc/manual/taint.txt +++ /dev/null @@ -1,20 +0,0 @@ -Taint mode -========== - -The Nim compiler and most parts of the standard library support -a taint mode. Input strings are declared with the `TaintedString`:idx: -string type declared in the ``system`` module. - -If the taint mode is turned on (via the ``--taintMode:on`` command line -option) it is a distinct string type which helps to detect input -validation errors: - -.. code-block:: nim - echo "your name: " - var name: TaintedString = stdin.readline - # it is safe here to output the name without any input validation, so - # we simply convert `name` to string to make the compiler happy: - echo "hi, ", name.string - -If the taint mode is turned off, ``TaintedString`` is simply an alias for -``string``. diff --git a/doc/manual/templates.txt b/doc/manual/templates.txt deleted file mode 100644 index af184589c..000000000 --- a/doc/manual/templates.txt +++ /dev/null @@ -1,498 +0,0 @@ -Templates -========= - -A template is a simple form of a macro: It is a simple substitution -mechanism that operates on Nim's abstract syntax trees. It is processed in -the semantic pass of the compiler. - -The syntax to *invoke* a template is the same as calling a procedure. - -Example: - -.. code-block:: nim - template `!=` (a, b: untyped): untyped = - # this definition exists in the System module - not (a == b) - - assert(5 != 6) # the compiler rewrites that to: assert(not (5 == 6)) - -The ``!=``, ``>``, ``>=``, ``in``, ``notin``, ``isnot`` operators are in fact -templates: - -| ``a > b`` is transformed into ``b < a``. -| ``a in b`` is transformed into ``contains(b, a)``. -| ``notin`` and ``isnot`` have the obvious meanings. - -The "types" of templates can be the symbols ``untyped``, -``typed`` or ``typedesc`` (stands for *type -description*). These are "meta types", they can only be used in certain -contexts. Real types can be used too; this implies that ``typed`` expressions -are expected. - - -Typed vs untyped parameters ---------------------------- - -An ``untyped`` parameter means that symbol lookups and type resolution is not -performed before the expression is passed to the template. This means that for -example *undeclared* identifiers can be passed to the template: - -.. code-block:: nim - - template declareInt(x: untyped) = - var x: int - - declareInt(x) # valid - x = 3 - - -.. code-block:: nim - - template declareInt(x: typed) = - var x: int - - declareInt(x) # invalid, because x has not been declared and so has no type - -A template where every parameter is ``untyped`` is called an `immediate`:idx: -template. For historical reasons templates can be explicitly annotated with -an ``immediate`` pragma and then these templates do not take part in -overloading resolution and the parameters' types are *ignored* by the -compiler. Explicit immediate templates are now deprecated. - -**Note**: For historical reasons ``stmt`` is an alias for ``typed`` and -``expr`` an alias for ``untyped``, but new code should use the newer, -clearer names. - - -Passing a code block to a template ----------------------------------- - -You can pass a block of statements as a last parameter to a template via a -special ``:`` syntax: - -.. code-block:: nim - template withFile(f, fn, mode, actions: untyped): untyped = - var f: File - if open(f, fn, mode): - try: - actions - finally: - close(f) - else: - quit("cannot open: " & fn) - - withFile(txt, "ttempl3.txt", fmWrite): - txt.writeLine("line 1") - txt.writeLine("line 2") - -In the example the two ``writeLine`` statements are bound to the ``actions`` -parameter. - - -Usually to pass a block of code to a template the parameter that accepts -the block needs to be of type ``untyped``. Because symbol lookups are then -delayed until template instantiation time: - -.. code-block:: nim - template t(body: typed) = - block: - body - - t: - var i = 1 - echo i - - t: - var i = 2 # fails with 'attempt to redeclare i' - echo i - -The above code fails with the mysterious error message that ``i`` has already -been declared. The reason for this is that the ``var i = ...`` bodies need to -be type-checked before they are passed to the ``body`` parameter and type -checking in Nim implies symbol lookups. For the symbol lookups to succeed -``i`` needs to be added to the current (i.e. outer) scope. After type checking -these additions to the symbol table are not rolled back (for better or worse). -The same code works with ``untyped`` as the passed body is not required to be -type-checked: - -.. code-block:: nim - template t(body: untyped) = - block: - body - - t: - var i = 1 - echo i - - t: - var i = 2 # compiles - echo i - - -Varargs of untyped ------------------- - -In addition to the ``untyped`` meta-type that prevents type checking there is -also ``varargs[untyped]`` so that not even the number of parameters is fixed: - -.. code-block:: nim - template hideIdentifiers(x: varargs[untyped]) = discard - - hideIdentifiers(undeclared1, undeclared2) - -However, since a template cannot iterate over varargs, this feature is -generally much more useful for macros. - -**Note**: For historical reasons ``varargs[expr]`` is not equivalent -to ``varargs[untyped]``. - - -Symbol binding in templates ---------------------------- - -A template is a `hygienic`:idx: macro and so opens a new scope. Most symbols are -bound from the definition scope of the template: - -.. code-block:: nim - # Module A - var - lastId = 0 - - template genId*: untyped = - inc(lastId) - lastId - -.. code-block:: nim - # Module B - import A - - echo genId() # Works as 'lastId' has been bound in 'genId's defining scope - -As in generics symbol binding can be influenced via ``mixin`` or ``bind`` -statements. - - - -Identifier construction ------------------------ - -In templates identifiers can be constructed with the backticks notation: - -.. code-block:: nim - - template typedef(name: untyped, typ: typedesc) = - type - `T name`* {.inject.} = typ - `P name`* {.inject.} = ref `T name` - - typedef(myint, int) - var x: PMyInt - -In the example ``name`` is instantiated with ``myint``, so \`T name\` becomes -``Tmyint``. - - -Lookup rules for template parameters ------------------------------------- - -A parameter ``p`` in a template is even substituted in the expression ``x.p``. -Thus template arguments can be used as field names and a global symbol can be -shadowed by the same argument name even when fully qualified: - -.. code-block:: nim - # module 'm' - - type - Lev = enum - levA, levB - - var abclev = levB - - template tstLev(abclev: Lev) = - echo abclev, " ", m.abclev - - tstLev(levA) - # produces: 'levA levA' - -But the global symbol can properly be captured by a ``bind`` statement: - -.. code-block:: nim - # module 'm' - - type - Lev = enum - levA, levB - - var abclev = levB - - template tstLev(abclev: Lev) = - bind m.abclev - echo abclev, " ", m.abclev - - tstLev(levA) - # produces: 'levA levB' - - -Hygiene in templates --------------------- - -Per default templates are `hygienic`:idx:\: Local identifiers declared in a -template cannot be accessed in the instantiation context: - -.. code-block:: nim - - template newException*(exceptn: typedesc, message: string): untyped = - var - e: ref exceptn # e is implicitly gensym'ed here - new(e) - e.msg = message - e - - # so this works: - let e = "message" - raise newException(EIO, e) - - -Whether a symbol that is declared in a template is exposed to the instantiation -scope is controlled by the `inject`:idx: and `gensym`:idx: pragmas: gensym'ed -symbols are not exposed but inject'ed are. - -The default for symbols of entity ``type``, ``var``, ``let`` and ``const`` -is ``gensym`` and for ``proc``, ``iterator``, ``converter``, ``template``, -``macro`` is ``inject``. However, if the name of the entity is passed as a -template parameter, it is an inject'ed symbol: - -.. code-block:: nim - template withFile(f, fn, mode: untyped, actions: untyped): untyped = - block: - var f: File # since 'f' is a template param, it's injected implicitly - ... - - withFile(txt, "ttempl3.txt", fmWrite): - txt.writeLine("line 1") - txt.writeLine("line 2") - - -The ``inject`` and ``gensym`` pragmas are second class annotations; they have -no semantics outside of a template definition and cannot be abstracted over: - -.. code-block:: nim - {.pragma myInject: inject.} - - template t() = - var x {.myInject.}: int # does NOT work - - -To get rid of hygiene in templates, one can use the `dirty`:idx: pragma for -a template. ``inject`` and ``gensym`` have no effect in ``dirty`` templates. - - - -Limitations of the method call syntax -------------------------------------- - -The expression ``x`` in ``x.f`` needs to be semantically checked (that means -symbol lookup and type checking) before it can be decided that it needs to be -rewritten to ``f(x)``. Therefore the dot syntax has some limitations when it -is used to invoke templates/macros: - -.. code-block:: nim - template declareVar(name: untyped) = - const name {.inject.} = 45 - - # Doesn't compile: - unknownIdentifier.declareVar - - -Another common example is this: - -.. code-block:: nim - from sequtils import toSeq - - iterator something: string = - yield "Hello" - yield "World" - - var info = toSeq(something()) - -The problem here is that the compiler already decided that ``something()`` as -an iterator is not callable in this context before ``toSeq`` gets its -chance to convert it into a sequence. - - -Macros -====== - -A macro is a special kind of low level template. Macros can be used -to implement `domain specific languages`:idx:. - -While macros enable advanced compile-time code transformations, they -cannot change Nim's syntax. However, this is no real restriction because -Nim's syntax is flexible enough anyway. - -To write macros, one needs to know how the Nim concrete syntax is converted -to an abstract syntax tree. - -There are two ways to invoke a macro: -(1) invoking a macro like a procedure call (`expression macros`) -(2) invoking a macro with the special ``macrostmt`` syntax (`statement macros`) - - -Expression Macros ------------------ - -The following example implements a powerful ``debug`` command that accepts a -variable number of arguments: - -.. code-block:: nim - # to work with Nim syntax trees, we need an API that is defined in the - # ``macros`` module: - import macros - - macro debug(n: varargs[untyped]): untyped = - # `n` is a Nim AST that contains the whole macro invocation - # this macro returns a list of statements: - result = newNimNode(nnkStmtList, n) - # iterate over any argument that is passed to this macro: - for i in 0..n.len-1: - # add a call to the statement list that writes the expression; - # `toStrLit` converts an AST to its string representation: - add(result, newCall("write", newIdentNode("stdout"), toStrLit(n[i]))) - # add a call to the statement list that writes ": " - add(result, newCall("write", newIdentNode("stdout"), newStrLitNode(": "))) - # add a call to the statement list that writes the expressions value: - add(result, newCall("writeLine", newIdentNode("stdout"), n[i])) - - var - a: array [0..10, int] - x = "some string" - a[0] = 42 - a[1] = 45 - - debug(a[0], a[1], x) - -The macro call expands to: - -.. code-block:: nim - write(stdout, "a[0]") - write(stdout, ": ") - writeLine(stdout, a[0]) - - write(stdout, "a[1]") - write(stdout, ": ") - writeLine(stdout, a[1]) - - write(stdout, "x") - write(stdout, ": ") - writeLine(stdout, x) - - -Arguments that are passed to a ``varargs`` parameter are wrapped in an array -constructor expression. This is why ``debug`` iterates over all of ``n``'s -children. - - -BindSym -------- - -The above ``debug`` macro relies on the fact that ``write``, ``writeLine`` and -``stdout`` are declared in the system module and thus visible in the -instantiating context. There is a way to use bound identifiers -(aka `symbols`:idx:) instead of using unbound identifiers. The ``bindSym`` -builtin can be used for that: - -.. code-block:: nim - import macros - - macro debug(n: varargs[typed]): untyped = - result = newNimNode(nnkStmtList, n) - for x in n: - # we can bind symbols in scope via 'bindSym': - add(result, newCall(bindSym"write", bindSym"stdout", toStrLit(x))) - add(result, newCall(bindSym"write", bindSym"stdout", newStrLitNode(": "))) - add(result, newCall(bindSym"writeLine", bindSym"stdout", x)) - - var - a: array [0..10, int] - x = "some string" - a[0] = 42 - a[1] = 45 - - debug(a[0], a[1], x) - -The macro call expands to: - -.. code-block:: nim - write(stdout, "a[0]") - write(stdout, ": ") - writeLine(stdout, a[0]) - - write(stdout, "a[1]") - write(stdout, ": ") - writeLine(stdout, a[1]) - - write(stdout, "x") - write(stdout, ": ") - writeLine(stdout, x) - -However, the symbols ``write``, ``writeLine`` and ``stdout`` are already bound -and are not looked up again. As the example shows, ``bindSym`` does work with -overloaded symbols implicitly. - - -Statement Macros ----------------- - -Statement macros are defined just as expression macros. However, they are -invoked by an expression following a colon. - -The following example outlines a macro that generates a lexical analyzer from -regular expressions: - -.. code-block:: nim - import macros - - macro case_token(n: untyped): untyped = - # creates a lexical analyzer from regular expressions - # ... (implementation is an exercise for the reader :-) - discard - - case_token: # this colon tells the parser it is a macro statement - of r"[A-Za-z_]+[A-Za-z_0-9]*": - return tkIdentifier - of r"0-9+": - return tkInteger - of r"[\+\-\*\?]+": - return tkOperator - else: - return tkUnknown - - -**Style note**: For code readability, it is the best idea to use the least -powerful programming construct that still suffices. So the "check list" is: - -(1) Use an ordinary proc/iterator, if possible. -(2) Else: Use a generic proc/iterator, if possible. -(3) Else: Use a template, if possible. -(4) Else: Use a macro. - - -Macros as pragmas ------------------ - -Whole routines (procs, iterators etc.) can also be passed to a template or -a macro via the pragma notation: - -.. code-block:: nim - template m(s: untyped) = discard - - proc p() {.m.} = discard - -This is a simple syntactic transformation into: - -.. code-block:: nim - template m(s: untyped) = discard - - m: - proc p() = discard - diff --git a/doc/manual/threads.txt b/doc/manual/threads.txt deleted file mode 100644 index 53071b5a5..000000000 --- a/doc/manual/threads.txt +++ /dev/null @@ -1,221 +0,0 @@ -Threads -======= - -To enable thread support the ``--threads:on`` command line switch needs to -be used. The ``system`` module then contains several threading primitives. -See the `threads <threads.html>`_ and `channels <channels.html>`_ modules -for the low level thread API. There are also high level parallelism constructs -available. See `spawn <#parallel-spawn>`_ for further details. - -Nim's memory model for threads is quite different than that of other common -programming languages (C, Pascal, Java): Each thread has its own (garbage -collected) heap and sharing of memory is restricted to global variables. This -helps to prevent race conditions. GC efficiency is improved quite a lot, -because the GC never has to stop other threads and see what they reference. -Memory allocation requires no lock at all! This design easily scales to massive -multicore processors that are becoming the norm. - - -Thread pragma -------------- - -A proc that is executed as a new thread of execution should be marked by the -``thread`` pragma for reasons of readability. The compiler checks for -violations of the `no heap sharing restriction`:idx:\: This restriction implies -that it is invalid to construct a data structure that consists of memory -allocated from different (thread local) heaps. - -A thread proc is passed to ``createThread`` or ``spawn`` and invoked -indirectly; so the ``thread`` pragma implies ``procvar``. - - -GC safety ---------- - -We call a proc ``p`` `GC safe`:idx: when it doesn't access any global variable -that contains GC'ed memory (``string``, ``seq``, ``ref`` or a closure) either -directly or indirectly through a call to a GC unsafe proc. - -The `gcsafe`:idx: annotation can be used to mark a proc to be gcsafe, -otherwise this property is inferred by the compiler. Note that ``noSideEffect`` -implies ``gcsafe``. The only way to create a thread is via ``spawn`` or -``createThread``. ``spawn`` is usually the preferable method. Either way -the invoked proc must not use ``var`` parameters nor must any of its parameters -contain a ``ref`` or ``closure`` type. This enforces -the *no heap sharing restriction*. - -Routines that are imported from C are always assumed to be ``gcsafe``. -To disable the GC-safety checking the ``--threadAnalysis:off`` command line -switch can be used. This is a temporary workaround to ease the porting effort -from old code to the new threading model. - -To override the compiler's gcsafety analysis a ``{.gcsafe.}`` pragma block can -be used: - -.. code-block:: nim - - var - someGlobal: string = "some string here" - perThread {.threadvar.}: string - - proc setPerThread() = - {.gcsafe.}: - deepCopy(perThread, someGlobal) - - -Future directions: - -- A shared GC'ed heap might be provided. - - -Threadvar pragma ----------------- - -A global variable can be marked with the ``threadvar`` pragma; it is -a `thread-local`:idx: variable then: - -.. code-block:: nim - var checkpoints* {.threadvar.}: seq[string] - -Due to implementation restrictions thread local variables cannot be -initialized within the ``var`` section. (Every thread local variable needs to -be replicated at thread creation.) - - -Threads and exceptions ----------------------- - -The interaction between threads and exceptions is simple: A *handled* exception -in one thread cannot affect any other thread. However, an *unhandled* exception -in one thread terminates the whole *process*! - - - -Parallel & Spawn -================ - -Nim has two flavors of parallelism: -1) `Structured`:idx: parallelism via the ``parallel`` statement. -2) `Unstructured`:idx: parallelism via the standalone ``spawn`` statement. - -Nim has a builtin thread pool that can be used for CPU intensive tasks. For -IO intensive tasks the ``async`` and ``await`` features should be -used instead. Both parallel and spawn need the `threadpool <threadpool.html>`_ -module to work. - -Somewhat confusingly, ``spawn`` is also used in the ``parallel`` statement -with slightly different semantics. ``spawn`` always takes a call expression of -the form ``f(a, ...)``. Let ``T`` be ``f``'s return type. If ``T`` is ``void`` -then ``spawn``'s return type is also ``void`` otherwise it is ``FlowVar[T]``. - -Within a ``parallel`` section sometimes the ``FlowVar[T]`` is eliminated -to ``T``. This happens when ``T`` does not contain any GC'ed memory. -The compiler can ensure the location in ``location = spawn f(...)`` is not -read prematurely within a ``parallel`` section and so there is no need for -the overhead of an indirection via ``FlowVar[T]`` to ensure correctness. - -**Note**: Currently exceptions are not propagated between ``spawn``'ed tasks! - - -Spawn statement ---------------- - -`spawn`:idx: can be used to pass a task to the thread pool: - -.. code-block:: nim - import threadpool - - proc processLine(line: string) = - discard "do some heavy lifting here" - - for x in lines("myinput.txt"): - spawn processLine(x) - sync() - -For reasons of type safety and implementation simplicity the expression -that ``spawn`` takes is restricted: - -* It must be a call expression ``f(a, ...)``. -* ``f`` must be ``gcsafe``. -* ``f`` must not have the calling convention ``closure``. -* ``f``'s parameters may not be of type ``var``. - This means one has to use raw ``ptr``'s for data passing reminding the - programmer to be careful. -* ``ref`` parameters are deeply copied which is a subtle semantic change and - can cause performance problems but ensures memory safety. This deep copy - is performed via ``system.deepCopy`` and so can be overridden. -* For *safe* data exchange between ``f`` and the caller a global ``TChannel`` - needs to be used. However, since spawn can return a result, often no further - communication is required. - - -``spawn`` executes the passed expression on the thread pool and returns -a `data flow variable`:idx: ``FlowVar[T]`` that can be read from. The reading -with the ``^`` operator is **blocking**. However, one can use ``awaitAny`` to -wait on multiple flow variables at the same time: - -.. code-block:: nim - import threadpool, ... - - # wait until 2 out of 3 servers received the update: - proc main = - var responses = newSeq[FlowVarBase](3) - for i in 0..2: - responses[i] = spawn tellServer(Update, "key", "value") - var index = awaitAny(responses) - assert index >= 0 - responses.del(index) - discard awaitAny(responses) - -Data flow variables ensure that no data races -are possible. Due to technical limitations not every type ``T`` is possible in -a data flow variable: ``T`` has to be of the type ``ref``, ``string``, ``seq`` -or of a type that doesn't contain a type that is garbage collected. This -restriction is not hard to work-around in practice. - - - -Parallel statement ------------------- - -Example: - -.. code-block:: nim - # Compute PI in an inefficient way - import strutils, math, threadpool - - proc term(k: float): float = 4 * math.pow(-1, k) / (2*k + 1) - - proc pi(n: int): float = - var ch = newSeq[float](n+1) - parallel: - for k in 0..ch.high: - ch[k] = spawn term(float(k)) - for k in 0..ch.high: - result += ch[k] - - echo formatFloat(pi(5000)) - - -The parallel statement is the preferred mechanism to introduce parallelism -in a Nim program. A subset of the Nim language is valid within a -``parallel`` section. This subset is checked to be free of data races at -compile time. A sophisticated `disjoint checker`:idx: ensures that no data -races are possible even though shared memory is extensively supported! - -The subset is in fact the full language with the following -restrictions / changes: - -* ``spawn`` within a ``parallel`` section has special semantics. -* Every location of the form ``a[i]`` and ``a[i..j]`` and ``dest`` where - ``dest`` is part of the pattern ``dest = spawn f(...)`` has to be - provably disjoint. This is called the *disjoint check*. -* Every other complex location ``loc`` that is used in a spawned - proc (``spawn f(loc)``) has to be immutable for the duration of - the ``parallel`` section. This is called the *immutability check*. Currently - it is not specified what exactly "complex location" means. We need to make - this an optimization! -* Every array access has to be provably within bounds. This is called - the *bounds check*. -* Slices are optimized so that no copy is performed. This optimization is not - yet performed for ordinary slices outside of a ``parallel`` section. diff --git a/doc/manual/trmacros.txt b/doc/manual/trmacros.txt deleted file mode 100644 index 0845cebf5..000000000 --- a/doc/manual/trmacros.txt +++ /dev/null @@ -1,367 +0,0 @@ -Term rewriting macros -===================== - -Term rewriting macros are macros or templates that have not only -a *name* but also a *pattern* that is searched for after the semantic checking -phase of the compiler: This means they provide an easy way to enhance the -compilation pipeline with user defined optimizations: - -.. code-block:: nim - template optMul{`*`(a, 2)}(a: int): int = a+a - - let x = 3 - echo x * 2 - -The compiler now rewrites ``x * 2`` as ``x + x``. The code inside the -curlies is the pattern to match against. The operators ``*``, ``**``, -``|``, ``~`` have a special meaning in patterns if they are written in infix -notation, so to match verbatim against ``*`` the ordinary function call syntax -needs to be used. - - -Unfortunately optimizations are hard to get right and even the tiny example -is **wrong**: - -.. code-block:: nim - template optMul{`*`(a, 2)}(a: int): int = a+a - - proc f(): int = - echo "side effect!" - result = 55 - - echo f() * 2 - -We cannot duplicate 'a' if it denotes an expression that has a side effect! -Fortunately Nim supports side effect analysis: - -.. code-block:: nim - template optMul{`*`(a, 2)}(a: int{noSideEffect}): int = a+a - - proc f(): int = - echo "side effect!" - result = 55 - - echo f() * 2 # not optimized ;-) - -You can make one overload matching with a constraint and one without, and the -one with a constraint will have precedence, and so you can handle both cases -differently. - -So what about ``2 * a``? We should tell the compiler ``*`` is commutative. We -cannot really do that however as the following code only swaps arguments -blindly: - -.. code-block:: nim - template mulIsCommutative{`*`(a, b)}(a, b: int): int = b*a - -What optimizers really need to do is a *canonicalization*: - -.. code-block:: nim - template canonMul{`*`(a, b)}(a: int{lit}, b: int): int = b*a - -The ``int{lit}`` parameter pattern matches against an expression of -type ``int``, but only if it's a literal. - - - -Parameter constraints ---------------------- - -The `parameter constraint`:idx: expression can use the operators ``|`` (or), -``&`` (and) and ``~`` (not) and the following predicates: - -=================== ===================================================== -Predicate Meaning -=================== ===================================================== -``atom`` The matching node has no children. -``lit`` The matching node is a literal like "abc", 12. -``sym`` The matching node must be a symbol (a bound - identifier). -``ident`` The matching node must be an identifier (an unbound - identifier). -``call`` The matching AST must be a call/apply expression. -``lvalue`` The matching AST must be an lvalue. -``sideeffect`` The matching AST must have a side effect. -``nosideeffect`` The matching AST must have no side effect. -``param`` A symbol which is a parameter. -``genericparam`` A symbol which is a generic parameter. -``module`` A symbol which is a module. -``type`` A symbol which is a type. -``var`` A symbol which is a variable. -``let`` A symbol which is a ``let`` variable. -``const`` A symbol which is a constant. -``result`` The special ``result`` variable. -``proc`` A symbol which is a proc. -``method`` A symbol which is a method. -``iterator`` A symbol which is an iterator. -``converter`` A symbol which is a converter. -``macro`` A symbol which is a macro. -``template`` A symbol which is a template. -``field`` A symbol which is a field in a tuple or an object. -``enumfield`` A symbol which is a field in an enumeration. -``forvar`` A for loop variable. -``label`` A label (used in ``block`` statements). -``nk*`` The matching AST must have the specified kind. - (Example: ``nkIfStmt`` denotes an ``if`` statement.) -``alias`` States that the marked parameter needs to alias - with *some* other parameter. -``noalias`` States that *every* other parameter must not alias - with the marked parameter. -=================== ===================================================== - -Predicates that share their name with a keyword have to be escaped with -backticks: `` `const` ``. -The ``alias`` and ``noalias`` predicates refer not only to the matching AST, -but also to every other bound parameter; syntactically they need to occur after -the ordinary AST predicates: - -.. code-block:: nim - template ex{a = b + c}(a: int{noalias}, b, c: int) = - # this transformation is only valid if 'b' and 'c' do not alias 'a': - a = b - inc a, c - - -Pattern operators ------------------ - -The operators ``*``, ``**``, ``|``, ``~`` have a special meaning in patterns -if they are written in infix notation. - - -The ``|`` operator -~~~~~~~~~~~~~~~~~~ - -The ``|`` operator if used as infix operator creates an ordered choice: - -.. code-block:: nim - template t{0|1}(): untyped = 3 - let a = 1 - # outputs 3: - echo a - -The matching is performed after the compiler performed some optimizations like -constant folding, so the following does not work: - -.. code-block:: nim - template t{0|1}(): untyped = 3 - # outputs 1: - echo 1 - -The reason is that the compiler already transformed the 1 into "1" for -the ``echo`` statement. However, a term rewriting macro should not change the -semantics anyway. In fact they can be deactivated with the ``--patterns:off`` -command line option or temporarily with the ``patterns`` pragma. - - -The ``{}`` operator -~~~~~~~~~~~~~~~~~~~ - -A pattern expression can be bound to a pattern parameter via the ``expr{param}`` -notation: - -.. code-block:: nim - template t{(0|1|2){x}}(x: untyped): untyped = x+1 - let a = 1 - # outputs 2: - echo a - - -The ``~`` operator -~~~~~~~~~~~~~~~~~~ - -The ``~`` operator is the **not** operator in patterns: - -.. code-block:: nim - template t{x = (~x){y} and (~x){z}}(x, y, z: bool) = - x = y - if x: x = z - - var - a = false - b = true - c = false - a = b and c - echo a - - -The ``*`` operator -~~~~~~~~~~~~~~~~~~ - -The ``*`` operator can *flatten* a nested binary expression like ``a & b & c`` -to ``&(a, b, c)``: - -.. code-block:: nim - var - calls = 0 - - proc `&&`(s: varargs[string]): string = - result = s[0] - for i in 1..len(s)-1: result.add s[i] - inc calls - - template optConc{ `&&` * a }(a: string): untyped = &&a - - let space = " " - echo "my" && (space & "awe" && "some " ) && "concat" - - # check that it's been optimized properly: - doAssert calls == 1 - - -The second operator of `*` must be a parameter; it is used to gather all the -arguments. The expression ``"my" && (space & "awe" && "some " ) && "concat"`` -is passed to ``optConc`` in ``a`` as a special list (of kind ``nkArgList``) -which is flattened into a call expression; thus the invocation of ``optConc`` -produces: - -.. code-block:: nim - `&&`("my", space & "awe", "some ", "concat") - - -The ``**`` operator -~~~~~~~~~~~~~~~~~~~ - -The ``**`` is much like the ``*`` operator, except that it gathers not only -all the arguments, but also the matched operators in reverse polish notation: - -.. code-block:: nim - import macros - - type - Matrix = object - dummy: int - - proc `*`(a, b: Matrix): Matrix = discard - proc `+`(a, b: Matrix): Matrix = discard - proc `-`(a, b: Matrix): Matrix = discard - proc `$`(a: Matrix): string = result = $a.dummy - proc mat21(): Matrix = - result.dummy = 21 - - macro optM{ (`+`|`-`|`*`) ** a }(a: Matrix): untyped = - echo treeRepr(a) - result = newCall(bindSym"mat21") - - var x, y, z: Matrix - - echo x + y * z - x - -This passes the expression ``x + y * z - x`` to the ``optM`` macro as -an ``nnkArgList`` node containing:: - - Arglist - Sym "x" - Sym "y" - Sym "z" - Sym "*" - Sym "+" - Sym "x" - Sym "-" - -(Which is the reverse polish notation of ``x + y * z - x``.) - - -Parameters ----------- - -Parameters in a pattern are type checked in the matching process. If a -parameter is of the type ``varargs`` it is treated specially and it can match -0 or more arguments in the AST to be matched against: - -.. code-block:: nim - template optWrite{ - write(f, x) - ((write|writeLine){w})(f, y) - }(x, y: varargs[untyped], f: File, w: untyped) = - w(f, x, y) - - - -Example: Partial evaluation ---------------------------- - -The following example shows how some simple partial evaluation can be -implemented with term rewriting: - -.. code-block:: nim - proc p(x, y: int; cond: bool): int = - result = if cond: x + y else: x - y - - template optP1{p(x, y, true)}(x, y: untyped): untyped = x + y - template optP2{p(x, y, false)}(x, y: untyped): untyped = x - y - - -Example: Hoisting ------------------ - -The following example shows how some form of hoisting can be implemented: - -.. code-block:: nim - import pegs - - template optPeg{peg(pattern)}(pattern: string{lit}): Peg = - var gl {.global, gensym.} = peg(pattern) - gl - - for i in 0 .. 3: - echo match("(a b c)", peg"'(' @ ')'") - echo match("W_HI_Le", peg"\y 'while'") - -The ``optPeg`` template optimizes the case of a peg constructor with a string -literal, so that the pattern will only be parsed once at program startup and -stored in a global ``gl`` which is then re-used. This optimization is called -hoisting because it is comparable to classical loop hoisting. - - -AST based overloading -===================== - -Parameter constraints can also be used for ordinary routine parameters; these -constraints affect ordinary overloading resolution then: - -.. code-block:: nim - proc optLit(a: string{lit|`const`}) = - echo "string literal" - proc optLit(a: string) = - echo "no string literal" - - const - constant = "abc" - - var - variable = "xyz" - - optLit("literal") - optLit(constant) - optLit(variable) - -However, the constraints ``alias`` and ``noalias`` are not available in -ordinary routines. - - -Move optimization ------------------ - -The ``call`` constraint is particularly useful to implement a move -optimization for types that have copying semantics: - -.. code-block:: nim - proc `[]=`*(t: var Table, key: string, val: string) = - ## puts a (key, value)-pair into `t`. The semantics of string require - ## a copy here: - let idx = findInsertionPosition(key) - t[idx].key = key - t[idx].val = val - - proc `[]=`*(t: var Table, key: string{call}, val: string{call}) = - ## puts a (key, value)-pair into `t`. Optimized version that knows that - ## the strings are unique and thus don't need to be copied: - let idx = findInsertionPosition(key) - shallowCopy t[idx].key, key - shallowCopy t[idx].val, val - - var t: Table - # overloading resolution ensures that the optimized []= is called here: - t[f()] = g() - diff --git a/doc/manual/type_bound_ops.txt b/doc/manual/type_bound_ops.txt deleted file mode 100644 index b531abf31..000000000 --- a/doc/manual/type_bound_ops.txt +++ /dev/null @@ -1,134 +0,0 @@ -Type bound operations -===================== - -There are 3 operations that are bound to a type: - -1. Assignment -2. Destruction -3. Deep copying for communication between threads - -These operations can be *overridden* instead of *overloaded*. This means the -implementation is automatically lifted to structured types. For instance if type -``T`` has an overridden assignment operator ``=`` this operator is also used -for assignments of the type ``seq[T]``. Since these operations are bound to a -type they have to be bound to a nominal type for reasons of simplicity of -implementation: This means an overridden ``deepCopy`` for ``ref T`` is really -bound to ``T`` and not to ``ref T``. This also means that one cannot override -``deepCopy`` for both ``ptr T`` and ``ref T`` at the same time; instead a -helper distinct or object type has to be used for one pointer type. - - -operator `=` ------------- - -This operator is the assignment operator. Note that in the contexts -``result = expr``, ``parameter = defaultValue`` or for -parameter passing no assignment is performed. For a type ``T`` that has an -overloaded assignment operator ``var v = T()`` is rewritten -to ``var v: T; v = T()``; in other words ``var`` and ``let`` contexts do count -as assignments. - -The assignment operator needs to be attached to an object or distinct -type ``T``. Its signature has to be ``(var T, T)``. Example: - -.. code-block:: nim - type - Concrete = object - a, b: string - - proc `=`(d: var Concrete; src: Concrete) = - shallowCopy(d.a, src.a) - shallowCopy(d.b, src.b) - echo "Concrete '=' called" - - var x, y: array[0..2, Concrete] - var cA, cB: Concrete - - var cATup, cBTup: tuple[x: int, ha: Concrete] - - x = y - cA = cB - cATup = cBTup - - - -destructors ------------ - -A destructor must have a single parameter with a concrete type (the name of a -generic type is allowed too). The name of the destructor has to be ``=destroy``. - -``=destroy(v)`` will be automatically invoked for every local stack -variable ``v`` that goes out of scope. - -If a structured type features a field with destructable type and -the user has not provided an explicit implementation, a destructor for the -structured type will be automatically generated. Calls to any base class -destructors in both user-defined and generated destructors will be inserted. - -A destructor is attached to the type it destructs; expressions of this type -can then only be used in *destructible contexts* and as parameters: - -.. code-block:: nim - type - MyObj = object - x, y: int - p: pointer - - proc `=destroy`(o: var MyObj) = - if o.p != nil: dealloc o.p - - proc open: MyObj = - result = MyObj(x: 1, y: 2, p: alloc(3)) - - proc work(o: MyObj) = - echo o.x - # No destructor invoked here for 'o' as 'o' is a parameter. - - proc main() = - # destructor automatically invoked at the end of the scope: - var x = open() - # valid: pass 'x' to some other proc: - work(x) - - # Error: usage of a type with a destructor in a non destructible context - echo open() - -A destructible context is currently only the following: - -1. The ``expr`` in ``var x = expr``. -2. The ``expr`` in ``let x = expr``. -3. The ``expr`` in ``return expr``. -4. The ``expr`` in ``result = expr`` where ``result`` is the special symbol - introduced by the compiler. - -These rules ensure that the construction is tied to a variable and can easily -be destructed at its scope exit. Later versions of the language will improve -the support of destructors. - -Be aware that destructors are not called for objects allocated with ``new``. -This may change in future versions of language, but for now the `finalizer`:idx: -parameter to ``new`` has to be used. - -**Note**: Destructors are still experimental and the spec might change -significantly in order to incorporate an escape analysis. - - -deepCopy --------- - -``=deepCopy`` is a builtin that is invoked whenever data is passed to -a ``spawn``'ed proc to ensure memory safety. The programmer can override its -behaviour for a specific ``ref`` or ``ptr`` type ``T``. (Later versions of the -language may weaken this restriction.) - -The signature has to be: - -.. code-block:: nim - proc `=deepCopy`(x: T): T - -This mechanism will be used by most data structures that support shared memory -like channels to implement thread safe automatic memory management. - -The builtin ``deepCopy`` can even clone closures and their environments. See -the documentation of `spawn`_ for details. diff --git a/doc/manual/type_rel.txt b/doc/manual/type_rel.txt deleted file mode 100644 index 3372691fb..000000000 --- a/doc/manual/type_rel.txt +++ /dev/null @@ -1,494 +0,0 @@ -Type relations -============== - -The following section defines several relations on types that are needed to -describe the type checking done by the compiler. - - -Type equality -------------- -Nim uses structural type equivalence for most types. Only for objects, -enumerations and distinct types name equivalence is used. The following -algorithm, *in pseudo-code*, determines type equality: - -.. code-block:: nim - proc typeEqualsAux(a, b: PType, - s: var HashSet[(PType, PType)]): bool = - if (a,b) in s: return true - incl(s, (a,b)) - if a.kind == b.kind: - case a.kind - of int, intXX, float, floatXX, char, string, cstring, pointer, - bool, nil, void: - # leaf type: kinds identical; nothing more to check - result = true - of ref, ptr, var, set, seq, openarray: - result = typeEqualsAux(a.baseType, b.baseType, s) - of range: - result = typeEqualsAux(a.baseType, b.baseType, s) and - (a.rangeA == b.rangeA) and (a.rangeB == b.rangeB) - of array: - result = typeEqualsAux(a.baseType, b.baseType, s) and - typeEqualsAux(a.indexType, b.indexType, s) - of tuple: - if a.tupleLen == b.tupleLen: - for i in 0..a.tupleLen-1: - if not typeEqualsAux(a[i], b[i], s): return false - result = true - of object, enum, distinct: - result = a == b - of proc: - result = typeEqualsAux(a.parameterTuple, b.parameterTuple, s) and - typeEqualsAux(a.resultType, b.resultType, s) and - a.callingConvention == b.callingConvention - - proc typeEquals(a, b: PType): bool = - var s: HashSet[(PType, PType)] = {} - result = typeEqualsAux(a, b, s) - -Since types are graphs which can have cycles, the above algorithm needs an -auxiliary set ``s`` to detect this case. - - -Type equality modulo type distinction -------------------------------------- - -The following algorithm (in pseudo-code) determines whether two types -are equal with no respect to ``distinct`` types. For brevity the cycle check -with an auxiliary set ``s`` is omitted: - -.. code-block:: nim - proc typeEqualsOrDistinct(a, b: PType): bool = - if a.kind == b.kind: - case a.kind - of int, intXX, float, floatXX, char, string, cstring, pointer, - bool, nil, void: - # leaf type: kinds identical; nothing more to check - result = true - of ref, ptr, var, set, seq, openarray: - result = typeEqualsOrDistinct(a.baseType, b.baseType) - of range: - result = typeEqualsOrDistinct(a.baseType, b.baseType) and - (a.rangeA == b.rangeA) and (a.rangeB == b.rangeB) - of array: - result = typeEqualsOrDistinct(a.baseType, b.baseType) and - typeEqualsOrDistinct(a.indexType, b.indexType) - of tuple: - if a.tupleLen == b.tupleLen: - for i in 0..a.tupleLen-1: - if not typeEqualsOrDistinct(a[i], b[i]): return false - result = true - of distinct: - result = typeEqualsOrDistinct(a.baseType, b.baseType) - of object, enum: - result = a == b - of proc: - result = typeEqualsOrDistinct(a.parameterTuple, b.parameterTuple) and - typeEqualsOrDistinct(a.resultType, b.resultType) and - a.callingConvention == b.callingConvention - elif a.kind == distinct: - result = typeEqualsOrDistinct(a.baseType, b) - elif b.kind == distinct: - result = typeEqualsOrDistinct(a, b.baseType) - - -Subtype relation ----------------- -If object ``a`` inherits from ``b``, ``a`` is a subtype of ``b``. This subtype -relation is extended to the types ``var``, ``ref``, ``ptr``: - -.. code-block:: nim - proc isSubtype(a, b: PType): bool = - if a.kind == b.kind: - case a.kind - of object: - var aa = a.baseType - while aa != nil and aa != b: aa = aa.baseType - result = aa == b - of var, ref, ptr: - result = isSubtype(a.baseType, b.baseType) - -.. XXX nil is a special value! - - -Covariance ----------- - -Covariance in Nim can be introduced only though pointer-like types such -as ``ptr`` and ``ref``. Sequence, Array and OpenArray types, instantiated -with pointer-like types will be considered covariant if and only if they -are also immutable. The introduction of a ``var`` modifier or additional -``ptr`` or ``ref`` indirections would result in invariant treatment of -these types. - -``proc`` types are currently always invariant, but future versions of Nim -may relax this rule. - -User-defined generic types may also be covariant with respect to some of -their parameters. By default, all generic params are considered invariant, -but you may choose the apply the prefix modifier ``in`` to a parameter to -make it contravariant or ``out`` to make it covariant: - -.. code-block:: nim - type - AnnotatedPtr[out T] = - metadata: MyTypeInfo - p: ref T - - RingBuffer[out T] = - startPos: int - data: seq[T] - - Action {.importcpp: "std::function<void ('0)>".} [in T] = object - -When the designated generic parameter is used to instantiate a pointer-like -type as in the case of `AnnotatedPtr` above, the resulting generic type will -also have pointer-like covariance: - -.. code-block:: nim - type - GuiWidget = object of RootObj - Button = object of GuiWidget - ComboBox = object of GuiWidget - - var - widgetPtr: AnnotatedPtr[GuiWidget] - buttonPtr: AnnotatedPtr[Button] - - ... - - proc drawWidget[T](x: AnnotatedPtr[GuiWidget]) = ... - - # you can call procs expecting base types by supplying a derived type - drawWidget(buttonPtr) - - # and you can convert more-specific pointer types to more general ones - widgetPtr = buttonPtr - -Just like with regular pointers, covariance will be enabled only for immutable -values: - -.. code-block:: nim - proc makeComboBox[T](x: var AnnotatedPtr[GuiWidget]) = - x.p = new(ComboBox) - - makeComboBox(buttonPtr) # Error, AnnotatedPtr[Button] cannot be modified - # to point to a ComboBox - -On the other hand, in the `RingBuffer` example above, the designated generic -param is used to instantiate the non-pointer ``seq`` type, which means that -the resulting generic type will have covariance that mimics an array or -sequence (i.e. it will be covariant only when instantiated with ``ptr`` and -``ref`` types): - -.. code-block:: nim - - type - Base = object of RootObj - Derived = object of Base - - proc consumeBaseValues(b: RingBuffer[Base]) = ... - - var derivedValues: RingBuffer[Derived] - - consumeBaseValues(derivedValues) # Error, Base and Derived values may differ - # in size - - proc consumeBasePointers(b: RingBuffer[ptr Base]) = ... - - var derivedPointers: RingBuffer[ptr Derived] - - consumeBaseValues(derivedPointers) # This is legal - -Please note that Nim will treat the user-defined pointer-like types as -proper alternatives to the built-in pointer types. That is, types such -as `seq[AnnotatedPtr[T]]` or `RingBuffer[AnnotatedPtr[T]]` will also be -considered covariant and you can create new pointer-like types by instantiating -other user-defined pointer-like types. - -The contravariant parameters introduced with the ``in`` modifier are currently -useful only when interfacing with imported types having such semantics. - - -Convertible relation --------------------- -A type ``a`` is **implicitly** convertible to type ``b`` iff the following -algorithm returns true: - -.. code-block:: nim - # XXX range types? - proc isImplicitlyConvertible(a, b: PType): bool = - if isSubtype(a, b) or isCovariant(a, b): - return true - case a.kind - of int: result = b in {int8, int16, int32, int64, uint, uint8, uint16, - uint32, uint64, float, float32, float64} - of int8: result = b in {int16, int32, int64, int} - of int16: result = b in {int32, int64, int} - of int32: result = b in {int64, int} - of uint: result = b in {uint32, uint64} - of uint8: result = b in {uint16, uint32, uint64} - of uint16: result = b in {uint32, uint64} - of uint32: result = b in {uint64} - of float: result = b in {float32, float64} - of float32: result = b in {float64, float} - of float64: result = b in {float32, float} - of seq: - result = b == openArray and typeEquals(a.baseType, b.baseType) - of array: - result = b == openArray and typeEquals(a.baseType, b.baseType) - if a.baseType == char and a.indexType.rangeA == 0: - result = b = cstring - of cstring, ptr: - result = b == pointer - of string: - result = b == cstring - -A type ``a`` is **explicitly** convertible to type ``b`` iff the following -algorithm returns true: - -.. code-block:: nim - proc isIntegralType(t: PType): bool = - result = isOrdinal(t) or t.kind in {float, float32, float64} - - proc isExplicitlyConvertible(a, b: PType): bool = - result = false - if isImplicitlyConvertible(a, b): return true - if typeEqualsOrDistinct(a, b): return true - if isIntegralType(a) and isIntegralType(b): return true - if isSubtype(a, b) or isSubtype(b, a): return true - -The convertible relation can be relaxed by a user-defined type -`converter`:idx:. - -.. code-block:: nim - converter toInt(x: char): int = result = ord(x) - - var - x: int - chr: char = 'a' - - # implicit conversion magic happens here - x = chr - echo x # => 97 - # you can use the explicit form too - x = chr.toInt - echo x # => 97 - -The type conversion ``T(a)`` is an L-value if ``a`` is an L-value and -``typeEqualsOrDistinct(T, type(a))`` holds. - - -Assignment compatibility ------------------------- - -An expression ``b`` can be assigned to an expression ``a`` iff ``a`` is an -`l-value` and ``isImplicitlyConvertible(b.typ, a.typ)`` holds. - - -Overloading resolution -====================== - -In a call ``p(args)`` the routine ``p`` that matches best is selected. If -multiple routines match equally well, the ambiguity is reported at compiletime. - -Every arg in args needs to match. There are multiple different categories how an -argument can match. Let ``f`` be the formal parameter's type and ``a`` the type -of the argument. - -1. Exact match: ``a`` and ``f`` are of the same type. -2. Literal match: ``a`` is an integer literal of value ``v`` - and ``f`` is a signed or unsigned integer type and ``v`` is in ``f``'s - range. Or: ``a`` is a floating point literal of value ``v`` - and ``f`` is a floating point type and ``v`` is in ``f``'s - range. -3. Generic match: ``f`` is a generic type and ``a`` matches, for - instance ``a`` is ``int`` and ``f`` is a generic (constrained) parameter - type (like in ``[T]`` or ``[T: int|char]``. -4. Subrange or subtype match: ``a`` is a ``range[T]`` and ``T`` - matches ``f`` exactly. Or: ``a`` is a subtype of ``f``. -5. Integral conversion match: ``a`` is convertible to ``f`` and ``f`` and ``a`` - is some integer or floating point type. -6. Conversion match: ``a`` is convertible to ``f``, possibly via a user - defined ``converter``. - -These matching categories have a priority: An exact match is better than a -literal match and that is better than a generic match etc. In the following -``count(p, m)`` counts the number of matches of the matching category ``m`` -for the routine ``p``. - -A routine ``p`` matches better than a routine ``q`` if the following -algorithm returns true:: - - for each matching category m in ["exact match", "literal match", - "generic match", "subtype match", - "integral match", "conversion match"]: - if count(p, m) > count(q, m): return true - elif count(p, m) == count(q, m): - discard "continue with next category m" - else: - return false - return "ambiguous" - - -Some examples: - -.. code-block:: nim - proc takesInt(x: int) = echo "int" - proc takesInt[T](x: T) = echo "T" - proc takesInt(x: int16) = echo "int16" - - takesInt(4) # "int" - var x: int32 - takesInt(x) # "T" - var y: int16 - takesInt(y) # "int16" - var z: range[0..4] = 0 - takesInt(z) # "T" - - -If this algorithm returns "ambiguous" further disambiguation is performed: -If the argument ``a`` matches both the parameter type ``f`` of ``p`` -and ``g`` of ``q`` via a subtyping relation, the inheritance depth is taken -into account: - -.. code-block:: nim - type - A = object of RootObj - B = object of A - C = object of B - - proc p(obj: A) = - echo "A" - - proc p(obj: B) = - echo "B" - - var c = C() - # not ambiguous, calls 'B', not 'A' since B is a subtype of A - # but not vice versa: - p(c) - - proc pp(obj: A, obj2: B) = echo "A B" - proc pp(obj: B, obj2: A) = echo "B A" - - # but this is ambiguous: - pp(c, c) - - -Likewise for generic matches the most specialized generic type (that still -matches) is preferred: - -.. code-block:: nim - proc gen[T](x: ref ref T) = echo "ref ref T" - proc gen[T](x: ref T) = echo "ref T" - proc gen[T](x: T) = echo "T" - - var ri: ref int - gen(ri) # "ref T" - - -Overloading based on 'var T' ----------------------------- - -If the formal parameter ``f`` is of type ``var T`` in addition to the ordinary -type checking, the argument is checked to be an `l-value`:idx:. ``var T`` -matches better than just ``T`` then. - -.. code-block:: nim - proc sayHi(x: int): string = - # matches a non-var int - result = $x - proc sayHi(x: var int): string = - # matches a var int - result = $(x + 10) - - proc sayHello(x: int) = - var m = x # a mutable version of x - echo sayHi(x) # matches the non-var version of sayHi - echo sayHi(m) # matches the var version of sayHi - - sayHello(3) # 3 - # 13 - -Automatic dereferencing ------------------------ - -If the `experimental mode <#pragmas-experimental-pragma>`_ is active and no other match -is found, the first argument ``a`` is dereferenced automatically if it's a -pointer type and overloading resolution is tried with ``a[]`` instead. - -Automatic self insertions -------------------------- - -Starting with version 0.14 of the language, Nim supports ``field`` as a -shortcut for ``self.field`` comparable to the `this`:idx: keyword in Java -or C++. This feature has to be explicitly enabled via a ``{.this: self.}`` -statement pragma. This pragma is active for the rest of the module: - -.. code-block:: nim - type - Parent = object of RootObj - parentField: int - Child = object of Parent - childField: int - - {.this: self.} - proc sumFields(self: Child): int = - result = parentField + childField - # is rewritten to: - # result = self.parentField + self.childField - -Instead of ``self`` any other identifier can be used too, but -``{.this: self.}`` will become the default directive for the whole language -eventually. - -In addition to fields, routine applications are also rewritten, but only -if no other interpretation of the call is possible: - -.. code-block:: nim - proc test(self: Child) = - echo childField, " ", sumFields() - # is rewritten to: - echo self.childField, " ", sumFields(self) - # but NOT rewritten to: - echo self, self.childField, " ", sumFields(self) - - -Lazy type resolution for untyped --------------------------------- - -**Note**: An `unresolved`:idx: expression is an expression for which no symbol -lookups and no type checking have been performed. - -Since templates and macros that are not declared as ``immediate`` participate -in overloading resolution it's essential to have a way to pass unresolved -expressions to a template or macro. This is what the meta-type ``untyped`` -accomplishes: - -.. code-block:: nim - template rem(x: untyped) = discard - - rem unresolvedExpression(undeclaredIdentifier) - -A parameter of type ``untyped`` always matches any argument (as long as there is -any argument passed to it). - -But one has to watch out because other overloads might trigger the -argument's resolution: - -.. code-block:: nim - template rem(x: untyped) = discard - proc rem[T](x: T) = discard - - # undeclared identifier: 'unresolvedExpression' - rem unresolvedExpression(undeclaredIdentifier) - -``untyped`` and ``varargs[untyped]`` are the only metatype that are lazy in this sense, the other -metatypes ``typed`` and ``typedesc`` are not lazy. - - -Varargs matching ----------------- - -See `Varargs <#types-varargs>`_. diff --git a/doc/manual/type_sections.txt b/doc/manual/type_sections.txt deleted file mode 100644 index 12b2a11ac..000000000 --- a/doc/manual/type_sections.txt +++ /dev/null @@ -1,22 +0,0 @@ -Type sections -============= - -Example: - -.. code-block:: nim - type # example demonstrating mutually recursive types - Node = ref object # an object managed by the garbage collector (ref) - le, ri: Node # left and right subtrees - sym: ref Sym # leaves contain a reference to a Sym - - Sym = object # a symbol - name: string # the symbol's name - line: int # the line the symbol was declared in - code: Node # the symbol's abstract syntax tree - -A type section begins with the ``type`` keyword. It contains multiple -type definitions. A type definition binds a type to a name. Type definitions -can be recursive or even mutually recursive. Mutually recursive types are only -possible within a single ``type`` section. Nominal types like ``objects`` -or ``enums`` can only be defined in a ``type`` section. - diff --git a/doc/manual/typedesc.txt b/doc/manual/typedesc.txt deleted file mode 100644 index 6922d77e4..000000000 --- a/doc/manual/typedesc.txt +++ /dev/null @@ -1,95 +0,0 @@ -Special Types -============= - -static[T] ---------- - -**Note**: static[T] is still in development. - -As their name suggests, static parameters must be known at compile-time: - -.. code-block:: nim - - proc precompiledRegex(pattern: static[string]): RegEx = - var res {.global.} = re(pattern) - return res - - precompiledRegex("/d+") # Replaces the call with a precompiled - # regex, stored in a global variable - - precompiledRegex(paramStr(1)) # Error, command-line options - # are not known at compile-time - - -For the purposes of code generation, all static params are treated as -generic params - the proc will be compiled separately for each unique -supplied value (or combination of values). - -Static params can also appear in the signatures of generic types: - -.. code-block:: nim - - type - Matrix[M,N: static[int]; T: Number] = array[0..(M*N - 1), T] - # Note how `Number` is just a type constraint here, while - # `static[int]` requires us to supply a compile-time int value - - AffineTransform2D[T] = Matrix[3, 3, T] - AffineTransform3D[T] = Matrix[4, 4, T] - - var m1: AffineTransform3D[float] # OK - var m2: AffineTransform2D[string] # Error, `string` is not a `Number` - - -typedesc --------- - -`typedesc` is a special type allowing one to treat types as compile-time values -(i.e. if types are compile-time values and all values have a type, then -typedesc must be their type). - -When used as a regular proc param, typedesc acts as a type class. The proc -will be instantiated for each unique type parameter and one can refer to the -instantiation type using the param name: - -.. code-block:: nim - - proc new(T: typedesc): ref T = - echo "allocating ", T.name - new(result) - - var n = Node.new - var tree = new(BinaryTree[int]) - -When multiple typedesc params are present, they act like a distinct type class -(i.e. they will bind freely to different types). To force a bind-once behavior -one can use a named alias or an explicit `typedesc` generic param: - -.. code-block:: nim - proc acceptOnlyTypePairs[T: typedesc, U: typedesc](A, B: T; C, D: U) - -Once bound, typedesc params can appear in the rest of the proc signature: - -.. code-block:: nim - - template declareVariableWithType(T: typedesc, value: T) = - var x: T = value - - declareVariableWithType int, 42 - - -Overload resolution can be further influenced by constraining the set of -types that will match the typedesc param: - -.. code-block:: nim - - template maxval(T: typedesc[int]): int = high(int) - template maxval(T: typedesc[float]): float = Inf - - var i = int.maxval - var f = float.maxval - var s = string.maxval # error, maxval is not implemented for string - -The constraint can be a concrete type or a type class. - - diff --git a/doc/manual/types.txt b/doc/manual/types.txt deleted file mode 100644 index 8a90dee05..000000000 --- a/doc/manual/types.txt +++ /dev/null @@ -1,1277 +0,0 @@ -Types -===== - -All expressions have a type which is known at compile time. Nim -is statically typed. One can declare new types, which is in essence defining -an identifier that can be used to denote this custom type. - -These are the major type classes: - -* ordinal types (consist of integer, bool, character, enumeration - (and subranges thereof) types) -* floating point types -* string type -* structured types -* reference (pointer) type -* procedural type -* generic type - - -Ordinal types -------------- -Ordinal types have the following characteristics: - -- Ordinal types are countable and ordered. This property allows - the operation of functions as ``inc``, ``ord``, ``dec`` on ordinal types to - be defined. -- Ordinal values have a smallest possible value. Trying to count further - down than the smallest value gives a checked runtime or static error. -- Ordinal values have a largest possible value. Trying to count further - than the largest value gives a checked runtime or static error. - -Integers, bool, characters and enumeration types (and subranges of these -types) belong to ordinal types. For reasons of simplicity of implementation -the types ``uint`` and ``uint64`` are not ordinal types. - - -Pre-defined integer types -------------------------- -These integer types are pre-defined: - -``int`` - the generic signed integer type; its size is platform dependent and has the - same size as a pointer. This type should be used in general. An integer - literal that has no type suffix is of this type if it is in the range - ``low(int32)..high(int32)`` otherwise the literal's type is ``int64``. - -intXX - additional signed integer types of XX bits use this naming scheme - (example: int16 is a 16 bit wide integer). - The current implementation supports ``int8``, ``int16``, ``int32``, ``int64``. - Literals of these types have the suffix 'iXX. - -``uint`` - the generic `unsigned integer`:idx: type; its size is platform dependent and - has the same size as a pointer. An integer literal with the type - suffix ``'u`` is of this type. - -uintXX - additional signed integer types of XX bits use this naming scheme - (example: uint16 is a 16 bit wide unsigned integer). - The current implementation supports ``uint8``, ``uint16``, ``uint32``, - ``uint64``. Literals of these types have the suffix 'uXX. - Unsigned operations all wrap around; they cannot lead to over- or - underflow errors. - - -In addition to the usual arithmetic operators for signed and unsigned integers -(``+ - *`` etc.) there are also operators that formally work on *signed* -integers but treat their arguments as *unsigned*: They are mostly provided -for backwards compatibility with older versions of the language that lacked -unsigned integer types. These unsigned operations for signed integers use -the ``%`` suffix as convention: - - -====================== ====================================================== -operation meaning -====================== ====================================================== -``a +% b`` unsigned integer addition -``a -% b`` unsigned integer subtraction -``a *% b`` unsigned integer multiplication -``a /% b`` unsigned integer division -``a %% b`` unsigned integer modulo operation -``a <% b`` treat ``a`` and ``b`` as unsigned and compare -``a <=% b`` treat ``a`` and ``b`` as unsigned and compare -``ze(a)`` extends the bits of ``a`` with zeros until it has the - width of the ``int`` type -``toU8(a)`` treats ``a`` as unsigned and converts it to an - unsigned integer of 8 bits (but still the - ``int8`` type) -``toU16(a)`` treats ``a`` as unsigned and converts it to an - unsigned integer of 16 bits (but still the - ``int16`` type) -``toU32(a)`` treats ``a`` as unsigned and converts it to an - unsigned integer of 32 bits (but still the - ``int32`` type) -====================== ====================================================== - -`Automatic type conversion`:idx: is performed in expressions where different -kinds of integer types are used: the smaller type is converted to the larger. - -A `narrowing type conversion`:idx: converts a larger to a smaller type (for -example ``int32 -> int16``. A `widening type conversion`:idx: converts a -smaller type to a larger type (for example ``int16 -> int32``). In Nim only -widening type conversions are *implicit*: - -.. code-block:: nim - var myInt16 = 5i16 - var myInt: int - myInt16 + 34 # of type ``int16`` - myInt16 + myInt # of type ``int`` - myInt16 + 2i32 # of type ``int32`` - -However, ``int`` literals are implicitly convertible to a smaller integer type -if the literal's value fits this smaller type and such a conversion is less -expensive than other implicit conversions, so ``myInt16 + 34`` produces -an ``int16`` result. - -For further details, see `Convertible relation -<#type-relations-convertible-relation>`_. - - -Subrange types --------------- -A subrange type is a range of values from an ordinal type (the base -type). To define a subrange type, one must specify it's limiting values: the -lowest and highest value of the type: - -.. code-block:: nim - type - Subrange = range[0..5] - - -``Subrange`` is a subrange of an integer which can only hold the values 0 -to 5. Assigning any other value to a variable of type ``Subrange`` is a -checked runtime error (or static error if it can be statically -determined). Assignments from the base type to one of its subrange types -(and vice versa) are allowed. - -A subrange type has the same size as its base type (``int`` in the example). - - -Pre-defined floating point types --------------------------------- - -The following floating point types are pre-defined: - -``float`` - the generic floating point type; its size is platform dependent - (the compiler chooses the processor's fastest floating point type). - This type should be used in general. - -floatXX - an implementation may define additional floating point types of XX bits using - this naming scheme (example: float64 is a 64 bit wide float). The current - implementation supports ``float32`` and ``float64``. Literals of these types - have the suffix 'fXX. - - -Automatic type conversion in expressions with different kinds -of floating point types is performed: See `Convertible relation`_ for further -details. Arithmetic performed on floating point types follows the IEEE -standard. Integer types are not converted to floating point types automatically -and vice versa. - -The IEEE standard defines five types of floating-point exceptions: - -* Invalid: operations with mathematically invalid operands, - for example 0.0/0.0, sqrt(-1.0), and log(-37.8). -* Division by zero: divisor is zero and dividend is a finite nonzero number, - for example 1.0/0.0. -* Overflow: operation produces a result that exceeds the range of the exponent, - for example MAXDOUBLE+0.0000000000001e308. -* Underflow: operation produces a result that is too small to be represented - as a normal number, for example, MINDOUBLE * MINDOUBLE. -* Inexact: operation produces a result that cannot be represented with infinite - precision, for example, 2.0 / 3.0, log(1.1) and 0.1 in input. - -The IEEE exceptions are either ignored at runtime or mapped to the -Nim exceptions: `FloatInvalidOpError`:idx:, `FloatDivByZeroError`:idx:, -`FloatOverflowError`:idx:, `FloatUnderflowError`:idx:, -and `FloatInexactError`:idx:. -These exceptions inherit from the `FloatingPointError`:idx: base class. - -Nim provides the pragmas `nanChecks`:idx: and `infChecks`:idx: to control -whether the IEEE exceptions are ignored or trap a Nim exception: - -.. code-block:: nim - {.nanChecks: on, infChecks: on.} - var a = 1.0 - var b = 0.0 - echo b / b # raises FloatInvalidOpError - echo a / b # raises FloatOverflowError - -In the current implementation ``FloatDivByZeroError`` and ``FloatInexactError`` -are never raised. ``FloatOverflowError`` is raised instead of -``FloatDivByZeroError``. -There is also a `floatChecks`:idx: pragma that is a short-cut for the -combination of ``nanChecks`` and ``infChecks`` pragmas. ``floatChecks`` are -turned off as default. - -The only operations that are affected by the ``floatChecks`` pragma are -the ``+``, ``-``, ``*``, ``/`` operators for floating point types. - -An implementation should always use the maximum precision available to evaluate -floating pointer values at compile time; this means expressions like -``0.09'f32 + 0.01'f32 == 0.09'f64 + 0.01'f64`` are true. - - -Boolean type ------------- -The boolean type is named `bool`:idx: in Nim and can be one of the two -pre-defined values ``true`` and ``false``. Conditions in ``while``, -``if``, ``elif``, ``when``-statements need to be of type ``bool``. - -This condition holds:: - - ord(false) == 0 and ord(true) == 1 - -The operators ``not, and, or, xor, <, <=, >, >=, !=, ==`` are defined -for the bool type. The ``and`` and ``or`` operators perform short-cut -evaluation. Example: - -.. code-block:: nim - - while p != nil and p.name != "xyz": - # p.name is not evaluated if p == nil - p = p.next - - -The size of the bool type is one byte. - - -Character type --------------- -The character type is named ``char`` in Nim. Its size is one byte. -Thus it cannot represent an UTF-8 character, but a part of it. -The reason for this is efficiency: for the overwhelming majority of use-cases, -the resulting programs will still handle UTF-8 properly as UTF-8 was specially -designed for this. -Another reason is that Nim can support ``array[char, int]`` or -``set[char]`` efficiently as many algorithms rely on this feature. The -`Rune` type is used for Unicode characters, it can represent any Unicode -character. ``Rune`` is declared in the `unicode module <unicode.html>`_. - - - - -Enumeration types ------------------ -Enumeration types define a new type whose values consist of the ones -specified. The values are ordered. Example: - -.. code-block:: nim - - type - Direction = enum - north, east, south, west - - -Now the following holds:: - - ord(north) == 0 - ord(east) == 1 - ord(south) == 2 - ord(west) == 3 - -Thus, north < east < south < west. The comparison operators can be used -with enumeration types. - -For better interfacing to other programming languages, the fields of enum -types can be assigned an explicit ordinal value. However, the ordinal values -have to be in ascending order. A field whose ordinal value is not -explicitly given is assigned the value of the previous field + 1. - -An explicit ordered enum can have *holes*: - -.. code-block:: nim - type - TokenType = enum - a = 2, b = 4, c = 89 # holes are valid - -However, it is then not an ordinal anymore, so it is not possible to use these -enums as an index type for arrays. The procedures ``inc``, ``dec``, ``succ`` -and ``pred`` are not available for them either. - - -The compiler supports the built-in stringify operator ``$`` for enumerations. -The stringify's result can be controlled by explicitly giving the string -values to use: - -.. code-block:: nim - - type - MyEnum = enum - valueA = (0, "my value A"), - valueB = "value B", - valueC = 2, - valueD = (3, "abc") - -As can be seen from the example, it is possible to both specify a field's -ordinal value and its string value by using a tuple. It is also -possible to only specify one of them. - -An enum can be marked with the ``pure`` pragma so that it's fields are not -added to the current scope, so they always need to be accessed -via ``MyEnum.value``: - -.. code-block:: nim - - type - MyEnum {.pure.} = enum - valueA, valueB, valueC, valueD - - echo valueA # error: Unknown identifier - echo MyEnum.valueA # works - - -String type ------------ -All string literals are of the type ``string``. A string in Nim is very -similar to a sequence of characters. However, strings in Nim are both -zero-terminated and have a length field. One can retrieve the length with the -builtin ``len`` procedure; the length never counts the terminating zero. -The assignment operator for strings always copies the string. -The ``&`` operator concatenates strings. - -Most native Nim types support conversion to strings with the special ``$`` proc. -When calling the ``echo`` proc, for example, the built-in stringify operation -for the parameter is called: - -.. code-block:: nim - - echo 3 # calls `$` for `int` - -Whenever a user creates a specialized object, implementation of this procedure -provides for ``string`` representation. - -.. code-block:: nim - type - Person = object - name: string - age: int - - proc `$`(p: Person): string = # `$` always returns a string - result = p.name & " is " & - $p.age & # we *need* the `$` in front of p.age, which - # is natively an integer, to convert it to - # a string - " years old." - -While ``$p.name`` can also be used, the ``$`` operation on a string does -nothing. Note that we cannot rely on automatic conversion from an ``int`` to -a ``string`` like we can for the ``echo`` proc. - -Strings are compared by their lexicographical order. All comparison operators -are available. Strings can be indexed like arrays (lower bound is 0). Unlike -arrays, they can be used in case statements: - -.. code-block:: nim - - case paramStr(i) - of "-v": incl(options, optVerbose) - of "-h", "-?": incl(options, optHelp) - else: write(stdout, "invalid command line option!\n") - -Per convention, all strings are UTF-8 strings, but this is not enforced. For -example, when reading strings from binary files, they are merely a sequence of -bytes. The index operation ``s[i]`` means the i-th *char* of ``s``, not the -i-th *unichar*. The iterator ``runes`` from the `unicode module -<unicode.html>`_ can be used for iteration over all Unicode characters. - - -cstring type ------------- - -The ``cstring`` type meaning `compatible string` is the native representation -of a string for the compilation backend. For the C backend the ``cstring`` type -represents a pointer to a zero-terminated char array -compatible to the type ``char*`` in Ansi C. Its primary purpose lies in easy -interfacing with C. The index operation ``s[i]`` means the i-th *char* of -``s``; however no bounds checking for ``cstring`` is performed making the -index operation unsafe. - -A Nim ``string`` is implicitly convertible -to ``cstring`` for convenience. If a Nim string is passed to a C-style -variadic proc, it is implicitly converted to ``cstring`` too: - -.. code-block:: nim - proc printf(formatstr: cstring) {.importc: "printf", varargs, - header: "<stdio.h>".} - - printf("This works %s", "as expected") - -Even though the conversion is implicit, it is not *safe*: The garbage collector -does not consider a ``cstring`` to be a root and may collect the underlying -memory. However in practice this almost never happens as the GC considers -stack roots conservatively. One can use the builtin procs ``GC_ref`` and -``GC_unref`` to keep the string data alive for the rare cases where it does -not work. - -A `$` proc is defined for cstrings that returns a string. Thus to get a nim -string from a cstring: - -.. code-block:: nim - var str: string = "Hello!" - var cstr: cstring = str - var newstr: string = $cstr - -Structured types ----------------- -A variable of a structured type can hold multiple values at the same -time. Structured types can be nested to unlimited levels. Arrays, sequences, -tuples, objects and sets belong to the structured types. - -Array and sequence types ------------------------- -Arrays are a homogeneous type, meaning that each element in the array -has the same type. Arrays always have a fixed length which is specified at -compile time (except for open arrays). They can be indexed by any ordinal type. -A parameter ``A`` may be an *open array*, in which case it is indexed by -integers from 0 to ``len(A)-1``. An array expression may be constructed by the -array constructor ``[]``. The element type of this array expression is -inferred from the type of the first element. All other elements need to be -implicitly convertable to this type. - -Sequences are similar to arrays but of dynamic length which may change -during runtime (like strings). Sequences are implemented as growable arrays, -allocating pieces of memory as items are added. A sequence ``S`` is always -indexed by integers from 0 to ``len(S)-1`` and its bounds are checked. -Sequences can be constructed by the array constructor ``[]`` in conjunction -with the array to sequence operator ``@``. Another way to allocate space for a -sequence is to call the built-in ``newSeq`` procedure. - -A sequence may be passed to a parameter that is of type *open array*. - -Example: - -.. code-block:: nim - - type - IntArray = array[0..5, int] # an array that is indexed with 0..5 - IntSeq = seq[int] # a sequence of integers - var - x: IntArray - y: IntSeq - x = [1, 2, 3, 4, 5, 6] # [] is the array constructor - y = @[1, 2, 3, 4, 5, 6] # the @ turns the array into a sequence - - let z = [1.0, 2, 3, 4] # the type of z is array[0..3, float] - -The lower bound of an array or sequence may be received by the built-in proc -``low()``, the higher bound by ``high()``. The length may be -received by ``len()``. ``low()`` for a sequence or an open array always returns -0, as this is the first valid index. -One can append elements to a sequence with the ``add()`` proc or the ``&`` -operator, and remove (and get) the last element of a sequence with the -``pop()`` proc. - -The notation ``x[i]`` can be used to access the i-th element of ``x``. - -Arrays are always bounds checked (at compile-time or at runtime). These -checks can be disabled via pragmas or invoking the compiler with the -``--boundChecks:off`` command line switch. - - -Open arrays ------------ - -Often fixed size arrays turn out to be too inflexible; procedures should -be able to deal with arrays of different sizes. The `openarray`:idx: type -allows this; it can only be used for parameters. Openarrays are always -indexed with an ``int`` starting at position 0. The ``len``, ``low`` -and ``high`` operations are available for open arrays too. Any array with -a compatible base type can be passed to an openarray parameter, the index -type does not matter. In addition to arrays sequences can also be passed -to an open array parameter. - -The openarray type cannot be nested: multidimensional openarrays are not -supported because this is seldom needed and cannot be done efficiently. - -.. code-block:: nim - proc testOpenArray(x: openArray[int]) = echo repr(x) - - testOpenArray([1,2,3]) # array[] - testOpenArray(@[1,2,3]) # seq[] - -Varargs -------- - -A ``varargs`` parameter is an openarray parameter that additionally -allows to pass a variable number of arguments to a procedure. The compiler -converts the list of arguments to an array implicitly: - -.. code-block:: nim - proc myWriteln(f: File, a: varargs[string]) = - for s in items(a): - write(f, s) - write(f, "\n") - - myWriteln(stdout, "abc", "def", "xyz") - # is transformed to: - myWriteln(stdout, ["abc", "def", "xyz"]) - -This transformation is only done if the varargs parameter is the -last parameter in the procedure header. It is also possible to perform -type conversions in this context: - -.. code-block:: nim - proc myWriteln(f: File, a: varargs[string, `$`]) = - for s in items(a): - write(f, s) - write(f, "\n") - - myWriteln(stdout, 123, "abc", 4.0) - # is transformed to: - myWriteln(stdout, [$123, $"def", $4.0]) - -In this example ``$`` is applied to any argument that is passed to the -parameter ``a``. (Note that ``$`` applied to strings is a nop.) - -Note that an explicit array constructor passed to a ``varargs`` parameter is -not wrapped in another implicit array construction: - -.. code-block:: nim - proc takeV[T](a: varargs[T]) = discard - - takeV([123, 2, 1]) # takeV's T is "int", not "array of int" - - -``varargs[typed]`` is treated specially: It matches a variable list of arguments -of arbitrary type but *always* constructs an implicit array. This is required -so that the builtin ``echo`` proc does what is expected: - -.. code-block:: nim - proc echo*(x: varargs[typed, `$`]) {...} - - echo @[1, 2, 3] - # prints "@[1, 2, 3]" and not "123" - - -Tuples and object types ------------------------ -A variable of a tuple or object type is a heterogeneous storage -container. -A tuple or object defines various named *fields* of a type. A tuple also -defines an *order* of the fields. Tuples are meant for heterogeneous storage -types with no overhead and few abstraction possibilities. The constructor ``()`` -can be used to construct tuples. The order of the fields in the constructor -must match the order of the tuple's definition. Different tuple-types are -*equivalent* if they specify the same fields of the same type in the same -order. The *names* of the fields also have to be identical. - -The assignment operator for tuples copies each component. -The default assignment operator for objects copies each component. Overloading -of the assignment operator is described in `type-bound-operations-operator`_. - -.. code-block:: nim - - type - Person = tuple[name: string, age: int] # type representing a person: - # a person consists of a name - # and an age - var - person: Person - person = (name: "Peter", age: 30) - # the same, but less readable: - person = ("Peter", 30) - -The implementation aligns the fields for best access performance. The alignment -is compatible with the way the C compiler does it. - -For consistency with ``object`` declarations, tuples in a ``type`` section -can also be defined with indentation instead of ``[]``: - -.. code-block:: nim - type - Person = tuple # type representing a person - name: string # a person consists of a name - age: natural # and an age - -Objects provide many features that tuples do not. Object provide inheritance -and information hiding. Objects have access to their type at runtime, so that -the ``of`` operator can be used to determine the object's type. The ``of`` operator -is similar to the ``instanceof`` operator in Java. - -.. code-block:: nim - type - Person = object of RootObj - name*: string # the * means that `name` is accessible from other modules - age: int # no * means that the field is hidden - - Student = ref object of Person # a student is a person - id: int # with an id field - - var - student: Student - person: Person - assert(student of Student) # is true - assert(student of Person) # also true - -Object fields that should be visible from outside the defining module, have to -be marked by ``*``. In contrast to tuples, different object types are -never *equivalent*. Objects that have no ancestor are implicitly ``final`` -and thus have no hidden type field. One can use the ``inheritable`` pragma to -introduce new object roots apart from ``system.RootObj``. - - -Object construction -------------------- - -Objects can also be created with an `object construction expression`:idx: that -has the syntax ``T(fieldA: valueA, fieldB: valueB, ...)`` where ``T`` is -an ``object`` type or a ``ref object`` type: - -.. code-block:: nim - var student = Student(name: "Anton", age: 5, id: 3) - -Note that, unlike tuples, objects require the field names along with their values. -For a ``ref object`` type ``system.new`` is invoked implicitly. - - -Object variants ---------------- -Often an object hierarchy is overkill in certain situations where simple -variant types are needed. - -An example: - -.. code-block:: nim - - # This is an example how an abstract syntax tree could be modelled in Nim - type - NodeKind = enum # the different node types - nkInt, # a leaf with an integer value - nkFloat, # a leaf with a float value - nkString, # a leaf with a string value - nkAdd, # an addition - nkSub, # a subtraction - nkIf # an if statement - Node = ref NodeObj - NodeObj = object - case kind: NodeKind # the ``kind`` field is the discriminator - of nkInt: intVal: int - of nkFloat: floatVal: float - of nkString: strVal: string - of nkAdd, nkSub: - leftOp, rightOp: Node - of nkIf: - condition, thenPart, elsePart: Node - - # create a new case object: - var n = Node(kind: nkIf, condition: nil) - # accessing n.thenPart is valid because the ``nkIf`` branch is active: - n.thenPart = Node(kind: nkFloat, floatVal: 2.0) - - # the following statement raises an `FieldError` exception, because - # n.kind's value does not fit and the ``nkString`` branch is not active: - n.strVal = "" - - # invalid: would change the active object branch: - n.kind = nkInt - - var x = Node(kind: nkAdd, leftOp: Node(kind: nkInt, intVal: 4), - rightOp: Node(kind: nkInt, intVal: 2)) - # valid: does not change the active object branch: - x.kind = nkSub - -As can been seen from the example, an advantage to an object hierarchy is that -no casting between different object types is needed. Yet, access to invalid -object fields raises an exception. - -The syntax of ``case`` in an object declaration follows closely the syntax of -the ``case`` statement: The branches in a ``case`` section may be indented too. - -In the example the ``kind`` field is called the `discriminator`:idx:\: For -safety its address cannot be taken and assignments to it are restricted: The -new value must not lead to a change of the active object branch. For an object -branch switch ``system.reset`` has to be used. Also, when the fields of a -particular branch are specified during object construction, the correct value -for the discriminator must be supplied at compile-time. - -Package level objects ---------------------- - -Every Nim module resides in a (nimble) package. An object type can be attached -to the package it resides in. If that is done, the type can be referenced from -other modules as an `incomplete`:idx: object type. This features allows to -break up recursive type dependencies accross module boundaries. Incomplete -object types are always passed ``byref`` and can only be used in pointer like -contexts (``var/ref/ptr IncompleteObject``) in general since the compiler does -not yet know the size of the object. To complete an incomplete object -the ``package`` pragma has to be used. ``package`` implies ``byref``. - -As long as a type ``T`` is incomplete ``sizeof(T)`` or "runtime type -information" for ``T`` is not available. - - -Example: - -.. code-block:: nim - - # module A (in an arbitrary package) - type - Pack.SomeObject = object ## declare as incomplete object of package 'Pack' - Triple = object - a, b, c: ref SomeObject ## pointers to incomplete objects are allowed - - ## Incomplete objects can be used as parameters: - proc myproc(x: SomeObject) = discard - - -.. code-block:: nim - - # module B (in package "Pack") - type - SomeObject* {.package.} = object ## Use 'package' to complete the object - s, t: string - x, y: int - - -Set type --------- - -.. include:: ../sets_fragment.txt - -Reference and pointer types ---------------------------- -References (similar to pointers in other programming languages) are a -way to introduce many-to-one relationships. This means different references can -point to and modify the same location in memory (also called `aliasing`:idx:). - -Nim distinguishes between `traced`:idx: and `untraced`:idx: references. -Untraced references are also called *pointers*. Traced references point to -objects of a garbage collected heap, untraced references point to -manually allocated objects or to objects somewhere else in memory. Thus -untraced references are *unsafe*. However for certain low-level operations -(accessing the hardware) untraced references are unavoidable. - -Traced references are declared with the **ref** keyword, untraced references -are declared with the **ptr** keyword. In general, a `ptr T` is implicitly -convertible to the `pointer` type. - -An empty subscript ``[]`` notation can be used to derefer a reference, -the ``addr`` procedure returns the address of an item. An address is always -an untraced reference. -Thus the usage of ``addr`` is an *unsafe* feature. - -The ``.`` (access a tuple/object field operator) -and ``[]`` (array/string/sequence index operator) operators perform implicit -dereferencing operations for reference types: - -.. code-block:: nim - - type - Node = ref NodeObj - NodeObj = object - le, ri: Node - data: int - - var - n: Node - new(n) - n.data = 9 - # no need to write n[].data; in fact n[].data is highly discouraged! - -Automatic dereferencing is also performed for the first argument of a routine -call. But currently this feature has to be only enabled -via ``{.experimental.}``: - -.. code-block:: nim - {.experimental.} - - proc depth(x: NodeObj): int = ... - - var - n: Node - new(n) - echo n.depth - # no need to write n[].depth either - - - -In order to simplify structural type checking, recursive tuples are not valid: - -.. code-block:: nim - # invalid recursion - type MyTuple = tuple[a: ref MyTuple] - -Likewise ``T = ref T`` is an invalid type. - -As a syntactical extension ``object`` types can be anonymous if -declared in a type section via the ``ref object`` or ``ptr object`` notations. -This feature is useful if an object should only gain reference semantics: - -.. code-block:: nim - - type - Node = ref object - le, ri: Node - data: int - - -To allocate a new traced object, the built-in procedure ``new`` has to be used. -To deal with untraced memory, the procedures ``alloc``, ``dealloc`` and -``realloc`` can be used. The documentation of the system module contains -further information. - -If a reference points to *nothing*, it has the value ``nil``. - -Special care has to be taken if an untraced object contains traced objects like -traced references, strings or sequences: in order to free everything properly, -the built-in procedure ``GCunref`` has to be called before freeing the untraced -memory manually: - -.. code-block:: nim - type - Data = tuple[x, y: int, s: string] - - # allocate memory for Data on the heap: - var d = cast[ptr Data](alloc0(sizeof(Data))) - - # create a new string on the garbage collected heap: - d.s = "abc" - - # tell the GC that the string is not needed anymore: - GCunref(d.s) - - # free the memory: - dealloc(d) - -Without the ``GCunref`` call the memory allocated for the ``d.s`` string would -never be freed. The example also demonstrates two important features for low -level programming: the ``sizeof`` proc returns the size of a type or value -in bytes. The ``cast`` operator can circumvent the type system: the compiler -is forced to treat the result of the ``alloc0`` call (which returns an untyped -pointer) as if it would have the type ``ptr Data``. Casting should only be -done if it is unavoidable: it breaks type safety and bugs can lead to -mysterious crashes. - -**Note**: The example only works because the memory is initialized to zero -(``alloc0`` instead of ``alloc`` does this): ``d.s`` is thus initialized to -``nil`` which the string assignment can handle. One needs to know low level -details like this when mixing garbage collected data with unmanaged memory. - -.. XXX finalizers for traced objects - - -Not nil annotation ------------------- - -All types for that ``nil`` is a valid value can be annotated to -exclude ``nil`` as a valid value with the ``not nil`` annotation: - -.. code-block:: nim - type - PObject = ref TObj not nil - TProc = (proc (x, y: int)) not nil - - proc p(x: PObject) = - echo "not nil" - - # compiler catches this: - p(nil) - - # and also this: - var x: PObject - p(x) - -The compiler ensures that every code path initializes variables which contain -non nilable pointers. The details of this analysis are still to be specified -here. - - -Memory regions --------------- - -The types ``ref`` and ``ptr`` can get an optional ``region`` annotation. -A region has to be an object type. - -Regions are very useful to separate user space and kernel memory in the -development of OS kernels: - -.. code-block:: nim - type - Kernel = object - Userspace = object - - var a: Kernel ptr Stat - var b: Userspace ptr Stat - - # the following does not compile as the pointer types are incompatible: - a = b - -As the example shows ``ptr`` can also be used as a binary -operator, ``region ptr T`` is a shortcut for ``ptr[region, T]``. - -In order to make generic code easier to write ``ptr T`` is a subtype -of ``ptr[R, T]`` for any ``R``. - -Furthermore the subtype relation of the region object types is lifted to -the pointer types: If ``A <: B`` then ``ptr[A, T] <: ptr[B, T]``. This can be -used to model subregions of memory. As a special typing rule ``ptr[R, T]`` is -not compatible to ``pointer`` to prevent the following from compiling: - -.. code-block:: nim - # from system - proc dealloc(p: pointer) - - # wrap some scripting language - type - PythonsHeap = object - PyObjectHeader = object - rc: int - typ: pointer - PyObject = ptr[PythonsHeap, PyObjectHeader] - - proc createPyObject(): PyObject {.importc: "...".} - proc destroyPyObject(x: PyObject) {.importc: "...".} - - var foo = createPyObject() - # type error here, how convenient: - dealloc(foo) - - -Future directions: - -* Memory regions might become available for ``string`` and ``seq`` too. -* Builtin regions like ``private``, ``global`` and ``local`` might be - useful for an OpenCL target. -* Builtin "regions" can model ``lent`` and ``unique`` pointers. -* An assignment operator can be attached to a region so that proper write - barriers can be generated. This would imply that the GC can be implemented - completely in user-space. - - -Procedural type ---------------- -A procedural type is internally a pointer to a procedure. ``nil`` is -an allowed value for variables of a procedural type. Nim uses procedural -types to achieve `functional`:idx: programming techniques. - -Examples: - -.. code-block:: nim - - proc printItem(x: int) = ... - - proc forEach(c: proc (x: int) {.cdecl.}) = - ... - - forEach(printItem) # this will NOT compile because calling conventions differ - - -.. code-block:: nim - - type - OnMouseMove = proc (x, y: int) {.closure.} - - proc onMouseMove(mouseX, mouseY: int) = - # has default calling convention - echo "x: ", mouseX, " y: ", mouseY - - proc setOnMouseMove(mouseMoveEvent: OnMouseMove) = discard - - # ok, 'onMouseMove' has the default calling convention, which is compatible - # to 'closure': - setOnMouseMove(onMouseMove) - - -A subtle issue with procedural types is that the calling convention of the -procedure influences the type compatibility: procedural types are only -compatible if they have the same calling convention. As a special extension, -a procedure of the calling convention ``nimcall`` can be passed to a parameter -that expects a proc of the calling convention ``closure``. - -Nim supports these `calling conventions`:idx:\: - -`nimcall`:idx: - is the default convention used for a Nim **proc**. It is the - same as ``fastcall``, but only for C compilers that support ``fastcall``. - -`closure`:idx: - is the default calling convention for a **procedural type** that lacks - any pragma annotations. It indicates that the procedure has a hidden - implicit parameter (an *environment*). Proc vars that have the calling - convention ``closure`` take up two machine words: One for the proc pointer - and another one for the pointer to implicitly passed environment. - -`stdcall`:idx: - This the stdcall convention as specified by Microsoft. The generated C - procedure is declared with the ``__stdcall`` keyword. - -`cdecl`:idx: - The cdecl convention means that a procedure shall use the same convention - as the C compiler. Under windows the generated C procedure is declared with - the ``__cdecl`` keyword. - -`safecall`:idx: - This is the safecall convention as specified by Microsoft. The generated C - procedure is declared with the ``__safecall`` keyword. The word *safe* - refers to the fact that all hardware registers shall be pushed to the - hardware stack. - -`inline`:idx: - The inline convention means the the caller should not call the procedure, - but inline its code directly. Note that Nim does not inline, but leaves - this to the C compiler; it generates ``__inline`` procedures. This is - only a hint for the compiler: it may completely ignore it and - it may inline procedures that are not marked as ``inline``. - -`fastcall`:idx: - Fastcall means different things to different C compilers. One gets whatever - the C ``__fastcall`` means. - -`syscall`:idx: - The syscall convention is the same as ``__syscall`` in C. It is used for - interrupts. - -`noconv`:idx: - The generated C code will not have any explicit calling convention and thus - use the C compiler's default calling convention. This is needed because - Nim's default calling convention for procedures is ``fastcall`` to - improve speed. - -Most calling conventions exist only for the Windows 32-bit platform. - -The default calling convention is ``nimcall``, unless it is an inner proc (a -proc inside of a proc). For an inner proc an analysis is performed whether it -accesses its environment. If it does so, it has the calling convention -``closure``, otherwise it has the calling convention ``nimcall``. - - -Distinct type -------------- - -A ``distinct`` type is new type derived from a `base type`:idx: that is -incompatible with its base type. In particular, it is an essential property -of a distinct type that it **does not** imply a subtype relation between it -and its base type. Explicit type conversions from a distinct type to its -base type and vice versa are allowed. - - -Modelling currencies -~~~~~~~~~~~~~~~~~~~~ - -A distinct type can be used to model different physical `units`:idx: with a -numerical base type, for example. The following example models currencies. - -Different currencies should not be mixed in monetary calculations. Distinct -types are a perfect tool to model different currencies: - -.. code-block:: nim - type - Dollar = distinct int - Euro = distinct int - - var - d: Dollar - e: Euro - - echo d + 12 - # Error: cannot add a number with no unit and a ``Dollar`` - -Unfortunately, ``d + 12.Dollar`` is not allowed either, -because ``+`` is defined for ``int`` (among others), not for ``Dollar``. So -a ``+`` for dollars needs to be defined: - -.. code-block:: - proc `+` (x, y: Dollar): Dollar = - result = Dollar(int(x) + int(y)) - -It does not make sense to multiply a dollar with a dollar, but with a -number without unit; and the same holds for division: - -.. code-block:: - proc `*` (x: Dollar, y: int): Dollar = - result = Dollar(int(x) * y) - - proc `*` (x: int, y: Dollar): Dollar = - result = Dollar(x * int(y)) - - proc `div` ... - -This quickly gets tedious. The implementations are trivial and the compiler -should not generate all this code only to optimize it away later - after all -``+`` for dollars should produce the same binary code as ``+`` for ints. -The pragma `borrow`:idx: has been designed to solve this problem; in principle -it generates the above trivial implementations: - -.. code-block:: nim - proc `*` (x: Dollar, y: int): Dollar {.borrow.} - proc `*` (x: int, y: Dollar): Dollar {.borrow.} - proc `div` (x: Dollar, y: int): Dollar {.borrow.} - -The ``borrow`` pragma makes the compiler use the same implementation as -the proc that deals with the distinct type's base type, so no code is -generated. - -But it seems all this boilerplate code needs to be repeated for the ``Euro`` -currency. This can be solved with templates_. - -.. code-block:: nim - template additive(typ: typedesc) = - proc `+` *(x, y: typ): typ {.borrow.} - proc `-` *(x, y: typ): typ {.borrow.} - - # unary operators: - proc `+` *(x: typ): typ {.borrow.} - proc `-` *(x: typ): typ {.borrow.} - - template multiplicative(typ, base: typedesc) = - proc `*` *(x: typ, y: base): typ {.borrow.} - proc `*` *(x: base, y: typ): typ {.borrow.} - proc `div` *(x: typ, y: base): typ {.borrow.} - proc `mod` *(x: typ, y: base): typ {.borrow.} - - template comparable(typ: typedesc) = - proc `<` * (x, y: typ): bool {.borrow.} - proc `<=` * (x, y: typ): bool {.borrow.} - proc `==` * (x, y: typ): bool {.borrow.} - - template defineCurrency(typ, base: untyped) = - type - typ* = distinct base - additive(typ) - multiplicative(typ, base) - comparable(typ) - - defineCurrency(Dollar, int) - defineCurrency(Euro, int) - - -The borrow pragma can also be used to annotate the distinct type to allow -certain builtin operations to be lifted: - -.. code-block:: nim - type - Foo = object - a, b: int - s: string - - Bar {.borrow: `.`.} = distinct Foo - - var bb: ref Bar - new bb - # field access now valid - bb.a = 90 - bb.s = "abc" - -Currently only the dot accessor can be borrowed in this way. - - -Avoiding SQL injection attacks -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -An SQL statement that is passed from Nim to an SQL database might be -modelled as a string. However, using string templates and filling in the -values is vulnerable to the famous `SQL injection attack`:idx:\: - -.. code-block:: nim - import strutils - - proc query(db: DbHandle, statement: string) = ... - - var - username: string - - db.query("SELECT FROM users WHERE name = '$1'" % username) - # Horrible security hole, but the compiler does not mind! - -This can be avoided by distinguishing strings that contain SQL from strings -that don't. Distinct types provide a means to introduce a new string type -``SQL`` that is incompatible with ``string``: - -.. code-block:: nim - type - SQL = distinct string - - proc query(db: DbHandle, statement: SQL) = ... - - var - username: string - - db.query("SELECT FROM users WHERE name = '$1'" % username) - # Error at compile time: `query` expects an SQL string! - - -It is an essential property of abstract types that they **do not** imply a -subtype relation between the abstract type and its base type. Explicit type -conversions from ``string`` to ``SQL`` are allowed: - -.. code-block:: nim - import strutils, sequtils - - proc properQuote(s: string): SQL = - # quotes a string properly for an SQL statement - return SQL(s) - - proc `%` (frmt: SQL, values: openarray[string]): SQL = - # quote each argument: - let v = values.mapIt(SQL, properQuote(it)) - # we need a temporary type for the type conversion :-( - type StrSeq = seq[string] - # call strutils.`%`: - result = SQL(string(frmt) % StrSeq(v)) - - db.query("SELECT FROM users WHERE name = '$1'".SQL % [username]) - -Now we have compile-time checking against SQL injection attacks. Since -``"".SQL`` is transformed to ``SQL("")`` no new syntax is needed for nice -looking ``SQL`` string literals. The hypothetical ``SQL`` type actually -exists in the library as the `TSqlQuery type <db_sqlite.html#TSqlQuery>`_ of -modules like `db_sqlite <db_sqlite.html>`_. - - -Void type ---------- - -The ``void`` type denotes the absence of any type. Parameters of -type ``void`` are treated as non-existent, ``void`` as a return type means that -the procedure does not return a value: - -.. code-block:: nim - proc nothing(x, y: void): void = - echo "ha" - - nothing() # writes "ha" to stdout - -The ``void`` type is particularly useful for generic code: - -.. code-block:: nim - proc callProc[T](p: proc (x: T), x: T) = - when T is void: - p() - else: - p(x) - - proc intProc(x: int) = discard - proc emptyProc() = discard - - callProc[int](intProc, 12) - callProc[void](emptyProc) - -However, a ``void`` type cannot be inferred in generic code: - -.. code-block:: nim - callProc(emptyProc) - # Error: type mismatch: got (proc ()) - # but expected one of: - # callProc(p: proc (T), x: T) - -The ``void`` type is only valid for parameters and return types; other symbols -cannot have the type ``void``. - - -Auto type ---------- - -The ``auto`` type can only be used for return types and parameters. For return -types it causes the compiler to infer the type from the routine body: - -.. code-block:: nim - proc returnsInt(): auto = 1984 - -For parameters it currently creates implicitly generic routines: - -.. code-block:: nim - proc foo(a, b: auto) = discard - -Is the same as: - -.. code-block:: nim - proc foo[T1, T2](a: T1, b: T2) = discard - -However later versions of the language might change this to mean "infer the -parameters' types from the body". Then the above ``foo`` would be rejected as -the parameters' types can not be inferred from an empty ``discard`` statement. diff --git a/doc/manual/var_t_return.rst b/doc/manual/var_t_return.rst new file mode 100644 index 000000000..b9ff1d892 --- /dev/null +++ b/doc/manual/var_t_return.rst @@ -0,0 +1,20 @@ +Memory safety for returning by ``var T`` is ensured by a simple borrowing +rule: If ``result`` does not refer to a location pointing to the heap +(that is in ``result = X`` the ``X`` involves a ``ptr`` or ``ref`` access) +then it has to be deviated by the routine's first parameter: + +.. code-block:: nim + proc forward[T](x: var T): var T = + result = x # ok, deviated from the first parameter. + + proc p(param: var int): var int = + var x: int + # we know 'forward' provides a view into the location deviated by + # its first argument 'x'. + result = forward(x) # Error: location is derived from ``x`` + # which is not p's first parameter and lives + # on the stack. + +In other words, the lifetime of what ``result`` points to is attached to the +lifetime of the first parameter and that is enough knowledge to verify +memory safety at the callsite. diff --git a/doc/mytest.cfg b/doc/mytest.cfg index be1118c46..1385e493d 100644 --- a/doc/mytest.cfg +++ b/doc/mytest.cfg @@ -3,6 +3,7 @@ [Common] cc=gcc # '=' and ':' are the same +--foo="bar" # '--cc' and 'cc' are the same, 'bar' and '"bar"' are the same --verbose [Windows] diff --git a/doc/nep1.rst b/doc/nep1.rst index d44265193..1ef8c3c24 100644 --- a/doc/nep1.rst +++ b/doc/nep1.rst @@ -60,7 +60,7 @@ Spacing and Whitespace Conventions Naming Conventions -------------------------- +------------------ Note: While the rules outlined below are the *current* naming conventions, these conventions have not always been in place. Previously, the naming @@ -134,6 +134,92 @@ changed in the future. as what they are: Real words. So it's ``parseUrl`` rather than ``parseURL``, ``checkHttpHeader`` instead of ``checkHTTPHeader`` etc. +- Operations like ``mitems`` or ``mpairs`` (or the now deprecated ``mget``) + that allow a *mutating view* into some data structure should start with an ``m``. +- When both in-place mutation and 'returns transformed copy' are available the latter + is a past participle of the former: + + - reverse and reversed in algorithm + - sort and sorted + - rotate and rotated + +- When the 'returns transformed copy' version already exists like ``strutils.replace`` + an in-place version should get an ``-In`` suffix (``replaceIn`` for this example). + + +The stdlib API is designed to be **easy to use** and consistent. Ease of use is +measured by the number of calls to achieve a concrete high level action. The +ultimate goal is that the programmer can *guess* a name. + +The library uses a simple naming scheme that makes use of common abbreviations +to keep the names short but meaningful. + + +------------------- ------------ -------------------------------------- +English word To use Notes +------------------- ------------ -------------------------------------- +initialize initT ``init`` is used to create a + value type ``T`` +new newP ``new`` is used to create a + reference type ``P`` +find find should return the position where + something was found; for a bool result + use ``contains`` +contains contains often short for ``find() >= 0`` +append add use ``add`` instead of ``append`` +compare cmp should return an int with the + ``< 0`` ``== 0`` or ``> 0`` semantics; + for a bool result use ``sameXYZ`` +put put, ``[]=`` consider overloading ``[]=`` for put +get get, ``[]`` consider overloading ``[]`` for get; + consider to not use ``get`` as a + prefix: ``len`` instead of ``getLen`` +length len also used for *number of elements* +size size, len size should refer to a byte size +capacity cap +memory mem implies a low-level operation +items items default iterator over a collection +pairs pairs iterator over (key, value) pairs +delete delete, del del is supposed to be faster than + delete, because it does not keep + the order; delete keeps the order +remove delete, del inconsistent right now +include incl +exclude excl +command cmd +execute exec +environment env +variable var +value value, val val is preferred, inconsistent right + now +executable exe +directory dir +path path path is the string "/usr/bin" (for + example), dir is the content of + "/usr/bin"; inconsistent right now +extension ext +separator sep +column col, column col is preferred, inconsistent right + now +application app +configuration cfg +message msg +argument arg +object obj +parameter param +operator opr +procedure proc +function func +coordinate coord +rectangle rect +point point +symbol sym +literal lit +string str +identifier ident +indentation indent +------------------- ------------ -------------------------------------- + Coding Conventions ------------------ @@ -169,7 +255,7 @@ Conventions for multi-line statements and expressions LongTupleA = tuple[wordyTupleMemberOne: int, wordyTupleMemberTwo: string, wordyTupleMemberThree: float] -- Similarly, any procedure and procedure type declarations that are longer# +- Similarly, any procedure and procedure type declarations that are longer than one line should do the same thing. .. code-block:: nim @@ -186,4 +272,4 @@ Conventions for multi-line statements and expressions .. code-block:: nim startProcess(nimExecutable, currentDirectory, compilerArguments - environment, processOptions) \ No newline at end of file + environment, processOptions) diff --git a/doc/nimc.rst b/doc/nimc.rst index e949df69c..4082b5378 100644 --- a/doc/nimc.rst +++ b/doc/nimc.rst @@ -68,6 +68,46 @@ User Some user defined warning. ========================== ============================================ +List of hints +------------- + +Each hint can be activated individually with ``--hint[NAME]:on|off`` or in a +``push`` pragma. + +========================== ============================================ +Name Description +========================== ============================================ +CC Shows when the C compiler is called. +CodeBegin +CodeEnd +CondTrue +Conf A config file was loaded. +ConvToBaseNotNeeded +ConvFromXtoItselfNotNeeded +Dependency +Exec Program is executed. +ExprAlwaysX +ExtendedContext +GCStats Dumps statistics about the Garbage Collector. +GlobalVar Shows global variables declarations. +LineTooLong Line exceeds the maximum length. +Link Linking phase. +Name +Path Search paths modifications. +Pattern +Performance +Processing Artifact being compiled. +QuitCalled +Source The source line that triggered a diagnostic + message. +StackTrace +Success, SuccessX Successful compilation of a library or a binary. +User +UserRaw +XDeclaredButNotUsed Unused symbols in the code. +========================== ============================================ + + Verbosity levels ---------------- @@ -103,6 +143,9 @@ which may be used in conjunction with the `compile time define pragmas<manual.html#implementation-specific-pragmas-compile-time-define-pragmas>`_ to override symbols during build time. +Compile time symbols are completely **case insensitive** and underscores are +ignored too. ``--define:FOO`` and ``--define:foo`` are identical. + Configuration files ------------------- @@ -114,8 +157,8 @@ passed as a command line argument to the compiler. The ``nim`` executable processes configuration files in the following directories (in this order; later files overwrite previous settings): -1) ``$nim/config/nim.cfg``, ``/etc/nim.cfg`` (UNIX) or ``%NIMROD%/config/nim.cfg`` (Windows). This file can be skipped with the ``--skipCfg`` command line option. -2) ``/home/$user/.config/nim.cfg`` (UNIX) or ``%APPDATA%/nim.cfg`` (Windows). This file can be skipped with the ``--skipUserCfg`` command line option. +1) ``$nim/config/nim.cfg``, ``/etc/nim/nim.cfg`` (UNIX) or ``%NIM%/config/nim.cfg`` (Windows). This file can be skipped with the ``--skipCfg`` command line option. +2) ``$HOME/.config/nim.cfg`` (POSIX) or ``%APPDATA%/nim.cfg`` (Windows). This file can be skipped with the ``--skipUserCfg`` command line option. 3) ``$parentDir/nim.cfg`` where ``$parentDir`` stands for any parent directory of the project file's path. These files can be skipped with the ``--skipParentCfg`` command line option. 4) ``$projectDir/nim.cfg`` where ``$projectDir`` stands for the project file's path. This file can be skipped with the ``--skipProjCfg`` command line option. 5) A project can also have a project specific configuration file named ``$project.nim.cfg`` that resides in the same directory as ``$project.nim``. This file can be skipped with the ``--skipProjCfg`` command line option. @@ -157,38 +200,28 @@ the first matching file is used. Generated C code directory -------------------------- The generated files that Nim produces all go into a subdirectory called -``nimcache`` in your project directory. This makes it easy to delete all +``nimcache``. Its full path is + +- ``$XDG_CACHE_HOME/nim/$projectname(_r|_d)`` or ``~/.cache/nim/$projectname(_r|_d)`` + on Posix +- ``$HOME/nimcache/$projectname(_r|_d)`` on Windows. + +The ``_r`` suffix is used for release builds, ``_d`` is for debug builds. + +This makes it easy to delete all generated files. Files generated in this directory follow a naming logic which you can read about in the `Nim Backend Integration document <backends.html#nimcache-naming-logic>`_. +The ``--nimcache`` +`compiler switch <nimc.html#command-line-switches>`_ can be used to +to change the ``nimcache`` directory. + However, the generated C code is not platform independent. C code generated for Linux does not compile on Windows, for instance. The comment on top of the C file lists the OS, CPU and CC the file has been compiled for. -Compilation cache -================= - -**Warning**: The compilation cache is still highly experimental! - -The ``nimcache`` directory may also contain so called `rod`:idx: -or `symbol files`:idx:. These files are pre-compiled modules that are used by -the compiler to perform `incremental compilation`:idx:. This means that only -modules that have changed since the last compilation (or the modules depending -on them etc.) are re-compiled. However, per default no symbol files are -generated; use the ``--symbolFiles:on`` command line switch to activate them. - -Unfortunately due to technical reasons the ``--symbolFiles:on`` needs -to *aggregate* some generated C code. This means that the resulting executable -might contain some cruft even when dead code elimination is turned on. So -the final release build should be done with ``--symbolFiles:off``. - -Due to the aggregation of C code it is also recommended that each project -resides in its own directory so that the generated ``nimcache`` directory -is not shared between different projects. - - Compiler Selection ================== @@ -228,6 +261,48 @@ configuration file should contain something like:: arm.linux.gcc.exe = "arm-linux-gcc" arm.linux.gcc.linkerexe = "arm-linux-gcc" +Cross compilation for Nintendo Switch +===================================== + +Simply add --os:nintendoswitch +to your usual ``nim c`` or ``nim cpp`` command and set the ``passC`` +and ``passL`` command line switches to something like: + +.. code-block:: console + nim c ... --passC="-I$DEVKITPRO/libnx/include" ... + --passL="-specs=$DEVKITPRO/libnx/switch.specs -L$DEVKITPRO/libnx/lib -lnx" + +or setup a nim.cfg file like so: + +.. code-block:: Nim + #nim.cfg + --passC="-I$DEVKITPRO/libnx/include" + --passL="-specs=$DEVKITPRO/libnx/switch.specs -L$DEVKITPRO/libnx/lib -lnx" + +The DevkitPro setup must be the same as the default with their new installer +`here for Mac/Linux <https://github.com/devkitPro/pacman/releases>`_ or +`here for Windows <https://github.com/devkitPro/installer/releases>`_. + +For example, with the above mentioned config:: + + nim c --os:nintendoswitch switchhomebrew.nim + +This will generate a file called ``switchhomebrew.elf`` which can then be turned into +an nro file with the ``elf2nro`` tool in the DevkitPro release. Examples can be found at +`the nim-libnx github repo <https://github.com/jyapayne/nim-libnx.git>`_ or you can use +`the switch builder tool <https://github.com/jyapayne/switch-builder.git>`_. + +There are a few things that don't work because the DevkitPro libraries don't support them. +They are: + +1. Waiting for a subprocess to finish. A subprocess can be started, but right + now it can't be waited on, which sort of makes subprocesses a bit hard to use +2. Dynamic calls. DevkitPro libraries have no dlopen/dlclose functions. +3. Command line parameters. It doesn't make sense to have these for a console + anyways, so no big deal here. +4. mqueue. Sadly there are no mqueue headers. +5. ucontext. No headers for these either. No coroutines for now :( +6. nl_types. No headers for this. DLL generation ============== @@ -254,40 +329,41 @@ The standard library supports a growing number of ``useX`` conditional defines affecting how some features are implemented. This section tries to give a complete list. -================== ========================================================= -Define Effect -================== ========================================================= -``release`` Turns off runtime checks and turns on the optimizer. -``useWinAnsi`` Modules like ``os`` and ``osproc`` use the Ansi versions - of the Windows API. The default build uses the Unicode - version. -``useFork`` Makes ``osproc`` use ``fork`` instead of ``posix_spawn``. -``useNimRtl`` Compile and link against ``nimrtl.dll``. -``useMalloc`` Makes Nim use C's `malloc`:idx: instead of Nim's - own memory manager, ableit prefixing each allocation with - its size to support clearing memory on reallocation. - This only works with ``gc:none``. -``useRealtimeGC`` Enables support of Nim's GC for *soft* realtime - systems. See the documentation of the `gc <gc.html>`_ - for further information. -``nodejs`` The JS target is actually ``node.js``. -``ssl`` Enables OpenSSL support for the sockets module. -``memProfiler`` Enables memory profiling for the native GC. -``uClibc`` Use uClibc instead of libc. (Relevant for Unix-like OSes) -``checkAbi`` When using types from C headers, add checks that compare - what's in the Nim file with what's in the C header - (requires a C compiler with _Static_assert support, like - any C11 compiler) -``tempDir`` This symbol takes a string as its value, like - ``--define:tempDir:/some/temp/path`` to override the - temporary directory returned by ``os.getTempDir()``. - The value **should** end with a directory separator - character. (Relevant for the Android platform) -``useShPath`` This symbol takes a string as its value, like - ``--define:useShPath:/opt/sh/bin/sh`` to override the - path for the ``sh`` binary, in cases where it is not - located in the default location ``/bin/sh`` -================== ========================================================= +====================== ========================================================= +Define Effect +====================== ========================================================= +``release`` Turns off runtime checks and turns on the optimizer. +``useWinAnsi`` Modules like ``os`` and ``osproc`` use the Ansi versions + of the Windows API. The default build uses the Unicode + version. +``useFork`` Makes ``osproc`` use ``fork`` instead of ``posix_spawn``. +``useNimRtl`` Compile and link against ``nimrtl.dll``. +``useMalloc`` Makes Nim use C's `malloc`:idx: instead of Nim's + own memory manager, ableit prefixing each allocation with + its size to support clearing memory on reallocation. + This only works with ``gc:none``. +``useRealtimeGC`` Enables support of Nim's GC for *soft* realtime + systems. See the documentation of the `gc <gc.html>`_ + for further information. +``nodejs`` The JS target is actually ``node.js``. +``ssl`` Enables OpenSSL support for the sockets module. +``memProfiler`` Enables memory profiling for the native GC. +``uClibc`` Use uClibc instead of libc. (Relevant for Unix-like OSes) +``checkAbi`` When using types from C headers, add checks that compare + what's in the Nim file with what's in the C header + (requires a C compiler with _Static_assert support, like + any C11 compiler) +``tempDir`` This symbol takes a string as its value, like + ``--define:tempDir:/some/temp/path`` to override the + temporary directory returned by ``os.getTempDir()``. + The value **should** end with a directory separator + character. (Relevant for the Android platform) +``useShPath`` This symbol takes a string as its value, like + ``--define:useShPath:/opt/sh/bin/sh`` to override the + path for the ``sh`` binary, in cases where it is not + located in the default location ``/bin/sh``. +``noSignalHandler`` Disable the crash handler from ``system.nim``. +====================== ========================================================= @@ -325,6 +401,46 @@ Debugger option The ``debugger`` option enables or disables the *Embedded Nim Debugger*. See the documentation of endb_ for further information. +Hot code reloading +------------------ +**Note:** At the moment hot code reloading is supported only in +JavaScript projects. + +The `hotCodeReloading`:idx: option enables special compilation mode where changes in +the code can be applied automatically to a running program. The code reloading +happens at the granularity of an individual module. When a module is reloaded, +Nim will preserve the state of all global variables which are initialized with +a standard variable declaration in the code. All other top level code will be +executed repeatedly on each reload. If you want to prevent this behavior, you +can guard a block of code with the ``once`` construct: + +.. code-block:: Nim + var settings = initTable[string, string]() + + once: + myInit() + + for k, v in loadSettings(): + settings[k] = v + +If you want to reset the state of a global variable on each reload, just +re-assign a value anywhere within the top-level code: + +.. code-block:: Nim + var lastReload: Time + + lastReload = now() + resetProgramState() + +**Known limitations:** In the JavaScript target, global variables using the +``codegenDecl`` pragma will be re-initialized on each reload. Please guard the +initialization with a `once` block to work-around this. + +**Usage in JavaScript projects:** + +Once your code is compiled for hot reloading, you can use a framework such +as `LiveReload <http://livereload.com/>` or `BrowserSync <https://browsersync.io/>` +to implement the actual reloading behavior in your project. Breakpoint pragma ----------------- @@ -399,7 +515,7 @@ target. For example, to generate code for an `AVR`:idx: processor use this command:: - nim c --cpu:avr --os:standalone --deadCodeElim:on --genScript x.nim + nim c --cpu:avr --os:standalone --genScript x.nim For the ``standalone`` target one needs to provide a file ``panicoverride.nim``. @@ -417,6 +533,16 @@ See the documentation of Nim's soft realtime `GC <gc.html>`_ for further information. +Signal handling in Nim +====================== + +The Nim programming language has no concept of Posix's signal handling +mechanisms. However, the standard library offers some rudimentary support +for signal handling, in particular, segmentation faults are turned into +fatal errors that produce a stack trace. This can be disabled with the +``-d:noSignalHandler`` switch. + + Debugging with Nim ================== diff --git a/doc/spawn.txt b/doc/spawn.txt index 522c94464..ab667ff48 100644 --- a/doc/spawn.txt +++ b/doc/spawn.txt @@ -25,7 +25,7 @@ Spawn statement A standalone ``spawn`` statement is a simple construct. It executes the passed expression on the thread pool and returns a `data flow variable`:idx: ``FlowVar[T]`` that can be read from. The reading with the ``^`` operator is -**blocking**. However, one can use ``awaitAny`` to wait on multiple flow +**blocking**. However, one can use ``blockUntilAny`` to wait on multiple flow variables at the same time: .. code-block:: nim @@ -36,10 +36,10 @@ variables at the same time: var responses = newSeq[FlowVarBase](3) for i in 0..2: responses[i] = spawn tellServer(Update, "key", "value") - var index = awaitAny(responses) + var index = blockUntilAny(responses) assert index >= 0 responses.del(index) - discard awaitAny(responses) + discard blockUntilAny(responses) Data flow variables ensure that no data races are possible. Due to technical limitations not every type ``T`` is possible in diff --git a/doc/tools.txt b/doc/tools.txt index 7c9aed7ad..070deb806 100644 --- a/doc/tools.txt +++ b/doc/tools.txt @@ -27,7 +27,3 @@ The standard distribution ships with the following tools: - | `estp <estp.html>`_ | Nim's slow platform independent embedded stack trace profiler. - -- | `nimfix <nimfix.html>`_ - | Nimfix is a tool to help you upgrade from Nimrod (<= version 0.9.6) to - Nim (=> version 0.10.0). diff --git a/doc/tut1.rst b/doc/tut1.rst index cbfef183e..a7f1b741a 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -208,7 +208,8 @@ Note that declaring multiple variables with a single assignment which calls a procedure can have unexpected results: the compiler will *unroll* the assignments and end up calling the procedure several times. If the result of the procedure depends on side effects, your variables may end up having -different values! For safety use only constant values. +different values! For safety use side-effect free procedures if making multiple +assignments. Constants @@ -570,8 +571,8 @@ With parenthesis and semicolons ``(;)`` you can use statements where only an expression is allowed: .. code-block:: nim - # computes fac(4) at compile time: :test: "nim c $1" + # computes fac(4) at compile time: const fac4 = (var x = 1; for i in 1..4: x *= i; x) @@ -642,7 +643,7 @@ initialisation. Parameters ---------- -Parameters are constant in the procedure body. By default, their value cannot be +Parameters are immutable in the procedure body. By default, their value cannot be changed because this allows the compiler to implement parameter passing in the most efficient way. If a mutable variable is needed inside the procedure, it has to be declared with ``var`` in the procedure body. Shadowing the parameter name @@ -890,7 +891,7 @@ important differences: future version of the compiler.) However, you can also use a ``closure`` iterator to get a different set of -restrictions. See `first class iterators <manual.html#first-class-iterators>`_ +restrictions. See `first class iterators <manual.html#iterators-and-the-for-statement-first-class-iterators>`_ for details. Iterators can have the same name and parameters as a proc, since essentially they have their own namespaces. Therefore it is common practice to wrap iterators in procs of the same name which accumulate the result of the @@ -944,12 +945,8 @@ String variables are **mutable**, so appending to a string is possible, and quite efficient. Strings in Nim are both zero-terminated and have a length field. A string's length can be retrieved with the builtin ``len`` procedure; the length never counts the terminating zero. Accessing the -terminating zero is not an error and often leads to simpler code: - -.. code-block:: nim - if s[i] == 'a' and s[i+1] == 'b': - # no need to check whether ``i < len(s)``! - ... +terminating zero is an error, it only exists so that a Nim string can be converted +to a ``cstring`` without doing a copy. The assignment operator for strings copies the string. You can use the ``&`` operator to concatenate strings and ``add`` to append to a string. @@ -960,11 +957,7 @@ enforced. For example, when reading strings from binary files, they are merely a sequence of bytes. The index operation ``s[i]`` means the i-th *char* of ``s``, not the i-th *unichar*. -String variables are initialized with a special value, called ``nil``. However, -most string operations cannot deal with ``nil`` (leading to an exception being -raised) for performance reasons. It is best to use empty strings ``""`` -rather than ``nil`` as the *empty* value. But ``""`` often creates a string -object on the heap, so there is a trade-off to be made here. +A string variable is initialized with the empty string ``""``. Integers @@ -1119,21 +1112,12 @@ proc can convert it to its underlying integer value. For better interfacing to other programming languages, the symbols of enum types can be assigned an explicit ordinal value. However, the ordinal values -must be in ascending order. A symbol whose ordinal value is not -explicitly given is assigned the value of the previous symbol + 1. - -An explicit ordered enum can have *holes*: - -.. code-block:: nim - :test: "nim c $1" - type - MyEnum = enum - a = 2, b = 4, c = 89 +must be in ascending order. Ordinal types ------------- -Enumerations without holes, integer types, ``char`` and ``bool`` (and +Enumerations, integer types, ``char`` and ``bool`` (and subranges) are called ordinal types. Ordinal types have quite a few special operations: @@ -1309,11 +1293,7 @@ Example: x: seq[int] # a reference to a sequence of integers x = @[1, 2, 3, 4, 5, 6] # the @ turns the array into a sequence allocated on the heap -Sequence variables are initialized with ``nil``. However, most sequence -operations cannot deal with ``nil`` (leading to an exception being -raised) for performance reasons. Thus one should use empty sequences ``@[]`` -rather than ``nil`` as the *empty* value. But ``@[]`` creates a sequence -object on the heap, so there is a trade-off to be made here. +Sequence variables are initialized with ``@[]``. The ``for`` statement can be used with one or two variables when used with a sequence. When you use the one variable form, the variable will hold the value @@ -1355,11 +1335,9 @@ type does not matter. .. code-block:: nim :test: "nim c $1" var - fruits: seq[string] # reference to a sequence of strings that is initialized with 'nil' + fruits: seq[string] # reference to a sequence of strings that is initialized with '@[]' capitals: array[3, string] # array of strings with a fixed size - fruits = @[] # creates an empty sequence on the heap that will be referenced by 'fruits' - capitals = ["New York", "London", "Berlin"] # array 'capitals' allows assignment of only three elements fruits.add("Banana") # sequence 'fruits' is dynamically expandable during runtime fruits.add("Mango") @@ -1691,7 +1669,7 @@ rules apply: write(stdout, x(3)) # no error: A.x is called write(stdout, x("")) # no error: B.x is called - proc x*(a: int): string = nil + proc x*(a: int): string = discard write(stdout, x(3)) # ambiguous: which `x` is to call? diff --git a/doc/tut2.rst b/doc/tut2.rst index 91cb52834..39e3bd89a 100644 --- a/doc/tut2.rst +++ b/doc/tut2.rst @@ -618,9 +618,9 @@ Turning the ``log`` proc into a template solves this problem: log("x has the value: " & $x) The parameters' types can be ordinary types or the meta types ``untyped``, -``typed``, or ``typedesc``. -``typedesc`` stands for *type description*, and ``untyped`` means symbol lookups and -type resolution is not performed before the expression is passed to the template. +``typed``, or ``type``. ``type`` suggests that only a type symbol may be given +as an argument, and ``untyped`` means symbol lookups and type resolution is not +performed before the expression is passed to the template. If the template has no explicit return type, ``void`` is used for consistency with procs and methods. diff --git a/examples/allany.nim b/examples/allany.nim index 6ff055aa6..8a5ab81f0 100644 --- a/examples/allany.nim +++ b/examples/allany.nim @@ -1,22 +1,20 @@ # All and any template all(container, cond: untyped): bool = - block: - var result = true - for it in items(container): - if not cond(it): - result = false - break - result + var result = true + for it in items(container): + if not cond(it): + result = false + break + result template any(container, cond: untyped): bool = - block: - var result = false - for it in items(container): - if cond(it): - result = true - break - result + var result = false + for it in items(container): + if cond(it): + result = true + break + result if all("mystring", {'a'..'z'}.contains) and any("myohmy", 'y'.`==`): echo "works" diff --git a/examples/c++iface/irrlichtex.nim b/examples/c++iface/irrlichtex.nim deleted file mode 100644 index 373a78ce7..000000000 --- a/examples/c++iface/irrlichtex.nim +++ /dev/null @@ -1,114 +0,0 @@ -# Horrible example of how to interface with a C++ engine ... ;-) - -{.link: "/usr/lib/libIrrlicht.so".} - -{.emit: """ -using namespace irr; -using namespace core; -using namespace scene; -using namespace video; -using namespace io; -using namespace gui; -""".} - -const - irr = "<irrlicht/irrlicht.h>" - -type - TDimension2d {.final, header: irr, importc: "dimension2d".} = object - Tvector3df {.final, header: irr, importc: "vector3df".} = object - TColor {.final, header: irr, importc: "SColor".} = object - - TIrrlichtDevice {.final, header: irr, importc: "IrrlichtDevice".} = object - TIVideoDriver {.final, header: irr, importc: "IVideoDriver".} = object - TISceneManager {.final, header: irr, importc: "ISceneManager".} = object - TIGUIEnvironment {.final, header: irr, importc: "IGUIEnvironment".} = object - TIAnimatedMesh {.final, header: irr, importc: "IAnimatedMesh".} = object - TIAnimatedMeshSceneNode {.final, header: irr, - importc: "IAnimatedMeshSceneNode".} = object - TITexture {.final, header: irr, importc: "ITexture".} = object - - PIrrlichtDevice = ptr TIrrlichtDevice - PIVideoDriver = ptr TIVideoDriver - PISceneManager = ptr TISceneManager - PIGUIEnvironment = ptr TIGUIEnvironment - PIAnimatedMesh = ptr TIAnimatedMesh - PIAnimatedMeshSceneNode = ptr TIAnimatedMeshSceneNode - PITexture = ptr TITexture - -proc dimension2d(x, y: cint): TDimension2d {. - header: irr, importc: "dimension2d<u32>".} -proc vector3df(x,y,z: cint): Tvector3df {. - header: irr, importc: "vector3df".} -proc SColor(r,g,b,a: cint): TColor {. - header: irr, importc: "SColor".} - -proc createDevice(): PIrrlichtDevice {. - header: irr, importc: "createDevice".} -proc run(device: PIrrlichtDevice): bool {. - header: irr, importcpp: "run".} - -proc getVideoDriver(dev: PIrrlichtDevice): PIVideoDriver {. - header: irr, importcpp: "getVideoDriver".} -proc getSceneManager(dev: PIrrlichtDevice): PISceneManager {. - header: irr, importcpp: "getSceneManager".} -proc getGUIEnvironment(dev: PIrrlichtDevice): PIGUIEnvironment {. - header: irr, importcpp: "getGUIEnvironment".} - -proc getMesh(smgr: PISceneManager, path: cstring): PIAnimatedMesh {. - header: irr, importcpp: "getMesh".} - -proc drawAll(smgr: PISceneManager) {. - header: irr, importcpp: "drawAll".} -proc drawAll(guienv: PIGUIEnvironment) {. - header: irr, importcpp: "drawAll".} - -proc drop(dev: PIrrlichtDevice) {. - header: irr, importcpp: "drop".} - -proc getTexture(driver: PIVideoDriver, path: cstring): PITexture {. - header: irr, importcpp: "getTexture".} -proc endScene(driver: PIVideoDriver) {. - header: irr, importcpp: "endScene".} -proc beginScene(driver: PIVideoDriver, a, b: bool, c: TColor) {. - header: irr, importcpp: "beginScene".} - -proc addAnimatedMeshSceneNode( - smgr: PISceneManager, mesh: PIAnimatedMesh): PIAnimatedMeshSceneNode {. - header: irr, importcpp: "addAnimatedMeshSceneNode".} - -proc setMaterialTexture(n: PIAnimatedMeshSceneNode, x: cint, t: PITexture) {. - header: irr, importcpp: "setMaterialTexture".} -proc addCameraSceneNode(smgr: PISceneManager, x: cint, a, b: TVector3df) {. - header: irr, importcpp: "addCameraSceneNode".} - - -var device = createDevice() -if device == nil: quit "device is nil" - -var driver = device.getVideoDriver() -var smgr = device.getSceneManager() -var guienv = device.getGUIEnvironment() - -var mesh = smgr.getMesh("/home/andreas/download/irrlicht-1.7.2/media/sydney.md2") -if mesh == nil: - device.drop() - quit "no mesh!" - -var node = smgr.addAnimatedMeshSceneNode(mesh) - -if node != nil: - #node->setMaterialFlag(EMF_LIGHTING, false) - #node->setMD2Animation(scene::EMAT_STAND) - node.setMaterialTexture(0, - driver.getTexture( - "/home/andreas/download/irrlicht-1.7.2/media/media/sydney.bmp")) - -smgr.addCameraSceneNode(0, vector3df(0,30,-40), vector3df(0,5,0)) -while device.run(): - driver.beginScene(true, true, SColor(255,100,101,140)) - smgr.drawAll() - guienv.drawAll() - driver.endScene() -device.drop() - diff --git a/examples/cgi/cgi_server.py b/examples/cgi/cgi_server.py deleted file mode 100644 index 1907515e8..000000000 --- a/examples/cgi/cgi_server.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python -import BaseHTTPServer -import CGIHTTPServer - -server = BaseHTTPServer.HTTPServer -handler = CGIHTTPServer.CGIHTTPRequestHandler -server_address = ('localhost', 8008) -handler.cgi_directories = ['/'] - -httpd = server(server_address, handler) -httpd.serve_forever() diff --git a/examples/cgi/cgi_stacktrace.nim b/examples/cgi/cgi_stacktrace.nim deleted file mode 100644 index e9f2f567c..000000000 --- a/examples/cgi/cgi_stacktrace.nim +++ /dev/null @@ -1,5 +0,0 @@ -import cgi -cgi.setStackTraceStdout() - -var a: string = nil -a.add "foobar" diff --git a/examples/cgi/example.nim b/examples/cgi/example.nim deleted file mode 100644 index 761197cdb..000000000 --- a/examples/cgi/example.nim +++ /dev/null @@ -1,7 +0,0 @@ -import cgi - -write(stdout, "Content-type: text/html\n\n") -write(stdout, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n") -write(stdout, "<html><head><title>Test</title></head><body>\n") -write(stdout, "Hello!") -writeLine(stdout, "</body></html>") diff --git a/examples/cgiex.nim b/examples/cgiex.nim deleted file mode 100644 index fb55a731a..000000000 --- a/examples/cgiex.nim +++ /dev/null @@ -1,12 +0,0 @@ -# Test/show CGI module -import strtabs, cgi - -var myData = readData() -validateData(myData, "name", "password") -writeContentType() - -write(stdout, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n") -write(stdout, "<html><head><title>Test</title></head><body>\n") -writeLine(stdout, "name: " & myData["name"]) -writeLine(stdout, "password: " & myData["password"]) -writeLine(stdout, "</body></html>") diff --git a/examples/cross_calculator/.gitignore b/examples/cross_calculator/.gitignore deleted file mode 100644 index e521bf338..000000000 --- a/examples/cross_calculator/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Android specific absolute paths. -android/bin/ -android/gen/ -android/jni/backend-jni.h -android/libs/ -android/local.properties -android/obj/ -android/tags -# iOS specific absolute paths -ios/resources/ui/*.m -ios/tags - diff --git a/examples/cross_calculator/android/AndroidManifest.xml b/examples/cross_calculator/android/AndroidManifest.xml deleted file mode 100644 index 05b96fb50..000000000 --- a/examples/cross_calculator/android/AndroidManifest.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.github.nimrod.crosscalculator" - android:versionCode="1" - android:versionName="1.0"> - - <uses-sdk android:minSdkVersion="3" /> - - <application android:label="@string/app_name" android:debuggable="true"> - <activity android:name=".CrossCalculator" - android:label="@string/app_name"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/examples/cross_calculator/android/build.xml b/examples/cross_calculator/android/build.xml deleted file mode 100644 index d7c88432a..000000000 --- a/examples/cross_calculator/android/build.xml +++ /dev/null @@ -1,92 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project name="CrossCalculator" default="help"> - - <!-- The local.properties file is created and updated by the 'android' tool. - It contains the path to the SDK. It should *NOT* be checked into - Version Control Systems. --> - <property file="local.properties" /> - - <!-- The ant.properties file can be created by you. It is only edited by the - 'android' tool to add properties to it. - This is the place to change some Ant specific build properties. - Here are some properties you may want to change/update: - - source.dir - The name of the source directory. Default is 'src'. - out.dir - The name of the output directory. Default is 'bin'. - - For other overridable properties, look at the beginning of the rules - files in the SDK, at tools/ant/build.xml - - Properties related to the SDK location or the project target should - be updated using the 'android' tool with the 'update' action. - - This file is an integral part of the build system for your - application and should be checked into Version Control Systems. - - --> - <property file="ant.properties" /> - - <!-- if sdk.dir was not set from one of the property file, then - get it from the ANDROID_HOME env var. - This must be done before we load project.properties since - the proguard config can use sdk.dir --> - <property environment="env" /> - <condition property="sdk.dir" value="${env.ANDROID_HOME}"> - <isset property="env.ANDROID_HOME" /> - </condition> - - <!-- The project.properties file is created and updated by the 'android' - tool, as well as ADT. - - This contains project specific properties such as project target, and library - dependencies. Lower level build properties are stored in ant.properties - (or in .classpath for Eclipse projects). - - This file is an integral part of the build system for your - application and should be checked into Version Control Systems. --> - <loadproperties srcFile="project.properties" /> - - <!-- quick check on sdk.dir --> - <fail - message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." - unless="sdk.dir" - /> - - <!-- - Import per project custom build rules if present at the root of the project. - This is the place to put custom intermediary targets such as: - -pre-build - -pre-compile - -post-compile (This is typically used for code obfuscation. - Compiled code location: ${out.classes.absolute.dir} - If this is not done in place, override ${out.dex.input.absolute.dir}) - -post-package - -post-build - -pre-clean - --> - <import file="custom_rules.xml" optional="true" /> - - <!-- Import the actual build file. - - To customize existing targets, there are two options: - - Customize only one target: - - copy/paste the target into this file, *before* the - <import> task. - - customize it to your needs. - - Customize the whole content of build.xml - - copy/paste the content of the rules files (minus the top node) - into this file, replacing the <import> task. - - customize to your needs. - - *********************** - ****** IMPORTANT ****** - *********************** - In all cases you must update the value of version-tag below to read 'custom' instead of an integer, - in order to avoid having your file be overridden by tools such as "android update project" - --> - <!-- version-tag: 1 --> - <import file="${sdk.dir}/tools/ant/build.xml" /> - -</project> diff --git a/examples/cross_calculator/android/custom_rules.xml b/examples/cross_calculator/android/custom_rules.xml deleted file mode 100644 index 91783a50e..000000000 --- a/examples/cross_calculator/android/custom_rules.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project name="custom_rules" default="debug"> - <target name="nim"> - <exec executable="scripts/nimbuild.sh" failonerror="true"> - </exec> - </target> - <target name="jni"> - <exec executable="scripts/jnibuild.sh" failonerror="true"> - </exec> - </target> - <target name="ndk"> - <exec executable="ndk-build" failonerror="true"> - </exec> - </target> - <target name="-post-compile" depends="nim, jni, ndk"> - </target> -</project> diff --git a/examples/cross_calculator/android/jni/Android.mk b/examples/cross_calculator/android/jni/Android.mk deleted file mode 100644 index c1a0feac3..000000000 --- a/examples/cross_calculator/android/jni/Android.mk +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (C) 2009 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_MODULE := nimrod -LOCAL_SRC_FILES := nimcache/backend.c nimcache/system.c -LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/nimcache - -include $(BUILD_STATIC_LIBRARY) - -include $(CLEAR_VARS) - -LOCAL_MODULE := backend-jni -LOCAL_SRC_FILES := backend-jni.c -LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog -LOCAL_STATIC_LIBRARIES := nimrod - -include $(BUILD_SHARED_LIBRARY) diff --git a/examples/cross_calculator/android/jni/backend-jni.c b/examples/cross_calculator/android/jni/backend-jni.c deleted file mode 100644 index 3d65458ec..000000000 --- a/examples/cross_calculator/android/jni/backend-jni.c +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -#include <android/log.h> -#include <string.h> -#include "backend-jni.h" -#include "backend.h" - -#define TAG "backend-jni.c" - -jint JNICALL Java_com_github_nimrod_crosscalculator_CrossCalculator_myAdd - (JNIEnv *env, jobject thiz, jint a, jint b) -{ - char buf[256]; - const jint ret = myAdd(a, b); - // Using logging from inside the native bridge to log-debug. - sprintf(buf, "a %d + b %d = ret %d", a, b, ret); - __android_log_write(ANDROID_LOG_DEBUG, TAG, buf); - return ret; -} - -void JNICALL Java_com_github_nimrod_crosscalculator_CrossCalculator_initNimMain - (JNIEnv *env, jclass thiz) -{ - NimMain(); - __android_log_write(ANDROID_LOG_DEBUG, TAG, "Nimrod initialised"); -} - -// vim:tabstop=2 shiftwidth=2 syntax=c diff --git a/examples/cross_calculator/android/project.properties b/examples/cross_calculator/android/project.properties deleted file mode 100644 index 9fb894d9b..000000000 --- a/examples/cross_calculator/android/project.properties +++ /dev/null @@ -1,14 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-3 diff --git a/examples/cross_calculator/android/readme.txt b/examples/cross_calculator/android/readme.txt deleted file mode 100644 index 96bd403ca..000000000 --- a/examples/cross_calculator/android/readme.txt +++ /dev/null @@ -1,24 +0,0 @@ -In this directory you will find the Android platform cross-calculator sample. - -Due to the nature of Android being java and Nim generating C code, the build -process is slightly more complex because jni code has to be written to bridge -both languages. In a distant future it may be possible for Nim to generate -the whole jni bridge, but for the moment this is manual work. - -For the jni bridge to work first the java code is compiled with the Nim code -just declared as a native method which will be resolved at runtime. The scripts -nimbuild.sh and jnibuild.sh are in charge of building the Nim code and -generating the jni bridge from the java code respectively. Finally, the -ndk-build command from the android ndk tools has to be run to build the binary -library which will be installed along the final apk. - -All these steps are wrapped in the ant build script through the customization -of the -post-compile rule. If you have the android ndk tools installed and you -modify scripts/nimbuild.sh to point to the directory where you have Nim -installed on your system, you can simply run "ant debug" to build everything. - -Once the apk is built you can install it on your device or emulator with the -command "adb install bin/CrossCalculator-debug.apk". - -This example runs against the Android level 3 API, meaning devices from -Android 1.5 and above should be able to run the generated binary. diff --git a/examples/cross_calculator/android/res/layout/cross_calculator.xml b/examples/cross_calculator/android/res/layout/cross_calculator.xml deleted file mode 100644 index 11531334c..000000000 --- a/examples/cross_calculator/android/res/layout/cross_calculator.xml +++ /dev/null @@ -1,72 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/RelativeLayout1" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:orientation="vertical" > - - <TextView - android:id="@+id/title" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" - android:layout_centerHorizontal="true" - android:text="Crossplatform Nimrod calculator" - android:textSize="20dip" > - - </TextView> - - <TextView - android:id="@+id/value_a" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_below="@+id/title" - android:text="Value A: " > - </TextView> - - <EditText - android:id="@+id/edit_text_a" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_below="@+id/title" - android:ems="10" - android:inputType="number" /> - - <TextView - android:id="@+id/value_b" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_below="@+id/edit_text_a" - android:text="Value B: " > - </TextView> - - <EditText - android:id="@+id/edit_text_b" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_below="@+id/edit_text_a" - android:ems="10" - android:inputType="number" /> - - <Button - android:id="@+id/add_button" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_below="@+id/edit_text_b" - android:scrollbarAlwaysDrawVerticalTrack="false" - android:selectAllOnFocus="false" - android:text="Add!" - android:visibility="visible" /> - - <TextView - android:id="@+id/result_text" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_below="@+id/add_button" /> - -</RelativeLayout> diff --git a/examples/cross_calculator/android/res/values/strings.xml b/examples/cross_calculator/android/res/values/strings.xml deleted file mode 100644 index 05cd3ac93..000000000 --- a/examples/cross_calculator/android/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <string name="app_name">CrossCalculator</string> -</resources> diff --git a/examples/cross_calculator/android/scripts/jnibuild.sh b/examples/cross_calculator/android/scripts/jnibuild.sh deleted file mode 100644 index 8b61f20f7..000000000 --- a/examples/cross_calculator/android/scripts/jnibuild.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -# Force errors to fail script. -set -e - -# If we are running from inside the scripts subdir, get out. -if [ ! -d src ] -then - cd .. -fi - -# Ok, are we out now? -if [ -d src ] -then - javah -classpath bin/classes \ - -o jni/backend-jni.h \ - com.github.nimrod.crosscalculator.CrossCalculator -else - echo "Uh oh, bin/classes directory not found?" - echo "Try compiling your java code, or opening in eclipse." - exit 1 -fi diff --git a/examples/cross_calculator/android/scripts/nimbuild.sh b/examples/cross_calculator/android/scripts/nimbuild.sh deleted file mode 100644 index 97130b8dd..000000000 --- a/examples/cross_calculator/android/scripts/nimbuild.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh - -# Set this to the local or full path of your nimrod compiler -PATH_TO_NIMROD=~/project/nimrod/bin/nimrod -# Set this to the location of the nimbase.h file so -# the script can update it if it changes. -PATH_TO_NIMBASE=~/project/nimrod/lib/nimbase.h - -# Force errors to fail script. -set -e - -# If we are running from inside the scripts subdir, get out. -if [ ! -d src ] -then - cd .. -fi - -DEST_NIMBASE=jni/nimcache/nimbase.h - -# Ok, are we out now? -if [ -d src ] -then - $PATH_TO_NIMROD c --noMain --app:lib \ - --nimcache:jni/nimcache --cpu:arm --os:linux \ - --compileOnly --header ../nimrod_backend/*.nim - if [ "${PATH_TO_NIMBASE}" -nt "${DEST_NIMBASE}" ] - then - echo "Updating nimbase.h" - cp "${PATH_TO_NIMBASE}" "${DEST_NIMBASE}" - fi -else - echo "Uh oh, src directory not found?" - exit 1 -fi diff --git a/examples/cross_calculator/android/scripts/tags.sh b/examples/cross_calculator/android/scripts/tags.sh deleted file mode 100644 index 95507064f..000000000 --- a/examples/cross_calculator/android/scripts/tags.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -if [ ! -d src ] -then - cd .. -fi - -if [ -d src ] -then - ~/bin/objctags -R \ - jni \ - src -fi diff --git a/examples/cross_calculator/android/src/com/github/nimrod/crosscalculator/CrossCalculator.java b/examples/cross_calculator/android/src/com/github/nimrod/crosscalculator/CrossCalculator.java deleted file mode 100644 index df2eed5ea..000000000 --- a/examples/cross_calculator/android/src/com/github/nimrod/crosscalculator/CrossCalculator.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.github.nimrod.crosscalculator; - -import android.app.Activity; -import android.os.Bundle; -import android.util.Log; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; - -public class CrossCalculator extends Activity -{ - private static final String TAG = "CrossCalculator"; - private TextView result_text; - private EditText edit_text_a, edit_text_b; - /** Called when the activity is first created. */ - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.cross_calculator); - - final Button button = (Button)findViewById(R.id.add_button); - button.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { addButtonClicked(); } }); - - result_text = (TextView)findViewById(R.id.result_text); - edit_text_a = (EditText)findViewById(R.id.edit_text_a); - edit_text_b = (EditText)findViewById(R.id.edit_text_b); - } - - /** Handles clicks on the addition button. - * Reads the values form the input fields and performs the calculation. - */ - private void addButtonClicked() - { - int a = 0, b = 0; - String errors = ""; - final String a_text = edit_text_a.getText().toString(); - final String b_text = edit_text_b.getText().toString(); - try { - a = Integer.valueOf(a_text, 10); - } catch (NumberFormatException e) { - errors += "Can't parse a value '" + a_text + "'. "; - } - try { - b = Integer.valueOf(b_text, 10); - } catch (NumberFormatException e) { - errors += "Can't parse b value '" + b_text + "'"; - } - final int c = myAdd(a, b); - result_text.setText("myAdd(" + a + ", " + b + ") = " + c); - - if (errors.length() > 0) { - Log.e(TAG, errors); - Toast.makeText(this, errors, Toast.LENGTH_SHORT).show(); - } - } - - /* A native method that is implemented by the - * 'backend-jni' native library, which is packaged - * with this application. Adds to integers. - */ - public native int myAdd(int a, int b); - - /* A native method used to initialise Nimrod. - */ - static public native void initNimMain(); - - /* this is used to load the 'backend-jni' library on application - * startup. The library has already been unpacked into - * /data/data/com.github.nimrod.backendjni/lib/libbackend-jni.so at - * installation time by the package manager. - */ - static { - System.loadLibrary("backend-jni"); - initNimMain(); - } -} diff --git a/examples/cross_calculator/ios/cross-calculator.xcodeproj/project.pbxproj b/examples/cross_calculator/ios/cross-calculator.xcodeproj/project.pbxproj deleted file mode 100644 index 71cebc18a..000000000 --- a/examples/cross_calculator/ios/cross-calculator.xcodeproj/project.pbxproj +++ /dev/null @@ -1,337 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - D531422A15BC8611005EFF20 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D531422915BC8611005EFF20 /* UIKit.framework */; }; - D531422C15BC8611005EFF20 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D531422B15BC8611005EFF20 /* Foundation.framework */; }; - D531422E15BC8611005EFF20 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D531422D15BC8611005EFF20 /* CoreGraphics.framework */; }; - D531424D15BC87B6005EFF20 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D531424A15BC87B6005EFF20 /* AppDelegate.m */; }; - D531424E15BC87B6005EFF20 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D531424B15BC87B6005EFF20 /* main.m */; }; - D531427215BC94B1005EFF20 /* backend.m in Sources */ = {isa = PBXBuildFile; fileRef = D531426F15BC94B1005EFF20 /* backend.m */; }; - D531427415BC94B1005EFF20 /* stdlib_system.m in Sources */ = {isa = PBXBuildFile; fileRef = D531427115BC94B1005EFF20 /* stdlib_system.m */; }; - D5B6F94815FA8D4C0084A85B /* NRViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D5B6F94615FA8D4C0084A85B /* NRViewController.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - D531422515BC8611005EFF20 /* cross-calculator.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "cross-calculator.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - D531422915BC8611005EFF20 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; - D531422B15BC8611005EFF20 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - D531422D15BC8611005EFF20 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; - D531424715BC87A5005EFF20 /* cross-calculator-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "cross-calculator-Info.plist"; path = "resources/plist/cross-calculator-Info.plist"; sourceTree = "<group>"; }; - D531424915BC87B6005EFF20 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = src/AppDelegate.h; sourceTree = "<group>"; }; - D531424A15BC87B6005EFF20 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = src/AppDelegate.m; sourceTree = "<group>"; }; - D531424B15BC87B6005EFF20 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = src/main.m; sourceTree = "<group>"; }; - D531424C15BC87B6005EFF20 /* cross-calculator-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "cross-calculator-Prefix.pch"; path = "src/cross-calculator-Prefix.pch"; sourceTree = "<group>"; }; - D531426715BC91EF005EFF20 /* tags.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; name = tags.sh; path = scripts/tags.sh; sourceTree = "<group>"; }; - D531426815BC91EF005EFF20 /* xcode_prebuild.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; name = xcode_prebuild.sh; path = scripts/xcode_prebuild.sh; sourceTree = "<group>"; }; - D531426F15BC94B1005EFF20 /* backend.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = backend.m; path = build/nimcache/backend.m; sourceTree = "<group>"; }; - D531427115BC94B1005EFF20 /* stdlib_system.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = stdlib_system.m; path = build/nimcache/stdlib_system.m; sourceTree = "<group>"; }; - D592E19015C7120F005258EA /* backend.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = backend.h; path = build/nimcache/backend.h; sourceTree = "<group>"; }; - D592E19115C71415005258EA /* nimbase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nimbase.h; path = build/nimcache/nimbase.h; sourceTree = "<group>"; }; - D5B6F94515FA8D4C0084A85B /* NRViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NRViewController.h; path = src/NRViewController.h; sourceTree = "<group>"; }; - D5B6F94615FA8D4C0084A85B /* NRViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NRViewController.m; path = src/NRViewController.m; sourceTree = "<group>"; }; - D5B6F96315FB448D0084A85B /* NRViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = NRViewController.xib; path = resources/ui/NRViewController.xib; sourceTree = "<group>"; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - D531422215BC8610005EFF20 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - D531422A15BC8611005EFF20 /* UIKit.framework in Frameworks */, - D531422C15BC8611005EFF20 /* Foundation.framework in Frameworks */, - D531422E15BC8611005EFF20 /* CoreGraphics.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - D531421A15BC8610005EFF20 = { - isa = PBXGroup; - children = ( - D531426A15BC93D5005EFF20 /* build */, - D531424515BC874E005EFF20 /* resources */, - D531426615BC91E1005EFF20 /* scripts */, - D531424815BC87AD005EFF20 /* src */, - D531422815BC8611005EFF20 /* Frameworks */, - D531422615BC8611005EFF20 /* Products */, - ); - sourceTree = "<group>"; - }; - D531422615BC8611005EFF20 /* Products */ = { - isa = PBXGroup; - children = ( - D531422515BC8611005EFF20 /* cross-calculator.app */, - ); - name = Products; - sourceTree = "<group>"; - }; - D531422815BC8611005EFF20 /* Frameworks */ = { - isa = PBXGroup; - children = ( - D531422915BC8611005EFF20 /* UIKit.framework */, - D531422B15BC8611005EFF20 /* Foundation.framework */, - D531422D15BC8611005EFF20 /* CoreGraphics.framework */, - ); - name = Frameworks; - sourceTree = "<group>"; - }; - D531424515BC874E005EFF20 /* resources */ = { - isa = PBXGroup; - children = ( - D531424615BC8756005EFF20 /* plist */, - D5B6F96115FB447C0084A85B /* ui */, - ); - name = resources; - sourceTree = "<group>"; - }; - D531424615BC8756005EFF20 /* plist */ = { - isa = PBXGroup; - children = ( - D531424715BC87A5005EFF20 /* cross-calculator-Info.plist */, - ); - name = plist; - sourceTree = "<group>"; - }; - D531424815BC87AD005EFF20 /* src */ = { - isa = PBXGroup; - children = ( - D531424915BC87B6005EFF20 /* AppDelegate.h */, - D531424A15BC87B6005EFF20 /* AppDelegate.m */, - D531424C15BC87B6005EFF20 /* cross-calculator-Prefix.pch */, - D531424B15BC87B6005EFF20 /* main.m */, - D5B6F94515FA8D4C0084A85B /* NRViewController.h */, - D5B6F94615FA8D4C0084A85B /* NRViewController.m */, - ); - name = src; - sourceTree = "<group>"; - }; - D531426615BC91E1005EFF20 /* scripts */ = { - isa = PBXGroup; - children = ( - D531426715BC91EF005EFF20 /* tags.sh */, - D531426815BC91EF005EFF20 /* xcode_prebuild.sh */, - ); - name = scripts; - sourceTree = "<group>"; - }; - D531426A15BC93D5005EFF20 /* build */ = { - isa = PBXGroup; - children = ( - D531426E15BC94A6005EFF20 /* nimrod */, - ); - name = build; - sourceTree = "<group>"; - }; - D531426E15BC94A6005EFF20 /* nimrod */ = { - isa = PBXGroup; - children = ( - D592E19015C7120F005258EA /* backend.h */, - D531426F15BC94B1005EFF20 /* backend.m */, - D592E19115C71415005258EA /* nimbase.h */, - D531427115BC94B1005EFF20 /* stdlib_system.m */, - ); - name = nimrod; - sourceTree = "<group>"; - }; - D5B6F96115FB447C0084A85B /* ui */ = { - isa = PBXGroup; - children = ( - D5B6F96315FB448D0084A85B /* NRViewController.xib */, - ); - name = ui; - sourceTree = "<group>"; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - D531422415BC8610005EFF20 /* cross-calculator */ = { - isa = PBXNativeTarget; - buildConfigurationList = D531423D15BC8611005EFF20 /* Build configuration list for PBXNativeTarget "cross-calculator" */; - buildPhases = ( - D531426915BC926C005EFF20 /* ShellScript */, - D531422115BC8610005EFF20 /* Sources */, - D531422215BC8610005EFF20 /* Frameworks */, - D531422315BC8610005EFF20 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "cross-calculator"; - productName = "cross-calculator"; - productReference = D531422515BC8611005EFF20 /* cross-calculator.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - D531421C15BC8610005EFF20 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0420; - ORGANIZATIONNAME = "Electric Hands Software"; - }; - buildConfigurationList = D531421F15BC8610005EFF20 /* Build configuration list for PBXProject "cross-calculator" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = D531421A15BC8610005EFF20; - productRefGroup = D531422615BC8611005EFF20 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - D531422415BC8610005EFF20 /* cross-calculator */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - D531422315BC8610005EFF20 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - D531426915BC926C005EFF20 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = scripts/xcode_prebuild.sh; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - D531422115BC8610005EFF20 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D531424D15BC87B6005EFF20 /* AppDelegate.m in Sources */, - D531424E15BC87B6005EFF20 /* main.m in Sources */, - D531427215BC94B1005EFF20 /* backend.m in Sources */, - D531427415BC94B1005EFF20 /* stdlib_system.m in Sources */, - D5B6F94815FA8D4C0084A85B /* NRViewController.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - D531423B15BC8611005EFF20 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = ( - armv7, - armv6, - ); - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_VERSION = ""; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - D531423C15BC8611005EFF20 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = ( - armv7, - armv6, - ); - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_VERSION = ""; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; - OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - D531423E15BC8611005EFF20 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "src/cross-calculator-Prefix.pch"; - INFOPLIST_FILE = "resources/plist/cross-calculator-Info.plist"; - PRODUCT_NAME = "$(TARGET_NAME)"; - WRAPPER_EXTENSION = app; - }; - name = Debug; - }; - D531423F15BC8611005EFF20 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "src/cross-calculator-Prefix.pch"; - INFOPLIST_FILE = "resources/plist/cross-calculator-Info.plist"; - PRODUCT_NAME = "$(TARGET_NAME)"; - WRAPPER_EXTENSION = app; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - D531421F15BC8610005EFF20 /* Build configuration list for PBXProject "cross-calculator" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D531423B15BC8611005EFF20 /* Debug */, - D531423C15BC8611005EFF20 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - D531423D15BC8611005EFF20 /* Build configuration list for PBXNativeTarget "cross-calculator" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D531423E15BC8611005EFF20 /* Debug */, - D531423F15BC8611005EFF20 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = D531421C15BC8610005EFF20 /* Project object */; -} diff --git a/examples/cross_calculator/ios/readme.txt b/examples/cross_calculator/ios/readme.txt deleted file mode 100644 index 3ea03a367..000000000 --- a/examples/cross_calculator/ios/readme.txt +++ /dev/null @@ -1,13 +0,0 @@ -In this directory you will find the iOS platform cross-calculator sample. - -The iOS version of the code builds a view controller in charge of displaying -the interface to the user. The Nim backend code is compiled into C code and -put into build/nimrod as a pre-build phase of the project. - -When the calculate button is used the view controller calls the Nim code to -delegate the logic of the operation and puts the result in a label for display. -All interface error checks are implemented in the view controller. - -This version of the iOS project is known to work with Xcode 4.2 and Xcode -4.4.1. The final binary can be deployed on iOS 3.x to 5.x supporting all iOS -platforms and versions available at the moment. diff --git a/examples/cross_calculator/ios/resources/plist/cross-calculator-Info.plist b/examples/cross_calculator/ios/resources/plist/cross-calculator-Info.plist deleted file mode 100644 index 758f20e38..000000000 --- a/examples/cross_calculator/ios/resources/plist/cross-calculator-Info.plist +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -<plist version="1.0"> -<dict> - <key>CFBundleDevelopmentRegion</key> - <string>en</string> - <key>CFBundleDisplayName</key> - <string>${PRODUCT_NAME}</string> - <key>CFBundleExecutable</key> - <string>${EXECUTABLE_NAME}</string> - <key>CFBundleIconFiles</key> - <array/> - <key>CFBundleIdentifier</key> - <string>com.github.nimrod.${PRODUCT_NAME:rfc1034identifier}</string> - <key>CFBundleInfoDictionaryVersion</key> - <string>6.0</string> - <key>CFBundleName</key> - <string>${PRODUCT_NAME}</string> - <key>CFBundlePackageType</key> - <string>APPL</string> - <key>CFBundleShortVersionString</key> - <string>1.0</string> - <key>CFBundleSignature</key> - <string>????</string> - <key>CFBundleVersion</key> - <string>1.0</string> - <key>UIApplicationExitsOnSuspend</key> - <true/> -</dict> -</plist> diff --git a/examples/cross_calculator/ios/resources/ui/NRViewController.xib b/examples/cross_calculator/ios/resources/ui/NRViewController.xib deleted file mode 100644 index 2118b5044..000000000 --- a/examples/cross_calculator/ios/resources/ui/NRViewController.xib +++ /dev/null @@ -1,479 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="8.00"> - <data> - <int key="IBDocument.SystemTarget">0</int> - <string key="IBDocument.SystemVersion">11E53</string> - <string key="IBDocument.InterfaceBuilderVersion">2549</string> - <string key="IBDocument.AppKitVersion">1138.47</string> - <string key="IBDocument.HIToolboxVersion">569.00</string> - <object class="NSMutableDictionary" key="IBDocument.PluginVersions"> - <string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - <string key="NS.object.0">1498</string> - </object> - <array key="IBDocument.IntegratedClassDependencies"> - <string>IBProxyObject</string> - <string>IBUIButton</string> - <string>IBUILabel</string> - <string>IBUITextField</string> - <string>IBUIView</string> - </array> - <array key="IBDocument.PluginDependencies"> - <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - </array> - <object class="NSMutableDictionary" key="IBDocument.Metadata"> - <string key="NS.key.0">PluginDependencyRecalculationVersion</string> - <integer value="1" key="NS.object.0"/> - </object> - <array class="NSMutableArray" key="IBDocument.RootObjects" id="1000"> - <object class="IBProxyObject" id="372490531"> - <string key="IBProxiedObjectIdentifier">IBFilesOwner</string> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - </object> - <object class="IBProxyObject" id="975951072"> - <string key="IBProxiedObjectIdentifier">IBFirstResponder</string> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - </object> - <object class="IBUIView" id="191373211"> - <reference key="NSNextResponder"/> - <int key="NSvFlags">274</int> - <array class="NSMutableArray" key="NSSubviews"> - <object class="IBUIButton" id="467453084"> - <reference key="NSNextResponder" ref="191373211"/> - <int key="NSvFlags">292</int> - <string key="NSFrame">{{0, -10}, {320, 480}}</string> - <reference key="NSSuperview" ref="191373211"/> - <reference key="NSWindow"/> - <string key="NSReuseIdentifierKey">_NS:9</string> - <bool key="IBUIOpaque">NO</bool> - <int key="IBUITag">1</int> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - <int key="IBUIContentHorizontalAlignment">0</int> - <int key="IBUIContentVerticalAlignment">0</int> - <object class="NSColor" key="IBUIHighlightedTitleColor" id="95215378"> - <int key="NSColorSpace">3</int> - <bytes key="NSWhite">MQA</bytes> - </object> - <object class="NSColor" key="IBUINormalTitleColor"> - <int key="NSColorSpace">1</int> - <bytes key="NSRGB">MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA</bytes> - </object> - <object class="NSColor" key="IBUINormalTitleShadowColor" id="1056499111"> - <int key="NSColorSpace">3</int> - <bytes key="NSWhite">MC41AA</bytes> - </object> - <object class="IBUIFontDescription" key="IBUIFontDescription" id="686052398"> - <int key="type">2</int> - <double key="pointSize">15</double> - </object> - <object class="NSFont" key="IBUIFont" id="594372787"> - <string key="NSName">Helvetica-Bold</string> - <double key="NSSize">15</double> - <int key="NSfFlags">16</int> - </object> - </object> - <object class="IBUILabel" id="353054360"> - <reference key="NSNextResponder" ref="191373211"/> - <int key="NSvFlags">306</int> - <string key="NSFrameSize">{320, 34}</string> - <reference key="NSSuperview" ref="191373211"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView" ref="525225214"/> - <string key="NSReuseIdentifierKey">_NS:9</string> - <bool key="IBUIOpaque">NO</bool> - <bool key="IBUIClipsSubviews">YES</bool> - <int key="IBUIContentMode">7</int> - <int key="IBUITag">2</int> - <bool key="IBUIUserInteractionEnabled">NO</bool> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - <string key="IBUIText">Nim Crossplatform Calculator</string> - <object class="NSColor" key="IBUITextColor" id="128895179"> - <int key="NSColorSpace">1</int> - <bytes key="NSRGB">MCAwIDAAA</bytes> - </object> - <nil key="IBUIHighlightedColor"/> - <int key="IBUIBaselineAdjustment">0</int> - <float key="IBUIMinimumFontSize">10</float> - <int key="IBUITextAlignment">1</int> - <object class="IBUIFontDescription" key="IBUIFontDescription"> - <int key="type">2</int> - <double key="pointSize">18</double> - </object> - <object class="NSFont" key="IBUIFont"> - <string key="NSName">Helvetica-Bold</string> - <double key="NSSize">18</double> - <int key="NSfFlags">16</int> - </object> - </object> - <object class="IBUILabel" id="525225214"> - <reference key="NSNextResponder" ref="191373211"/> - <int key="NSvFlags">294</int> - <string key="NSFrame">{{20, 42}, {165, 31}}</string> - <reference key="NSSuperview" ref="191373211"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView" ref="1040444341"/> - <string key="NSReuseIdentifierKey">_NS:9</string> - <bool key="IBUIOpaque">NO</bool> - <bool key="IBUIClipsSubviews">YES</bool> - <int key="IBUIContentMode">7</int> - <int key="IBUITag">3</int> - <bool key="IBUIUserInteractionEnabled">NO</bool> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - <string key="IBUIText">Value A:</string> - <reference key="IBUITextColor" ref="128895179"/> - <nil key="IBUIHighlightedColor"/> - <int key="IBUIBaselineAdjustment">0</int> - <float key="IBUIMinimumFontSize">10</float> - <object class="IBUIFontDescription" key="IBUIFontDescription" id="768572949"> - <int key="type">1</int> - <double key="pointSize">17</double> - </object> - <object class="NSFont" key="IBUIFont" id="972319481"> - <string key="NSName">Helvetica</string> - <double key="NSSize">17</double> - <int key="NSfFlags">16</int> - </object> - </object> - <object class="IBUILabel" id="904781109"> - <reference key="NSNextResponder" ref="191373211"/> - <int key="NSvFlags">294</int> - <string key="NSFrame">{{20, 81}, {165, 31}}</string> - <reference key="NSSuperview" ref="191373211"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView" ref="1041721572"/> - <string key="NSReuseIdentifierKey">_NS:9</string> - <bool key="IBUIOpaque">NO</bool> - <bool key="IBUIClipsSubviews">YES</bool> - <int key="IBUIContentMode">7</int> - <int key="IBUITag">4</int> - <bool key="IBUIUserInteractionEnabled">NO</bool> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - <string key="IBUIText">Value B:</string> - <reference key="IBUITextColor" ref="128895179"/> - <nil key="IBUIHighlightedColor"/> - <int key="IBUIBaselineAdjustment">0</int> - <float key="IBUIMinimumFontSize">10</float> - <reference key="IBUIFontDescription" ref="768572949"/> - <reference key="IBUIFont" ref="972319481"/> - </object> - <object class="IBUIButton" id="557594991"> - <reference key="NSNextResponder" ref="191373211"/> - <int key="NSvFlags">291</int> - <string key="NSFrame">{{193, 124}, {107, 37}}</string> - <reference key="NSSuperview" ref="191373211"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView"/> - <string key="NSReuseIdentifierKey">_NS:9</string> - <bool key="IBUIOpaque">NO</bool> - <int key="IBUITag">5</int> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - <int key="IBUIContentHorizontalAlignment">0</int> - <int key="IBUIContentVerticalAlignment">0</int> - <int key="IBUIButtonType">1</int> - <string key="IBUINormalTitle">Add!</string> - <reference key="IBUIHighlightedTitleColor" ref="95215378"/> - <object class="NSColor" key="IBUINormalTitleColor"> - <int key="NSColorSpace">1</int> - <bytes key="NSRGB">MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA</bytes> - </object> - <reference key="IBUINormalTitleShadowColor" ref="1056499111"/> - <reference key="IBUIFontDescription" ref="686052398"/> - <reference key="IBUIFont" ref="594372787"/> - </object> - <object class="IBUILabel" id="360864196"> - <reference key="NSNextResponder" ref="191373211"/> - <int key="NSvFlags">292</int> - <string key="NSFrame">{{20, 124}, {60, 37}}</string> - <reference key="NSSuperview" ref="191373211"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView" ref="521073831"/> - <string key="NSReuseIdentifierKey">_NS:9</string> - <bool key="IBUIOpaque">NO</bool> - <bool key="IBUIClipsSubviews">YES</bool> - <int key="IBUIContentMode">7</int> - <int key="IBUITag">6</int> - <bool key="IBUIUserInteractionEnabled">NO</bool> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - <string key="IBUIText">Result:</string> - <reference key="IBUITextColor" ref="128895179"/> - <nil key="IBUIHighlightedColor"/> - <int key="IBUIBaselineAdjustment">0</int> - <float key="IBUIMinimumFontSize">10</float> - <reference key="IBUIFontDescription" ref="768572949"/> - <reference key="IBUIFont" ref="972319481"/> - </object> - <object class="IBUILabel" id="521073831"> - <reference key="NSNextResponder" ref="191373211"/> - <int key="NSvFlags">294</int> - <string key="NSFrame">{{88, 124}, {97, 37}}</string> - <reference key="NSSuperview" ref="191373211"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView" ref="557594991"/> - <string key="NSReuseIdentifierKey">_NS:9</string> - <bool key="IBUIOpaque">NO</bool> - <bool key="IBUIClipsSubviews">YES</bool> - <int key="IBUIContentMode">7</int> - <int key="IBUITag">7</int> - <bool key="IBUIUserInteractionEnabled">NO</bool> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - <string key="IBUIText"/> - <reference key="IBUITextColor" ref="128895179"/> - <nil key="IBUIHighlightedColor"/> - <int key="IBUIBaselineAdjustment">0</int> - <float key="IBUIMinimumFontSize">10</float> - <reference key="IBUIFontDescription" ref="768572949"/> - <reference key="IBUIFont" ref="972319481"/> - </object> - <object class="IBUITextField" id="1040444341"> - <reference key="NSNextResponder" ref="191373211"/> - <int key="NSvFlags">291</int> - <string key="NSFrame">{{193, 42}, {107, 31}}</string> - <reference key="NSSuperview" ref="191373211"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView" ref="904781109"/> - <string key="NSReuseIdentifierKey">_NS:9</string> - <bool key="IBUIOpaque">NO</bool> - <bool key="IBUIClipsSubviews">YES</bool> - <int key="IBUITag">8</int> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - <int key="IBUIContentVerticalAlignment">0</int> - <string key="IBUIText"/> - <int key="IBUIBorderStyle">3</int> - <string key="IBUIPlaceholder">Integer</string> - <object class="NSColor" key="IBUITextColor"> - <int key="NSColorSpace">3</int> - <bytes key="NSWhite">MAA</bytes> - <object class="NSColorSpace" key="NSCustomColorSpace" id="433120901"> - <int key="NSID">2</int> - </object> - </object> - <int key="IBUITextAlignment">1</int> - <bool key="IBUIAdjustsFontSizeToFit">YES</bool> - <float key="IBUIMinimumFontSize">17</float> - <object class="IBUITextInputTraits" key="IBUITextInputTraits"> - <int key="IBUIKeyboardType">4</int> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - </object> - <int key="IBUIClearButtonMode">1</int> - <object class="IBUIFontDescription" key="IBUIFontDescription" id="836805198"> - <int key="type">1</int> - <double key="pointSize">14</double> - </object> - <object class="NSFont" key="IBUIFont" id="700927782"> - <string key="NSName">Helvetica</string> - <double key="NSSize">14</double> - <int key="NSfFlags">16</int> - </object> - </object> - <object class="IBUITextField" id="1041721572"> - <reference key="NSNextResponder" ref="191373211"/> - <int key="NSvFlags">291</int> - <string key="NSFrame">{{193, 81}, {107, 31}}</string> - <reference key="NSSuperview" ref="191373211"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView" ref="360864196"/> - <string key="NSReuseIdentifierKey">_NS:9</string> - <bool key="IBUIOpaque">NO</bool> - <bool key="IBUIClipsSubviews">YES</bool> - <int key="IBUITag">9</int> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - <int key="IBUIContentVerticalAlignment">0</int> - <string key="IBUIText"/> - <int key="IBUIBorderStyle">3</int> - <string key="IBUIPlaceholder">Integer</string> - <object class="NSColor" key="IBUITextColor"> - <int key="NSColorSpace">3</int> - <bytes key="NSWhite">MAA</bytes> - <reference key="NSCustomColorSpace" ref="433120901"/> - </object> - <int key="IBUITextAlignment">1</int> - <bool key="IBUIAdjustsFontSizeToFit">YES</bool> - <float key="IBUIMinimumFontSize">17</float> - <object class="IBUITextInputTraits" key="IBUITextInputTraits"> - <int key="IBUIKeyboardType">4</int> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - </object> - <int key="IBUIClearButtonMode">1</int> - <reference key="IBUIFontDescription" ref="836805198"/> - <reference key="IBUIFont" ref="700927782"/> - </object> - </array> - <string key="NSFrame">{{0, 20}, {320, 460}}</string> - <reference key="NSSuperview"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView" ref="353054360"/> - <object class="NSColor" key="IBUIBackgroundColor"> - <int key="NSColorSpace">3</int> - <bytes key="NSWhite">MQA</bytes> - <reference key="NSCustomColorSpace" ref="433120901"/> - </object> - <object class="IBUISimulatedStatusBarMetrics" key="IBUISimulatedStatusBarMetrics"/> - <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string> - </object> - </array> - <object class="IBObjectContainer" key="IBDocument.Objects"> - <array class="NSMutableArray" key="connectionRecords"/> - <object class="IBMutableOrderedSet" key="objectRecords"> - <array key="orderedObjects"> - <object class="IBObjectRecord"> - <int key="objectID">0</int> - <array key="object" id="0"/> - <reference key="children" ref="1000"/> - <nil key="parent"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">1</int> - <reference key="object" ref="191373211"/> - <array class="NSMutableArray" key="children"> - <reference ref="353054360"/> - <reference ref="525225214"/> - <reference ref="904781109"/> - <reference ref="557594991"/> - <reference ref="360864196"/> - <reference ref="521073831"/> - <reference ref="1040444341"/> - <reference ref="1041721572"/> - <reference ref="467453084"/> - </array> - <reference key="parent" ref="0"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">-1</int> - <reference key="object" ref="372490531"/> - <reference key="parent" ref="0"/> - <string key="objectName">File's Owner</string> - </object> - <object class="IBObjectRecord"> - <int key="objectID">-2</int> - <reference key="object" ref="975951072"/> - <reference key="parent" ref="0"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">14</int> - <reference key="object" ref="1041721572"/> - <reference key="parent" ref="191373211"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">13</int> - <reference key="object" ref="1040444341"/> - <reference key="parent" ref="191373211"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">12</int> - <reference key="object" ref="521073831"/> - <reference key="parent" ref="191373211"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">11</int> - <reference key="object" ref="360864196"/> - <reference key="parent" ref="191373211"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">10</int> - <reference key="object" ref="557594991"/> - <reference key="parent" ref="191373211"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">8</int> - <reference key="object" ref="904781109"/> - <reference key="parent" ref="191373211"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">7</int> - <reference key="object" ref="525225214"/> - <reference key="parent" ref="191373211"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">4</int> - <reference key="object" ref="353054360"/> - <reference key="parent" ref="191373211"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">22</int> - <reference key="object" ref="467453084"/> - <reference key="parent" ref="191373211"/> - </object> - </array> - </object> - <dictionary class="NSMutableDictionary" key="flattenedProperties"> - <string key="-1.CustomClassName">NRViewController</string> - <string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - <string key="-2.CustomClassName">UIResponder</string> - <string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - <string key="1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - <string key="10.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - <string key="11.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - <string key="12.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - <string key="13.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - <string key="14.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - <string key="22.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - <string key="4.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - <string key="7.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - <string key="8.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string> - </dictionary> - <dictionary class="NSMutableDictionary" key="unlocalizedProperties"/> - <nil key="activeLocalization"/> - <dictionary class="NSMutableDictionary" key="localizations"/> - <nil key="sourceID"/> - <int key="maxID">22</int> - </object> - <object class="IBClassDescriber" key="IBDocument.Classes"> - <array class="NSMutableArray" key="referencedPartialClassDescriptions"> - <object class="IBPartialClassDescription"> - <string key="className">NRViewController</string> - <string key="superclassName">UIViewController</string> - <dictionary class="NSMutableDictionary" key="actions"> - <string key="backgroundTouched">id</string> - <string key="calculateButtonTouched">id</string> - </dictionary> - <dictionary class="NSMutableDictionary" key="actionInfosByName"> - <object class="IBActionInfo" key="backgroundTouched"> - <string key="name">backgroundTouched</string> - <string key="candidateClassName">id</string> - </object> - <object class="IBActionInfo" key="calculateButtonTouched"> - <string key="name">calculateButtonTouched</string> - <string key="candidateClassName">id</string> - </object> - </dictionary> - <dictionary class="NSMutableDictionary" key="outlets"> - <string key="aText">UITextField</string> - <string key="bText">UITextField</string> - <string key="calculateButton">UIButton</string> - <string key="resultLabel">UILabel</string> - </dictionary> - <dictionary class="NSMutableDictionary" key="toOneOutletInfosByName"> - <object class="IBToOneOutletInfo" key="aText"> - <string key="name">aText</string> - <string key="candidateClassName">UITextField</string> - </object> - <object class="IBToOneOutletInfo" key="bText"> - <string key="name">bText</string> - <string key="candidateClassName">UITextField</string> - </object> - <object class="IBToOneOutletInfo" key="calculateButton"> - <string key="name">calculateButton</string> - <string key="candidateClassName">UIButton</string> - </object> - <object class="IBToOneOutletInfo" key="resultLabel"> - <string key="name">resultLabel</string> - <string key="candidateClassName">UILabel</string> - </object> - </dictionary> - <object class="IBClassDescriptionSource" key="sourceIdentifier"> - <string key="majorKey">IBProjectSource</string> - <string key="minorKey">./Classes/NRViewController.h</string> - </object> - </object> - </array> - </object> - <int key="IBDocument.localizationMode">0</int> - <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string> - <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies"> - <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string> - <real value="0.0" key="NS.object.0"/> - </object> - <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool> - <int key="IBDocument.defaultPropertyAccessControl">3</int> - <string key="IBCocoaTouchPluginVersion">1498</string> - </data> -</archive> diff --git a/examples/cross_calculator/ios/scripts/tags.sh b/examples/cross_calculator/ios/scripts/tags.sh deleted file mode 100644 index 111e7a1c0..000000000 --- a/examples/cross_calculator/ios/scripts/tags.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -if [ ! -d src ] -then - cd .. -fi - -if [ -d src ] -then - ~/bin/objctags -R \ - build/nimcache \ - src -fi diff --git a/examples/cross_calculator/ios/scripts/xcode_prebuild.sh b/examples/cross_calculator/ios/scripts/xcode_prebuild.sh deleted file mode 100644 index 90bafd74e..000000000 --- a/examples/cross_calculator/ios/scripts/xcode_prebuild.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh - -# Set this to the full path of your nimrod compiler -# since Xcode doesn't inherit your user environment. -PATH_TO_NIMROD=~/project/nimrod/bin/nimrod -# Set this to the location of the nimbase.h file so -# the script can update it if it changes. -PATH_TO_NIMBASE=~/project/nimrod/lib/nimbase.h - -# Force errors to fail script. -set -e - -# If we are running from inside the scripts subdir, get out. -if [ ! -d src ] -then - cd .. -fi - -DEST_NIMBASE=build/nimcache/nimbase.h - -# Ok, are we out now? -if [ -d src ] -then - $PATH_TO_NIMROD objc --noMain --app:lib \ - --nimcache:./build/nimcache --compileOnly \ - --header --cpu:i386 ../nimrod_backend/backend.nim - if [ "${PATH_TO_NIMBASE}" -nt "${DEST_NIMBASE}" ] - then - echo "Updating nimbase.h" - cp "${PATH_TO_NIMBASE}" "${DEST_NIMBASE}" - fi -else - echo "Uh oh, src directory not found?" - exit 1 -fi diff --git a/examples/cross_calculator/ios/src/AppDelegate.h b/examples/cross_calculator/ios/src/AppDelegate.h deleted file mode 100644 index a5a8b3852..000000000 --- a/examples/cross_calculator/ios/src/AppDelegate.h +++ /dev/null @@ -1,7 +0,0 @@ -#import <UIKit/UIKit.h> - -@interface AppDelegate : UIResponder <UIApplicationDelegate> - -@property (strong, nonatomic) UIWindow *window; - -@end diff --git a/examples/cross_calculator/ios/src/AppDelegate.m b/examples/cross_calculator/ios/src/AppDelegate.m deleted file mode 100644 index 53e7f6188..000000000 --- a/examples/cross_calculator/ios/src/AppDelegate.m +++ /dev/null @@ -1,39 +0,0 @@ -#import "AppDelegate.h" - -#import "NRViewController.h" - - -@interface AppDelegate () -@property (nonatomic, retain) NRViewController *viewController; -@end - - -@implementation AppDelegate - -@synthesize viewController = _viewController; -@synthesize window = _window; - -- (BOOL)application:(UIApplication *)application - didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - self.window = [[[UIWindow alloc] - initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; - - self.viewController = [[NRViewController new] autorelease]; - if ([self.window respondsToSelector:@selector(setRootViewController:)]) - self.window.rootViewController = self.viewController; - else - [self.window addSubview:self.viewController.view]; - [self.window makeKeyAndVisible]; - - return YES; -} - -- (void)dealloc -{ - [_window release]; - [_viewController release]; - [super dealloc]; -} - -@end diff --git a/examples/cross_calculator/ios/src/NRViewController.h b/examples/cross_calculator/ios/src/NRViewController.h deleted file mode 100644 index 36ba37758..000000000 --- a/examples/cross_calculator/ios/src/NRViewController.h +++ /dev/null @@ -1,11 +0,0 @@ -@interface NRViewController : UIViewController - -@property (nonatomic, retain) IBOutlet UIButton *calculateButton; -@property (nonatomic, retain) IBOutlet UITextField *aText; -@property (nonatomic, retain) IBOutlet UITextField *bText; -@property (nonatomic, retain) IBOutlet UILabel *resultLabel; - -- (IBAction)calculateButtonTouched; -- (IBAction)backgroundTouched; - -@end \ No newline at end of file diff --git a/examples/cross_calculator/ios/src/NRViewController.m b/examples/cross_calculator/ios/src/NRViewController.m deleted file mode 100644 index f629bfc09..000000000 --- a/examples/cross_calculator/ios/src/NRViewController.m +++ /dev/null @@ -1,210 +0,0 @@ -#import "NRViewController.h" - -#import "backend.h" - - -@implementation NRViewController - -@synthesize aText = _aText; -@synthesize bText = _bText; -@synthesize calculateButton = _calculateButton; -@synthesize resultLabel = _resultLabel; - -- (void)dealloc -{ - [_aText release]; - [_bText release]; - [_calculateButton release]; - [_resultLabel release]; - [super dealloc]; -} - -- (void)viewDidUnload -{ - self.calculateButton = nil; - self.aText = nil; - self.bText = nil; - self.resultLabel = nil; - [super viewDidUnload]; -} - -- (BOOL)shouldAutorotateToInterfaceOrientation: - (UIInterfaceOrientation)interfaceOrientation -{ - return YES; -} - -/// User wants to calculate the inputs. Well, do it! -- (IBAction)calculateButtonTouched -{ - // Dismiss all keyboards. - [self backgroundTouched]; - - // Call Nim code, store the result and display it. - const int a = [self.aText.text intValue]; - const int b = [self.bText.text intValue]; - const int c = myAdd(a, b); - self.resultLabel.text = [NSString stringWithFormat:@"%d + %d = %d", - a, b, c]; -} - -/// If the user touches the background, dismiss any visible keyboard. -- (IBAction)backgroundTouched -{ - [self.aText resignFirstResponder]; - [self.bText resignFirstResponder]; -} - -/** Custom loadView method for backwards compatibility. - * Unfortunately I've been unable to coerce Xcode 4.4 to generate nib files - * which are compatible with my trusty iOS 3.0 ipod touch so in order to be - * fully compatible for all devices we have to build the interface manually in - * code rather than through the keyed archivers provided by the interface - * builder. - * - * Rather than recreating the user interface manually in code the tool nib2obj - * was used on the xib file and slightly modified to fit the original property - * names. Which means here is a lot of garbage you would never write in real - * life. Please ignore the following "wall of code" for the purposes of - * learning Nim, this is all just because Apple can't be bothered to - * maintain backwards compatibility properly. - */ -- (void)loadView -{ - [super loadView]; - - self.calculateButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; - self.calculateButton.autoresizesSubviews = YES; - self.calculateButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; - self.calculateButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; - self.calculateButton.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); - self.calculateButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; - self.calculateButton.frame = CGRectMake(193.0, 124.0, 107.0, 37.0); - self.calculateButton.tag = 5; - [self.calculateButton setTitle:@"Add!" forState:UIControlStateNormal]; - [self.calculateButton addTarget:self - action:@selector(calculateButtonTouched) - forControlEvents:UIControlEventTouchUpInside]; - - UILabel *label11 = [[UILabel alloc] initWithFrame:CGRectMake(20.0, 124.0, 60.0, 37.0)]; - label11.adjustsFontSizeToFitWidth = YES; - label11.autoresizesSubviews = YES; - label11.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; - label11.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); - label11.frame = CGRectMake(20.0, 124.0, 60.0, 37.0); - label11.tag = 6; - label11.text = @"Result:"; - - UILabel *label4 = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 34.0)]; - label4.adjustsFontSizeToFitWidth = YES; - label4.autoresizesSubviews = YES; - label4.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin; - label4.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); - label4.frame = CGRectMake(0.0, 0.0, 320.0, 34.0); - label4.tag = 2; - label4.text = @"Nim Crossplatform Calculator"; - label4.textAlignment = UITextAlignmentCenter; - - UIButton *background_button = [UIButton buttonWithType:UIButtonTypeCustom]; - background_button.autoresizesSubviews = YES; - background_button.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; - background_button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; - background_button.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); - background_button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; - background_button.frame = CGRectMake(0.0, -10.0, 320.0, 480.0); - background_button.tag = 1; - [background_button addTarget:self action:@selector(backgroundTouched) - forControlEvents:UIControlEventTouchDown]; - - self.resultLabel = [[[UILabel alloc] initWithFrame:CGRectMake(88.0, 124.0, 97.0, 37.0)] autorelease]; - self.resultLabel.adjustsFontSizeToFitWidth = YES; - self.resultLabel.autoresizesSubviews = YES; - self.resultLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; - self.resultLabel.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); - self.resultLabel.frame = CGRectMake(88.0, 124.0, 97.0, 37.0); - self.resultLabel.tag = 7; - self.resultLabel.text = @""; - - self.aText = [[[UITextField alloc] initWithFrame:CGRectMake(193.0, 42.0, 107.0, 31.0)] autorelease]; - self.aText.adjustsFontSizeToFitWidth = YES; - self.aText.autocapitalizationType = UITextAutocapitalizationTypeNone; - self.aText.autocorrectionType = UITextAutocorrectionTypeDefault; - self.aText.autoresizesSubviews = YES; - self.aText.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; - self.aText.borderStyle = UITextBorderStyleRoundedRect; - self.aText.clearButtonMode = UITextFieldViewModeWhileEditing; - self.aText.clearsOnBeginEditing = NO; - self.aText.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; - self.aText.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); - self.aText.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; - self.aText.enablesReturnKeyAutomatically = NO; - self.aText.frame = CGRectMake(193.0, 42.0, 107.0, 31.0); - self.aText.keyboardAppearance = UIKeyboardAppearanceDefault; - self.aText.keyboardType = UIKeyboardTypeNumberPad; - self.aText.placeholder = @"Integer"; - self.aText.returnKeyType = UIReturnKeyDefault; - self.aText.tag = 8; - self.aText.text = @""; - self.aText.textAlignment = UITextAlignmentCenter; - - UILabel *label7 = [[UILabel alloc] initWithFrame:CGRectMake(20.0, 42.0, 165.0, 31.0)]; - label7.adjustsFontSizeToFitWidth = YES; - label7.autoresizesSubviews = YES; - label7.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; - label7.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); - label7.frame = CGRectMake(20.0, 42.0, 165.0, 31.0); - label7.tag = 3; - label7.text = @"Value A:"; - - UILabel *label8 = [[UILabel alloc] initWithFrame:CGRectMake(20.0, 81.0, 165.0, 31.0)]; - label8.adjustsFontSizeToFitWidth = YES; - label8.autoresizesSubviews = YES; - label8.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; - label8.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); - label8.frame = CGRectMake(20.0, 81.0, 165.0, 31.0); - label8.tag = 4; - label8.text = @"Value B:"; - - self.bText = [[[UITextField alloc] - initWithFrame:CGRectMake(193.0, 81.0, 107.0, 31.0)] autorelease]; - self.bText.adjustsFontSizeToFitWidth = YES; - self.bText.autocapitalizationType = UITextAutocapitalizationTypeNone; - self.bText.autocorrectionType = UITextAutocorrectionTypeDefault; - self.bText.autoresizesSubviews = YES; - self.bText.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; - self.bText.borderStyle = UITextBorderStyleRoundedRect; - self.bText.clearButtonMode = UITextFieldViewModeWhileEditing; - self.bText.clearsOnBeginEditing = NO; - self.bText.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; - self.bText.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); - self.bText.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; - self.bText.enablesReturnKeyAutomatically = NO; - self.bText.frame = CGRectMake(193.0, 81.0, 107.0, 31.0); - self.bText.keyboardAppearance = UIKeyboardAppearanceDefault; - self.bText.keyboardType = UIKeyboardTypeNumberPad; - self.bText.placeholder = @"Integer"; - self.bText.returnKeyType = UIReturnKeyDefault; - self.bText.tag = 9; - self.bText.text = @""; - self.bText.textAlignment = UITextAlignmentCenter; - - self.view.frame = CGRectMake(0.0, 20.0, 320.0, 460.0); - self.view.autoresizesSubviews = YES; - self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - self.view.backgroundColor = [UIColor colorWithWhite:1.000 alpha:1.000]; - self.view.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); - self.view.frame = CGRectMake(0.0, 20.0, 320.0, 460.0); - self.view.tag = 0; - - [self.view addSubview:background_button]; - [self.view addSubview:label4]; - [self.view addSubview:label7]; - [self.view addSubview:label8]; - [self.view addSubview:self.calculateButton]; - [self.view addSubview:label11]; - [self.view addSubview:self.resultLabel]; - [self.view addSubview:self.aText]; - [self.view addSubview:self.bText]; -} - -@end diff --git a/examples/cross_calculator/ios/src/cross-calculator-Prefix.pch b/examples/cross_calculator/ios/src/cross-calculator-Prefix.pch deleted file mode 100644 index 2f331ed43..000000000 --- a/examples/cross_calculator/ios/src/cross-calculator-Prefix.pch +++ /dev/null @@ -1,10 +0,0 @@ -#import <Availability.h> - -#ifndef __IPHONE_3_0 -#warning "This project uses features only available in iOS SDK 3.0 and later." -#endif - -#ifdef __OBJC__ - #import <UIKit/UIKit.h> - #import <Foundation/Foundation.h> -#endif diff --git a/examples/cross_calculator/ios/src/main.m b/examples/cross_calculator/ios/src/main.m deleted file mode 100644 index 7866684fe..000000000 --- a/examples/cross_calculator/ios/src/main.m +++ /dev/null @@ -1,13 +0,0 @@ -#import <UIKit/UIKit.h> - -#import "AppDelegate.h" -#import "backend.h" - -int main(int argc, char *argv[]) -{ - @autoreleasepool { - NimMain(); - return UIApplicationMain(argc, argv, nil, - NSStringFromClass([AppDelegate class])); - } -} diff --git a/examples/cross_calculator/lazarus/nimlaz.lpi b/examples/cross_calculator/lazarus/nimlaz.lpi deleted file mode 100644 index 3b9abd129..000000000 --- a/examples/cross_calculator/lazarus/nimlaz.lpi +++ /dev/null @@ -1,140 +0,0 @@ -<?xml version="1.0"?> -<CONFIG> - <ProjectOptions> - <Version Value="7"/> - <General> - <Flags> - <LRSInOutputDirectory Value="False"/> - </Flags> - <MainUnit Value="0"/> - <TargetFileExt Value=".exe"/> - <UseXPManifest Value="True"/> - <ActiveEditorIndexAtStart Value="1"/> - </General> - <VersionInfo> - <ProjectVersion Value=""/> - <Language Value=""/> - <CharSet Value=""/> - </VersionInfo> - <PublishOptions> - <Version Value="2"/> - <IgnoreBinaries Value="False"/> - <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/> - <ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/> - </PublishOptions> - <RunParams> - <local> - <FormatVersion Value="1"/> - <LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/> - </local> - </RunParams> - <RequiredPackages Count="1"> - <Item1> - <PackageName Value="LCL"/> - </Item1> - </RequiredPackages> - <Units Count="2"> - <Unit0> - <Filename Value="nimlaz.lpr"/> - <IsPartOfProject Value="True"/> - <UnitName Value="nimlaz"/> - <CursorPos X="17" Y="21"/> - <TopLine Value="1"/> - <EditorIndex Value="1"/> - <UsageCount Value="21"/> - <Loaded Value="True"/> - </Unit0> - <Unit1> - <Filename Value="unit1.pas"/> - <IsPartOfProject Value="True"/> - <ComponentName Value="Form1"/> - <ResourceBaseClass Value="Form"/> - <UnitName Value="Unit1"/> - <CursorPos X="26" Y="27"/> - <TopLine Value="2"/> - <EditorIndex Value="0"/> - <UsageCount Value="21"/> - <Loaded Value="True"/> - </Unit1> - </Units> - <JumpHistory Count="12" HistoryIndex="11"> - <Position1> - <Filename Value="unit1.pas"/> - <Caret Line="27" Column="1" TopLine="1"/> - </Position1> - <Position2> - <Filename Value="unit1.pas"/> - <Caret Line="15" Column="3" TopLine="1"/> - </Position2> - <Position3> - <Filename Value="unit1.pas"/> - <Caret Line="17" Column="26" TopLine="1"/> - </Position3> - <Position4> - <Filename Value="unit1.pas"/> - <Caret Line="16" Column="18" TopLine="1"/> - </Position4> - <Position5> - <Filename Value="unit1.pas"/> - <Caret Line="20" Column="43" TopLine="2"/> - </Position5> - <Position6> - <Filename Value="unit1.pas"/> - <Caret Line="21" Column="48" TopLine="16"/> - </Position6> - <Position7> - <Filename Value="nimlaz.lpr"/> - <Caret Line="1" Column="1" TopLine="1"/> - </Position7> - <Position8> - <Filename Value="unit1.pas"/> - <Caret Line="52" Column="17" TopLine="9"/> - </Position8> - <Position9> - <Filename Value="unit1.pas"/> - <Caret Line="51" Column="12" TopLine="9"/> - </Position9> - <Position10> - <Filename Value="nimlaz.lpr"/> - <Caret Line="21" Column="3" TopLine="1"/> - </Position10> - <Position11> - <Filename Value="nimlaz.lpr"/> - <Caret Line="20" Column="1" TopLine="1"/> - </Position11> - <Position12> - <Filename Value="nimlaz.lpr"/> - <Caret Line="21" Column="7" TopLine="1"/> - </Position12> - </JumpHistory> - </ProjectOptions> - <CompilerOptions> - <Version Value="8"/> - <SearchPaths> - <IncludeFiles Value="$(ProjOutDir)/"/> - </SearchPaths> - <Linking> - <Options> - <Win32> - <GraphicApplication Value="True"/> - </Win32> - </Options> - </Linking> - <Other> - <CompilerPath Value="$(CompPath)"/> - </Other> - </CompilerOptions> - <Debugging> - <Exceptions Count="3"> - <Item1> - <Name Value="EAbort"/> - </Item1> - <Item2> - <Name Value="ECodetoolError"/> - </Item2> - <Item3> - <Name Value="EFOpenError"/> - </Item3> - </Exceptions> - </Debugging> -</CONFIG> diff --git a/examples/cross_calculator/lazarus/nimlaz.lpr b/examples/cross_calculator/lazarus/nimlaz.lpr deleted file mode 100644 index 4457209d1..000000000 --- a/examples/cross_calculator/lazarus/nimlaz.lpr +++ /dev/null @@ -1,21 +0,0 @@ -program nimlaz; - -{$mode objfpc}{$H+} - -uses - {$IFDEF UNIX}{$IFDEF UseCThreads} - cthreads, - {$ENDIF}{$ENDIF} - Interfaces, // this includes the LCL widgetset - Forms - { you can add units after this }, Unit1, LResources; - -{$IFDEF WINDOWS}{$R nimlaz.rc}{$ENDIF} - -begin - {$I nimlaz.lrs} - Application.Initialize; - Application.CreateForm(TForm1, Form1); - Application.Run; -end. - diff --git a/examples/cross_calculator/lazarus/nimlaz.lrs b/examples/cross_calculator/lazarus/nimlaz.lrs deleted file mode 100644 index 234df82bd..000000000 --- a/examples/cross_calculator/lazarus/nimlaz.lrs +++ /dev/null @@ -1,5222 +0,0 @@ -LazarusResources.Add('MAINICON','ICO',[ - #0#0#1#0#6#0#0#0#0#0#1#0' '#0#226#145#0#0'f'#0#0#0#128#128#0#0#1#0' '#0'('#8#1 - +#0'H'#146#0#0'@@'#0#0#1#0' '#0'(B'#0#0'p'#154#1#0'00'#0#0#1#0' '#0#168'%'#0#0 - +#152#220#1#0' '#0#0#1#0' '#0#168#16#0#0'@'#2#2#0#16#16#0#0#1#0' '#0'h'#4#0#0 - +#232#18#2#0#137'PNG'#13#10#26#10#0#0#0#13'IHDR'#0#0#1#0#0#0#1#0#8#6#0#0#0'\r' - +#168'f'#0#0#145#169'IDATx'#218#236']'#5#128#28#245#213''#235'n'#231'w9I.N' - +#136#144#4#139#17#220'['#220#221#138';!'#184'|'#180#148#2#197'Kq'#13'R('#20 - +'(V'#180#197#3#9'!'#144#144#16#187#156#219#202#173#251'~'#239#253'gfo'#246'r' - +'~'#187';{'#242#131#201#236#173#204#204#202#251#253#159'?'#25#140'a'#12'c'#24 - +#181#144'I}'#1'c'#24#195#24#164#195#24#1#140'a'#12#163#24'c'#4'0'#134'1'#140 - +'b'#140#17#192#24#198'0'#138'1F'#0'c'#24#195'('#198#24#1#140'"'#140#155'}2}' - +#223#6#220'L'#184#25'q'#11#227#230#161#173#225#167#21#17#169#175'o'#12#217 - +#199#24#1#12'c'#160'@'#155'q7'#21#183'i'#184'M'#198#173#8'8'#225'N'#217'd2' - +#153'p'#155#132#191#167#239'<'#132#155''''#145'H0B'#224'7'#175#232'v'#7'n' - +#219'p'#219#128#219'F'#220'j'#145'4'#18'R'#6'c'#24#26#198#8' '#199#129'B.' - +#199#221'x'#224#132'|'#170#176#161'P'#211#223'%='#189'N'#214#195'7+'#235#230 - +'+O@'#207'r'#156#232#249#161#0#146#197'&'#232'$'#132#228#134#196#224#145#250 - +'s'#27'C'#255'0F'#0'9'#6#20'x'#18#234#189'q'#219#7#183'=P'#208'ie'#215#136 - +#159'#'#22'nA'#160'e'#194#157#226#199'DO'#20'nwG'#12'b!O'#240'$R'#238#236 - +#230'q'#209#157']I'#2#159#211#136#187#181#184'}'#198'o'#171#145#20'bR'#182 - +'c'#216#17'c'#4' 1P'#224#11'p'#183#20'8'#161#223#27#5'u'#186#240'X'#167'L' - +#239'('#228't'#155#219#0#228#244#12#185#140'{'#158#140'{'#157#140#191'!'#235 - +#250'|'#232'<'#166' '#196#130#252#146'ps'#27'pRM'#255#203#184#191#197#143#209 - +#235#196#247'AbGb'#232'B'#10#29#248#248#255#160#147#16'~'#26'3'#31'r'#3'c'#4 - +#144'e'#160#192'[p'#183#23#240#171'<'#10#229'L'#224#191#7#177#192#203'8)'#6 - +#185'\'#158#20'h'#185#156#187'_.'#227#4#158#30#163#219#10'z'#142#188#203#227 - +#252#30'D'#247#201#21#10#220'+AF{P@\'#22#135'D,'#134#194#26#131'8'#238#227 - +#241#24'/'#216'qn'#31'O'#0#222#194#251#241#223'8@'#28#255#137#199#185#191#227 - +#137#4#187#157#224#239'#'#2#136''''#186''''#134'nH'#193#129#247''#14'<! '#25 - +#172#147#250'{'#25#173#24'#'#128','#0#133#158#28'p'#199#224'v'#26#10#227'b' - +#220'+'#232#254#174#2'/'#19#11#171#188'S'#176'i'#175'P'#200#153#192'''o'#227 - +#166'R'#169'A'#173'1'#128'Z'#173#3#149'V'#15'J'#165#26#31'S'#226'cJ~'#143#130 - +'.W'#224'm'#5'w'#155'i'#10#144#212#20'8'#1#5'v'#131#9'6'#9'q,'#202#136' '#22 - +#231'H!'#22#141#178#219#177'H'#4'"'#17'?'#132#131#1#8#135'|'#16#9#7'!F'#143 - +'#'#25#196'bqF'#10#220'>'#193#238#139#11'['#146'(R'#9#161#139#217'A&'#195#10 - +#220#158'G2'#248'E'#234#239'k4a'#140#0'2'#4#20'z%'#238#246#7'N'#232''#143'{' - +#29#221'/'#172#230#226#21'^,'#240'$'#172'$'#224'J'#165#2#148#180'W)A'#163#214 - +'3'#1#215#144#176'ku'#160#209#242'B'#175'V'#129#10#159#175'T'#202#217's'#233 - +'u'#10#185'p'#172'.'#26#129#130'3'#21#184#251#216#149'p'#23#202'Vn'#224'Vo' - +#129#4#226#156#128#198'x'#1#142#197'8'#161#142'E'#19#16'E!'#143'D'#227#16'Eb' - +#8'!'#9#132#131'~n'#11's'#251#16'#'#135'0>/'#134#207#225'6'#129#16#24'A'#136 - +#8#129#206#215'U;'#192#251#215#224#238#5#220'^B2h'#150#250'{'#28#233#24'#' - +#128'4'#3#5'.'#238'N'#197#237'D'#20#190'b'#186'O,'#244#130#170#174#144#209 - +'*.c'#2#175#228#133'^'#173'T'#162'P'#171'Ao'#178#130'^o'#3#157#209#10'Z'#29 - +#10#187'J'#129#171'=nL'#208#233#182#156#221'V)'#21#201'=#'#11'%G'#0'D$r9g' - +#247#211#185#146'&'#3#179#8'd)'#206'AnE'#134#20'5'#30'e'#149#187#29#231'6"' - +#128'h'#156#132'9'#158'$'#128'H'#132#246'1'#238#182'h'#31#198'}8'#24#130#128 - +#175#3#252'^'''#248'}N'#8#5'}'#236'1'#129#16#162#177#174#132#16'g'#218#1#153 - +#24#220'5'#177']'#12#175#225'c'#224#200#224'M$'#3#191#212#223#237'H'#196#24#1 - +#164#1'('#244#21#184';'#25#183'SQ'#184'v'#162#251#186#21'z~'#133#231'Vv'#18 - +'^'#20'x'#149#10#244'('#232'z'#163#13#12#6#220#27#204#184#226#227#170#175'Q' - +#130'V'#173'`'#194'O'#171'U'#188#192#171':'#133'^'#197#142#195#173#254't'#31 - +'g&p'#171''#210#148#160#235#224#29#132#130#250#159#18#9#224#205#0#178#245 - +#133#21#153#217#251#252#223'$'#155'$'#160#209'8'#183#250'w'#146#0#10'2'#145#0 - +#222#14'G8'#2#160'=m'#161'H'#20#205#132#24#4#195#220#237'@ '#0'~'#143#139#145 - +#1#145'B'#136#204#8#188#159'#'#131#24'gJ'#224'qbq'#193#223#176#131#153#224'E' - +'2'#248'''pd@>'#131#184#212#223#249'H'#193#24#1#12#1'('#248#243'pw='#10#219 - +#17#192'-'#174#12'rN'#210'8'#149'\&O'#170#244'*'#21#9#178#146#9#188#193'd' - +#195'}'#30'nf'#208'jT'#160#19#132#30'7'#29#19'~%G'#4'<'#9#168#149#220#202'O' - +#171'<'#169#253#10#165#12#148'ra'#213#167's'#164#10'>]'#2'wA'#188#192#11#209 - +#129'D*'#1'$U'#239#164'g'#159#255#139#183#215#227#188'3P'#16#206'(o'#14#16#9 - +#196#4'2Hj'#5#164#1#160#208#147#224#135#163'l'#31#8'E!('#222'P'#240#253'>'#31 - +#4#188'.'#240'!'#25#248'<'#14'4'#27#130#248#218'(#'#5'F'#4#140#12'x"'#232#226 - +'3'#192#191')'#25#233'/'#184'='#131'D'#16#146#250'70'#220'1F'#0#131#0#10#254 - +'"'#220#221#128#194'v'#16#253#157'\'#237'y'#207'<S'#195#153#208'+'#217'J'#205 - ,#132#30'Wv'#163#181#24#204#184#233#13':'#20'x'#21'h'#181'x'#191'H'#232'5'#188 - +#224#179#219'$'#252'*A'#221'W$Wx'#177#176#11#231'JQ'#239#229'"'#129#23#9'{JN' - +#128#232#189'$xg '#187#157#232'b'#143#11#130#199#251#8#4'3!'#198#251#10#216 - +'>'#198#147'BLl'#30#136#136#0#133'>'#16'N%'#1#250';'#16#196'-'#20#129'` '#2 - +#30#183#3'<'#174'f'#240'x'#218'Q;'#8'32 '#13'C'#236'd'#20'L'#4#17#17#144#227 - +#240'^'#220#30'C"'#240'I'#253#155#24#174#24'#'#128#1#0#5#159#156'z7'#162'0-' - +#161#191#185#24'<-'#181#220'j'#159'j'#203#163'0'#235't`'#182#20#131#201'V'#2 - +'F'#147#25#244'Z'#21#24'p'#163#189'^G'#130#174'b'#194#175#21'V|'#141#130#9'=' - +#17#6#19#252#164#131#143#183#229#153#195#16'D6='''#224#178#174#241#254'.'#215 - +'-'#147#13#236'k'#22''''#1#137's'#4#128#251#159'#'#2#193'THtF'#16#146#132'@' - +#26'B$'#158'4'#11'B'#145'N'#141' '#200#147'A '#24#1'?'#146#128#31'I'#192#239 - +#167#219#184#5#130#224'v'#181#129#219#217#194'i'#6','#242#16'c'#14#199#164 - +#137#16#139#167'h'#5'x'#187#29'/'#233'~'#220#30'F"'#232#144#250'72'#220'0F'#0 - +'}'#128'/'#160#249#29'p+'#254#174't'#31#23'^'#239#12#209'1!Ur'#234#189'V'#171 - +#1#147#165#136#173#244'&K'#30'/'#236'j0'#232'T'#201'M'#199#147#128'V'#195#169 - +#248#156#208'+83A'#201#31'O.'#178#231'{'#17'v'#254'&'#183#239'A'#208#7'"'#255 - +'='#165#254'v%'#133'D'#167#154#176#3')p^'#255#4#243#27#196'D'#142'C'#210#10 - +#194#188'_'#128#153#7'A'#142#8'|'#129'0n'#17#182#249'i'#143'Z'#128#23#205#4 - +#15#18#1#145#129#223#239#193#215'E'#153'f'#192#162#11#188#19#177#139#175#128 - +#146#141#30#193#253'}H'#4#237'R'#255'n'#134#11#198#8#160#7#240'9'#248#199#1 - +'g'#227'S'#178#206#14#130#175#18#236'z\'#189#13'&+X'#243#199#161#224#23#162 - +#144#235'P'#232'U`'#228#5#158#8#192#168#231#132'^'''#8'>'#9#189'Z'#145't'#230 - +'q'#26#132'8'#254'/J'#254#17'.'#170#15'u>['#232#202#17']'#201#128#249#14#4#13 - +'A'#228'?`'#209#4'2'#17'b'#228','#140'32 '#159#0#167#13#136#8#0'5'#2'/'#146 - +#130#159#255#219#227'qA'#135#179#25':'#28#141'hR'#132'92Hj'#5';'#16#129#31 - +#207#255#24#238#239'A"h'#148#224#227#25'V'#24'#'#128'n'#128#194#191#27#238#30 - +'EA'#163#144'^'#138#224'+yU_'#141'6'#188#6'7'#163'9'#15'l'#133#149'`'#177#230 - +#163#176#227'J'#143#130'n'#212#171#217'mN'#232#213'('#244'h'#235'k95_'#205'<' - +#250#188#7'_'#217#25#183#239'N'#173'g'#231#22'.j8|S]'#202#7'RR'#135'E'#230'B' - +','#193#231#20#240#161'Cf"'#136'|'#3'$'#244'^A+'#240#163'6 '#236'}~p'#180#213 - +'!'#17'4'#224#243#130#140#8':#'#9#169'D'#128#231#14#224'%'#220#137#219'_'#198 - +#156#133'=c8'#252#172#178#6#20#252'<'#220#221#137#194'w.'#240'2'#152'"'#248 - +#188#154#175'A'#219#221'd)`'#130'oE5'#223'hP3'#161'7'#25'4L'#232#13#188#202 - +#159#180#241#153#224#139#156'y'#188'z/'#8#190'8?'#165#208'g'#128#182'{'#174 - +#161''''#167'b2'#225#136'O2'#226#180#2'!'#140'H>'#130'X'#167#143#0#137#192'C' - +#194#207#8' '#12'n'#220'{'#188'~p'#182#215#131#203'^'#15'A'#144#249#10'z!'#2 - +#170'X'#188#24'I'#224'?R'#30#185#136#225#253#11'K'#19'x;'#255','#220#238'B' - +#161#203#23#156'{2'#133#160#234's+'#183#22'W|'#147#181#8#242#138'+'#193'l' - +#182#129#137#9'='''#248'&#'#238'Q'#240'uL'#229'W'#177#24#190#134'9'#243'RU|q' - +'r'#14#8#222'z'#254':d#'#248#235#216#161'z0'#25'f'#228'3'#17#197'&'#2'i'#5'Q' - +'^+ '#18#8'F'#152#22#224#245#133#192'C$'#224'#R'#8#129#215#27#0#167#189#1#156 - +#168#21'P'#174'A('#140'D@9'#9'T'#215#192';'#11#227#157#213#141#175#225#238#10 - +'$'#130#6#169'?'#139'\'#194#200#253#197#245#19'('#252#187#224#238'o('#152'{' - +#136#195'y,'#227#142'6'#18'|T'#227'-'#214#18#176#21'U'#160#224'['#153#208#155 - +'I'#232'y'#225'g'#26#128#150#19'|q'#248'N'#156#158'+'#8'>'#147#249#212'z'#222 - +#209#9'Q'#232'1'#233'7'#224#137#128#204#132#8#159'tDQ'#0#166#17#132#200'9' - +#136#155#143#211#8#220'H'#6'D'#4#28')'#4#145#8#26#145#8'j'#193#239#247'!qt' - +#18#1#167'm'#196#5'm'#128#26#156#220#138#219#3'H'#4'Q'#169'?'#130'\'#192'h' - +#253#249#9'Uyw'#160'0^'#128'{'#133#160#238#179'"'#27#222#185'G'#9':'#228#205 - +'/('#169'F'#193'71a'#239'I'#240#5#199#30'K'#203'U'#202'S'#236'zN'#232#187#177 - +#235#199#176'C'#30'BB'#172#25#176#236'CN+'#8'G'#163#157'D'#192#251#4'8m'#128 - +'#'#2#143'@'#4'm'#13#224'h'#169'A'#205'!'#192#136' '#194#155#6']'#204#2'*8' - +#186#16'I'#224#11#169#223#191#212#24#149#191'E'#20#254'Spw'#15#229#234'''Km' - +#21'|J-+'#190'A;'#222'hB'#193#159#4#182#252'"&'#244'f#'''#252'f^'#240#13#188 - +'W_'#219'e'#197'O&'#231#240'R'#223#25#162#147#250']'#231'>D'#193#4'n/'#242#21 - +'$S'#144'{'#210#8#188'!'#232' '#18#160'}'#135#23#218#155#183#130#203#217#4 - +#193'`'#152#249#21#136#8#226#172#246' %'#153#232'y'#220']'#141'D'#208'&'#245 - +'{'#151#10#163#234'g'#137#130'O'#141'0'#31'G'#193'?'#145#254#22'Vh'#5#191#234 - +#171'U*'#180#225'5'#204#185'WP2'#30','#6#29'XL'#26#176#24'9'#2#160'U'#159#9 - +#190'F'#136#225'+9'#167#30#139#219#11'y'#255'\'#213']'#231'''<'#170'>'#226 - +#244'A'#228'@L@gM'#2#249#9'"'#2#17#136#162#6#130'Y'#208#225#9'22'#160#205#225 - +'h'#135#246#166'MH'#10#29#156'6'#192#242#8#226#156''#160'S'#27#160'P'#225 - +#137'H'#2#255#147#250'-K'#129'Q'#243#235'D'#225#159#141#187''#160#144'N'#17 - +'V}'#5#159'i'#199#169#251'h'#215'['#11#160#176't2'#218#251'f&'#244'V'#163#22 - ,#204'D'#0#6'-S'#251'){'#143#4#159#203#244#227#146'u'#196'e'#183'];'#248#140 - +'a'#232#16#151#11#11#230'A,'#193#167#30'G'#184#228'"'#230','#196#205#195#155 - +#5#29#222' '#18'A'#136#237#221'H'#8#237#173#181#224'l'#217#14#254'`'#16'B' - +#225'0'#159'f'#28'Oj'#24#192'U'#30#222#130#251';G['#161#209#168#248#165#162 - +#240#159#135#187#7'PH'#181'2'#190#6'_H'#217#165#144#158#222'`'#196#21'"'#228 - +#229#151#178#149#158'V}+['#249'9'#193'7'#232#213'l'#213#167#172'='#242#234's' - +#9';\'#201#173'Pp'#195'>'#204'Q'#241'iJ'#131#206'DDn'#229'N'#166#31#147'F'#16 - +#229#170#16')'#143#128#210#138#189#188'I'#224#18#17'A'#135#219#11'mM'#155#161 - +#195#209#130'&'#4#146#0'%'#19#9#209#130'Nm'#224'#<'#193')H'#2#173'R'#191#223 - +'laD'#255'd'#249'N<'#143#137'U~'#193#214#167'D'#30#29#174#250'yE'#168#238#23 - +'O'#0#179'Y'#199#4'>'#185#242#27'9[_H'#217'e'#169#186#138#212#226#155#222#26 - +'m'#142'!3'#224#139#21#147#14#195'd>A'#148'K7'#230'2'#11'9"'#224#28#132'H'#4 - +'n$'#0#242#13#144'Y`o'#133#246#230'M'#224#243'x:'#181#129'h\'#28')h'#194'3' - +#156#140'$'#240#153#212#239'5'#27#24#177'?]^'#229''#141#186#234#138'='#252 - +#172#180'VEN>#'#148#148#239#4'V[>'#191#226'k'#25#1#144#202'O'#241'}'#178#245 - +'uj~'#213#23'j'#237'E^}'#246#225#141#216'O/'#247#145'$'#2#232#236'AH)'#199'Q' - +#190#0#137#10#142#168#208#200#231#227#136#128#132#223#229#9'2'#31'A'#135'''' - +#0#173#13#191#177#208'a'#144'O-'#142'&C'#134#236#200#184'K'#220#142#251#255 - +#27#233'&'#193#136#252#9#163#240#255#1'w'#247#139'U~'#174#147#14#197#244'U`' - +#177#21'Cq'#249'4'#176#153#13'('#252'Z&'#252#164#242's'#153'|\'#234'.'#173 - +#250'\5'#30#223'aG&'#235'6'#23''#12#210'"'#145'd'#1#224#27#152#242'D'#16#137 - +#179#162'#"'#2#31#239'$'#228#132'?'#4'N'#158#8#236'm'#13#208#214#248#27'j'#11 - +'A'#150'D$D'#10'D&'#193#167#192'i'#3'#'#182'5'#217#136#250'%'#243'*?y'#249'O' - +#160#191#197'*'#191'FM'#130#173#129#252#210'j(,'#169'bj'#190#213#162#5#155'I' - +#155#12#241#137#227#249'B'#18'Ogn>$k'#236#199#144'['#160#214#229#157#185#4 - +#188'Y'#192#218#152'qY'#133#212#153'('#192#215#23'0'#223#0#145#128';'#128#166 - +#1#238#157'Nh'#169'_'#15'^O'#7#231#27#216#209'$h'#1#206'/'#240#177#212#239'3' - +#19#24'1'#191'f'#20'~'#26#139#245#30#10#255#188#174'I=\\'#159'T'#254#25'`' - +#203#207'c+'#190#205#164#3#171'Y'#203#135#247#212#172'XGH'#221'e'#13'7'#146 - +#234#190'h'#229#151#250'M'#142#161'G'#136'|'#132#220','#131'8'#223#201'('#193 - +'7)'#9'sNB'#210#6#220#188'9@'#190#1#167#155#246#254#164'I'#16#224#27#146'DY' - +#155#244'$'#9'P'#214#224#217'H'#2#207'K'#253'>'#211#141#17#241#155'F'#225#159 - +#128#187#255#160#240'O'#18#132#159'b'#243#228#229'gi'#188#182'"T'#249#167#163 - +#192#163#202#143'B'#159#135#155#133'_'#249#141'|}'#190#154'o'#192'A'#141':' - +#229'2Q'#231#220#17#241#9#141'B'#240#13'L'#200#128#23'Z'#153#9#185#3'T['#224 - +#241#134#185'('#1'j'#1#14#212#6#220#168#21#180#183#212'C['#243'&'#214#152#132 - +#202#142#133#156#1#222'/'#128'<'#144'X'#134'$p'#143#212'o-'#157#24#246'?o' - +#222#217#247#1#10''#137#216#222#167#226#29#157'V'#203#210'x'#11'H'#229''';' - +#223#204#169#252't'#155'e'#243#233':='#252#201#10'='#161':O'#234'76'#134#161 - +'#'#193#165#26#11'-'#200#163#188#147#144#186#19#177#188#1'_8'#169#13'8'#153 - +'F'#16'@'#147#160#3'Z'#234#214'1'#147#128#146#135'('#5'YD'#2't'#172#187'qw' - +#237'H'#153'l4'#172''#231'('#252'4a'#231'-'#20'~'#139' '#252','#163#143'T~' - +#131#1#138'+f@^^A'#167#224#155#185'0'#31#9'?+'#213#229#227#250'B'#231#29#232 - +'R'#142';'#134#145#1'q'#163#211'd'#164' '#202#165#19#11#225'Br'#14':'#220#28 - +#9#208#214'Z'#191#1'\'#246'&'#212#24#194','#170#16''''#191#0'$M'#130#231#240 - +#128#231#140#132#130#162'a'#251'sG'#225'?'#10'w/'#161#240'k'#132#240#28#169 - +#252'j'#170#213'7'#153#160#164'r6'#216'lff'#235#219#204#218#164#189'/'#180 - +#228'R'#243#173#183#200#209''''#212#225#143#9#255#200#133#144'H'#148'lJ'#130 - +'&A'#24#237#252#16'_n'#204#162#4'nN'#27' '#147#128#162#4'-'#13#155#192#209'Z' - +#203#234#9'('#162' '#174'%@'#18#248'7'#30#238'8$'#129#128#212#239'm('#24#150 - +'?y>'#204'G%'#188'r'#193#211'/8'#251#168#15'_i'#213'L'#176'YM'#144'G'#206'>' - +#139'.'#25#226#211#179'&'#29#202'd'#223'='#161'D'#151#251' '#134#229'G1'#134 - +#1'B<'#216'Th^J+'#188'@'#2'd'#18#144'c'#208#217#17'`'#251#182#230#237#208#214 - +#180#9#130#129'03'#7#186'D'#8#190#194'C'#30#142'$'#224#148#250'}'#13#22#195 - +#238'W'#143#194'3'#10#238'mt['#16'~5_'#193'g'#177#22'@i'#229'L'#176'Z'#245 - +'L'#240#137#0#200#233#199#132#159'o'#201'%'#30#160#193'>'#128#177'e'#212'A<' - +#2']'#156'<D~'#1#175#136#4#28#29#1#22')hk'#173'G'#147'`#'#4#130'!|^'#164'+'#9 - +'Pi'#241'A'#195#181#209#200#176#250#245#163#240#255#5#5#246#26#186#205#166 - +#226'*'#185#250'{*'#228#177#229#149'@Q'#229'N'#144'g&'#149'_'#199'b'#252'V' - +#163'P'#196#163#226'b'#251#226':}'#217#152#240#143'f'#8'$'#16#231'{'#21#198 - +#248#150'd'#20'%`$'#224#225'5'#1#182#5#160#189#189#5'Z'#235#214#131#207#31 - +#224#194#132#148'4'#212'I'#2'5x'#168'E'#195#145#4#134#141#4#160#240'_'#5'\' - +#13'R'#248#201'{'#207#132#191#160#2#138#203#167#160#170#143#171#190'E'#155 - +#20'~'#202#234'#'#225#23#242#248#147'^~'#24#157#246#190#215#235#134#143#222 - ,''#19#190#253#234#19#216'^'#179#25#206'8'#247'J8'#232#208'c'#164#190',I'#209 - +#217#180#148#139#18'DDEEIs'#192#205#153#3'v'#135#29'Zj'#6#159#207#151'lH' - +#218'E'#19'X2'#220#204#129'a!'#6'|'#3#143#231'ed'#244'w'#17#254#252#226'j(*' - +#171'f'#161#189'<f'#239'kYn'#191#144#210'+.'#226#1#217#232#246#242#31#184'x' - +#10#174't'#169#142#235'Ysv'#131'?'#223#255'<'#168#213'j'#169'/O2p='#10';[' - +#146'Q'#203'r'#150'/ J'#26'b&'#1#31'&l'#222#190#150#145'i7$@>'#129#253#135 - +#147'c0'#231#197#1#133#159#198'o'#189#141#178#175#18#219#252':'#173#26#10#203 - +#166'@Aq%'#243#240''''#133#159'<'#253'$'#252#201#10'>'#174'tW6'#22#219#135 - +#223#29'0'#11#2#254#29#167'hi'#181':'#184#231#161#151'`'#234#244'YR_'#162'd' - +#224'9 '#233#23#136'D'#184#209'f'#228#28'd'#21#133#148''''#208#17'd'#161'B' - +#167#203#141'$'#240#19'x<'#29#221#145#192';x'#168'#'#145#4'bR'#191#167#254' ' - +#167'e'#130#239#207#255')'#10#191'A,'#252#180#242#23#148'N'#130#162#210#241 - +'I'#225#23'r'#250#5#225#231'Fi'#203'E='#249'r'#250#173'f'#5''''#31#181#16#218 - +#219#186#175'kQ('#148'p'#239'#'#175#192#244#25#187'H}'#153#146'AhP'#10#2#9'D' - +#185#225'%b'#18'pv'#8#155#27#154'j'#214#128#215#227'fm'#201#187'8'#6#159'F'#2 - +'8['#234#247#211#31#228#172'T'#160#240'O'#193#221'W('#184#5'B'#146#143' '#252 - +#249'EU'#156#205#207#132#159#203#235'gi'#189'z.'#179#143#28#131#138#148#148 - +'^'#24#157'F'#23','#191#252#20#248'i'#245#183'=>N$p'#215#131'/'#194#140#153 - +#243#165#190'T'#233#144#28'f'#2#201#129#167'D'#2#212''#144#230#18#8'$@'#230 - +#128#195#233'B'#18#248#17'|>2:'#192'&'#21'q'#14#198'?#'#9'\'''#245#219#233 - +#11'9)'#21'('#252#165#184#251#26#133''#188'8'#195#143'9'#252#10#199'AI'#197 - +'N,'#185''''#143#247#246#179#236'>'#157#154#13#215'd'#194'/j'#211#149#179'oR' - +#2#216#219'Z'#224#180#227#150#224#15#180#231#18'w'#185'B'#1'w'#222#251'<'#204 - +#156#179#155#212#151'+'#9#196#195'P'#133#206'CD'#2'!VC'#16'e'#221#135#133'""' - +#202#21' '#199' i'#2#1#170#31#160#241#230#204#28'H'#8'$@s'#8#238#151#250'=' - +#245#134#156#147#13#190']'#247#255'Pxg%'#133#31''#148#26#190#168#167#140#226 - +#252#164#242'S'#156#223',R'#251#213'JP'#170#184'>}c!'#190#158'q'#255']'#215 - +#194''''#31#190#217#235's'#228'r'#5#252#249#129#21'h'#14#204#149#250'r%'#133 - +#16'*'#20'r'#5'h'#132#25#149#21#139#205#1#210#4#236#237#173#208#178#253'g'#8 - +#132#130#140'('#132#218#129#4'7R'#249#148#134#159'^zI'#234#247#210#19'rJJP' - +#248#201#21#253#17#141#223#22#170#250#148'|'#134#159#217#146#7'%'#19'fC'#158 - +#201#0'6'#171#150#197#250#217#202#143#194#175#213'(S'#187#246#228#212#187#202 - +'-'#196#227'Q8'#254#176'y'#16#194#31'ko'#208'hu'#240#220'k_'#130'^o'#148#250 - +#146'%'#133#216'1('#152#3#204''''#224#237'L'#27'&'#231' '#151','#180#129#235 - +'0'#20'I!'#129#8#30#225'0$'#129#156#28'M'#150'S'#162#130#4#240#16#10#255#197 - +#226#146'^'#141#138'r'#251'-P:a'#23'\'#249#141'l'#213'g'#194'o'#226'V~-'#133 - +#250'D'#243#246'r'#234#13#229'('#222#254#231#179#240#244#223#255#220#231#243 - +#202'+'#171#225#225#167#222#147#250'r%Grr'#17#175#9#4#187#144#128#144''''#208 - +#218'T'#3#246#166#205#157'$'#208#217'O'#192#145'H'#196'vi\'#251'J'#173#212 - +#239#165'+rF^'#248#226#158''#138#19'}'#168'A'#7'5'#242'('#173#154#11'y63W' - +#213#199#215#242#147#195#143'*'#250#132#129#28#178#177#149'@8'#251#132'%' - +#224't'#244'='#15'c'#191#131#143#133#11#175#184']'#234#203#149#28'B1'#17'9' - +#249'"|'#187'1.O '#204'J'#137#137#4':P'#19'hi'#220#4#246#214#237#16#162#198 - +'"'#169'ME'#190#142#4#28'K[{?"'#245'{'#17'#''D'#134'o'#232#241'#'#149#245#10 - +#225'>'#18'~'#157'^'#15#165#227'w'#1#155#205#154't'#248'Y'#153#218#175#225 - +#235#248';'#213'~!'#183''#12#253#195'/?}'#7#183',;'#163'_'#207#189#234#134 - +#251'`'#193#146#131#164#190'd'#201#145#28'Y'#22#227#204#1#210#4'|B'#155#177 - +'dw'#161' 4'#213#173#131#14#190#148'85<'#24#191#27'M'#129'k!e'#144#186#180 - +#144'\jx'#187#255'K'#20#254']'#187#134#251#138'+w'#134#130#130#210'd'#172#191 - +#179'}'#151#138#235#207#207#183#232#22#6'}'#140'a`8'#239#228#165#168#5#244 - +#221#2#159#194#131#15'='#253#1#20#22#149'I}'#201#146#131'K'#27#230'5'#1#190 - +#185#8'M.'#166#218#1#18'~'#135''''#192'Z'#140'5m'#253#17'<ng'#215#200'@'#130 - +#247#7#188#15'9B'#2#146'K'#13#18#192'}('#252#151#167'8'#253'P'#181'O'#198#250 - +'M'#156#205'/.'#233'U'#11's'#248'd|'#168'O'#226'wq'#238#137#139'Y'#7#25'6/@' - +#174#128'3'#254#176#28'U'#231#227#164#254'h'#251#196'#'#247'\'#7#255#251#244 - +#237'~='#215'j+'#128'G_'#248#140#17#244#168'G'#2'R'#134#146'P'#218#176#143'u' - +#29#230':'#14'S'#21#161#147'r'#4#182#173'b'#237#197#186'8'#5#219#19#241#216 - +'.'#141'?'#191'R/'#245#219' H*:('#252#191#199#221#191'R'#236'~'#230#241#183 - +'A'#217#248#185#184#242's'#225'>'#202#242'3'#25#185#149#159#230#241'q'#133'=' - +'\'#168'Oj'#6#251#237#215#31#225#230'kNM'#185#143'H'#224#222#199#222#134#210 - +#210'*'#137#175#174'wl\'#183#26'n'#185#246#180'~?'#238#174'{'#193#178'['#30 - +#145#250#178's'#2#180'|s-'#200#169#187#16#154#3'An"'#17#235','#212#17#20#149 - +#17#175'Ov'#27#22#249#3#190#12'y['#246'n'#223#242#177#228#29#133'$'#147#31#20 - +'~'#146#14#178#251'm'#226'4_='#218#253'e'#19#230#161#221'oa'#130#207'9'#253 - ,'h'#229'W'#179#22'^\I'#175'<9r[j'#220'y'#243'y'#240#243#143#223#236'pqi'#5 - +#220#247'xnz'#208#219'['#155#224#213#23#30#128'5?|'#1'>'#175'{@'#175'='#243 - +#130#27'`'#255'CN'#144#250'-H'#14'![0'#193#135#7'I'#192'i"'#145#208'^'#140'5' - +#20'Am'#160#185#238'Wp'#182#215'C8'#196'u'#26'&'#194#224#15'pg'#253'O+n'#0 - +#137'M'#1'ID'#8#133'_'#5'\'#178#207#30#226'L?'#157'F'#3'E'#21';'#163#250'_' - +#146'l'#229'%T'#246'Q3'#15#214#194'K)'#227#166#239#230#130#244'#'#206'8z.D#;' - +':v'#149'J'#21'<'#251#198'j'#169'//'#137'p8'#12#239#188#254#4#252#247#227'7' - +#193#209#222'2'#232#227#200#240#187':'#253#188#235'`'#191'1'#18'H'#166#13#11 - +#227#203'Y'#162#144'P7'#144#236'%'#224'GS`5x'#221#174#29#252#1'h1'#30'R'#183 - +'f'#197#135' !'#9'HE'#0'w'#161#240'/'#163#219#10#161#139#175'F'#9#249#133#227 - +#161#168'|2o'#247'wv'#239'e'#225#190'd'''#159#220'q'#248#213'l^'#15'7]'#221 - +#179' <'#241#202#183#160#213#234'%'#189#198'`'#208#15''#185#245#15#176'y' - +#227#207#189#166#0#15#20#147#167#205#134#229#183'?'#1'j'#181'V'#210#247'''5' - +#18#162'a'#165'\'#142#0'?{'#128'H'#128#138#134'<\'#205'@'#179#216#31#208'i' - +#10#180'i'#212#138#217'['#191''#190#25'$"'#129#172'K'#18#10#255#28#220#253 - +#128#4#160#16#199#251#141'f'#206#238#183#176#6#158#188#221'o'#224'j'#250#233 - +'q*'#238#145#201'd9'#21#239''#255'_'#207#193'+'#207#253#181#199#199#175#189 - +#237'q'#216'i'#214#238#146']'#223#166#13'?'#193#221'('#252#161'Pf'#202#211 - +#213#26'-\t'#245#221'0g'#254#18#201#222#163#212'H$'''#18#9#21#132#220'\'#194 - +'dk1'#26'C'#198'g'#10#182'5n`'#13'F'#187#148#15#191#220#240#211#10#234'w'#193 - +#15'8'#203'.'#178'*J('#252't'#190#175#197#170#191'`'#247#151#140'G'#187#223 - +'jN'#246#239#167'Q]'#212#193'W'#173#22'M'#229#205#21#201#231#241#183'{'#151 - +#193#247'_'#247#156#225'y'#197#13#15#195#172#185#139'$'#185#182#230#198#237 - +'p'#253#165'G'#166'u'#213#239#9#187'.'#216#31'.'#188#234'nI'#222'g'#174'@(' - +#30'bN'#193#8#231#15#160'Qd'#212'i'#216#201#166#19#147'?`='#184#218#27'v('#26 - +#146#203#18#251#213#173'y'#137#166#17'g'#157#4#178'M'#0#231#2'7'#187#143#169 - +#254#148#234#171'U'#171'Q'#237#223#9#237#254'q,'#212'G'#4'@'#19'z'#141#186'N' - +#187'_'#220#202'+'#151'p'#203'U'#199'A}'#237#166#30#31'?'#245#188#27'a'#233 - +#254'GKrm'#203'/:'#12'W'#157#236#181#168'3'#154#173'p'#205#205#143'Cy'#213'd' - +'I'#222'o.@p'#10'F'#226#220'(2'#214'iX(!&'#18#240#248#161'q'#235#15#224#247 - +'y'#216'0R'#145')'#176#161#208#28#223'e'#205#23'/'#135'`'#164#18#0#10''#1 - +#238'6'#162#240#231#145',+'#168#194#15#5#220'l-'#130#210#170#217#220#136'nQ' - +#129#15#169#254#201#210#222#28#136#245'w'#135#171#207';'#0':\'#237'=>>{'#222 - +#18#184#248#218#236'W'#131#174#250#246#19#248#251'_'#175#201#250'y'#169#148 - +#248#162'k'#254#138'Z'#207#226#172#159';W@>'#129'x'#12#152#199#159':'#10#249 - +#168#183#160#159's'#10#18#17#216#219'['#160#165'v-'#155':'#20#21'B'#131'@?' - +#239#196#245'{L'#14#255#229#181#215'^'#139'C'#22'I '#155#4#240'4'#10#255#153 - +'B'#194#15'e'#242'i'#181#26'('#171#222#21'l'#22'+X-'#26#150#227'o'#210#11#241 - +'~E'#206'W'#247']y'#206#190#224#245#184'z|'#220'`'#180#192'}O}'#154#245#235 - +'z'#230'o'#183#192'7'#255#253#183'$'#159#9'iw'''#156#185#12#246'>0'#247#19 - +#161'2'#1'nE'#231'z'#11'R'#166' '#181#26'gIB'#194'@R'#26'8R'#251#11#184#28'M' - +']K'#135'}'#10'Yd'#230'YG'#236#180#253#214'[o'#205#154')'#144#21#209'B'#225 - +'_'#136#187'/'#196'M=)'#225''''#191'x'#2#20#150'M'#228#236'~'#190#159#31#27 - +#209#205''''#251#200#20'|'#154'o'#142#142#228#190#242#156#165'L'#157#235#13 - +#203'n'#22#170''''#207#204#234'u'#253#241#186#147#160#174'f'#163#148#31#13 - +#236'{'#200#201'p'#236#169'WJz'#13'R!'#217'e'#152'2'#5'Q'#19#160#162'!'#174 - +#155#16'_='#232'rC'#211#214#239'!'#16#12'tI'#16#138#191'5'#217#214'x'#204#231 - +#159'.h'#1#25''''#129#140'K'#22#10#191#18'w'#171'Q'#246'g'#138#29''#6#163#9 - +'W'#255#221'P'#232'u'#220#234'o'#20'B~'#156#221#175#148'Q?'#191#172'\'#226 - +#160'q'#229'YKX'#152#173'7L'#156':'#27#174#186#229')v'#155#190#232#141#235 - +#190#135'_~'#252#2'j6'#255#2#173'-u'#16#14#5#161#176#184#28'f'#238#178#24#150 - +#236','#216#242#139#135'|]w\{<4'#214'm'#145#250#227#129'='#150#28#6#167#157 - +''#171#212#151'!'#13'(*'#144#224#187#9#133'R'#203#135'iko'#222#14#237'M'#155 - +' '#28#230'M'#1#222'!'#168#144#197#143#216'}r'#244#223#217'2'#5#178'A'#0#201 - +'~'#254#130#227#143'V'#255#226#138'Y'#144'_X'#194#170#251#204#204#235#175'f3' - +#251'4'#194#164'^'#5#191#242#231#174#252#195#21'g.'#198'/0'#216#231#243'*''L' - +#135#246#214#6'^['#232#253';'#173#154'8'#3'.^'#246' '#232#141#230'A_'#215#223 - +#239#189#18'~^'#253#133#212#31#15#195#137'g]'#7#139#246'=J'#234#203#200':' - +#196#249#1#194#172#1#242#7'P4'#192#229#14#129#27'I'#160'q'#219#15#204#132#236 - +#146#27#176#173#196#26#155'S]'#20#243'e'#131#4'2*^('#252#229#184#251#21#133 - +#223'(8'#254'h'#245'7'#219#138#161#164'r&'#179#249#201#243'o'#22#169#254#148 - +#240#195#138'j'#134#193#140#238'+'#206'\'#4#209'H8'#237#199#165#234#187#227 - +#207#188#22#246'\'#250#251'A'#189#254#173#151#31#130#143#223'}A'#234#143#135 - +#129'4'#190#229'Z'#1#165#229#19#165#190#148#172'#9'#131#144'R'#133#249#209 - ,'cn'#26'I'#206#180#128#16'8'#236'm'#208'R'#187#134#213#10#136#27#138#202'e' - +#137'?'#21')6'#222#186'j'#213'*j-'#158'QS '#211#4#240','#238'N'#231#194'x\' - +#165#159'N'#167#129#210#9#187#162#218'o'#6'3o'#247#155'x'#213'_'#165'Rt'#198 - +#251's\'#248#9'W'#158#177'p'#135'A'#27#233#2'}'#6'W'#160#233'@'#26#193'@'#225 - +#180#183#192#173#151#255'N'#234#143''''#9#157#222#8'w<'#244'>(G'#227#240#145 - +#132#184#167' '#167#5#176#198#162#158#16#155'@'#220'Z'#255'+t8'#26#152'C0'#22 - +#139#9#164#17#210'(";W'#154'[k>'#255#252's'#129#4'2'#130#140#137#25'_'#236 - +#179#25''#200#202'd'#147#15'\'#225#243'J&Aa'#201#4'&'#248#22'>'#219#143#188 - +#254'B'#194#143#208#214'K6'#28'4'#128'3'#22#160#234#150#185#249#15#148'F'#252 - +#199'G>'#4#165'j'#224#130#243#199'k'#143#131#214#166#237'R~<)'#168#24'?'#13 - +#174#190#253'9'#169'/#'#171'H'#206#31#20'G'#5#130'\'#150' E'#5#152'&'#224#246 - +'A'#211#182#149#16#240#7#146#25#130'd6'#160#22#240#216#30#147'#'#151#226#203 - +'c'#153'4'#5'2I'#0''#195#221#5#194#234'O'#171#187#222'`'#198#213'>'#174#252 - +#186#164#234'O1'#13#175#250#11#173#188's5'#236#215#21'W"'#1'd:'#211'n'#206 - +'n'#251#194#233#23#253'q'#192#175'['#245#205#135#240#226#223'o'#145#234#163 - +#233#22'{'#29'x'#2#28'q'#210#229'R_FV'#145'L'#21'&_'#0#223'T4'#16#226#18#132 - +'H'#3' "ho'#169#5'{'#211#198#174'a'#193#160'Y'#23#157#174#143'ln'#200#164')' - +#144#17'Q'#227#251#250'oCa'#214#8#171#191#150#194'~'#227'v'#130#130#194'q,' - +#233#135'R}'#169#198#159#10'}'#146#173#189#134'Y['#175#171#207'^'#136'_lf'#9 - +'@'#173#209#193#157''#31'\.'#193'u'#231#239#3#225#12#213#1#12#22#231'^y'#31 - +'L'#155#185#135#212#151#145'u'#136'G'#145'S'#130#16#171#21#224'K'#135'I'#19 - +' -'#192#231#245'$'#147#131#226#156'/'#224#129#137#214#198#229#133#133#133 - +#145'Li'#1#153'"'#128'{pw'#149#216#246'7'#24'q'#245#175#222#21'W}m'#146#0'(' - +#215'_'#163'Q'#138'r'#253'3'#253'5'#164#23#203#206'Y'#196#190#172'L'#131#132 - +'f'#234#206#3'/*'#250#199'3'#130#149'_H'#147#16#212#19#242#10'J'#225#250#191 - +#252'S'#234#203#200':'#132#4'!'#161'V'#128#229#6#8#166'@W-'#128#250#8#178'(B' - +#194'k'#209#248#166#203'|'#155'Z'#143'='#246#216'h&'#18#132#210'.r('#252#249 - +#184#219'.'#158#231'G*~~'#233'4(('#174'`'#182#191'9'#153#238#171'J'#233#231 - +'?'#220#176#252#188#197#25#245#1#8#152#188#211#174'H'#2#15#12#248'u~'#159#27 - +'n'#187#252#224#164'-'#154'+'#184#244#198#167#161'|'#252'4'#169'/#'#235'`i' - +#194#162#178'a'#214'U'#152#143#10'Px'#176'i'#203'w'#224#247#251#152#22' D'#4 - +#20#178#248'_'#198#233'jn'#213'h4'#145'L8'#4'3A'#0#255#135#187#27#133#184'?' - +#173#254'z'#189#1#202'&'#238#193#4#159'<'#255#20#243#167#176#31#149#249#178 - +'b'#31#190#189#207'p'#226#128#166#186#205'p'#255'm'#167'g'#229'\&K>'#220'xo' - +#255'z'#247'u'#197#227#247'\'#10'[6'#172#202#230'G'#211'''*'#171'w'#134#139 - +#174'L'#234#203'H'#162'v'#235'z'#248#224#141#191#179'U'#248#202#219'^'#4'm' - +#6#134#161#236'P6LZ'#0's'#8#134'Xkq'#242#7#216'['#182#131#189'y'#19#203#11#16 - +'E'#4':'#10#13#222#25#165#150'p'#187#209'h'#140#166#219#20'H'#171#200#161#240 - +'S'#246#10#173#254#214'd'#193#15#10'y^'#233#20#200'/'#170'd'#4'`'#225'+'#253 - +#132#213'_.'#204#241'K'#251'G'#158'Y<'#245#192#149#176'i'#221#202#172#156#139 - +#138'l'#254#244#232''#7#245'Z'#183#171#13#238#188#246#168#156#210#2'hq'#184 - +#225#158'w'#192'h'#178'Jz'#29'?'#175#250#12#222'z'#233#175#224#245'8'#147#247 - +'Yl'#133'p'#237#157#175#225'oR'#153#246#243#9'#'#200#147#29#132#216#188#193#8 - +'x<H'#0'>'#242#5'P'#181#224'w'#16#164#136'@'#172'S'#11'P'#202#227#255'7'#181 - +#208'~'#151#221'n'#15#167#219'!'#152'n'#2#184#30'w'#20#175#254#172#183'' - +#245#238#184#234#235'p'#245'Ws'#157'}'#249'b'#159'd'#216'o8-'#253#192#133'tn' - +#190'x'#31#136'g('#7#160';\'#247#151#183#152'&0'#24'<Cd'#181'>;d'#213'_'#204 - +#156#191#15#156'x'#174#180#3'G'#222'|'#225'.'#248#254#203'wv'#184#191'x\5\v' - +#243#243#25'9'#167'0'#130'<'#22#19#194#130#145'd'#243#16#210#4#218#155#183 - +#130#179'uk'#215'r'#225#246#170'<'#255',u'#194#233'L'#183'C0m'#146#135#194'O' - +#189#175'jP'#152#11#133#156'J'#249#205'+'#158#4#249#197#227'q'#245'Ws'#131 - +'<uj~'#168#7#151#244#3#195'p'#144#231#23#31#190#4#31#190#249'hV'#207#249#251 - +'S'#174#129']'#23#13'.'#185#199#231'q'#193#159#151#253'.'#167#180#0#202'v' - +#188#229#161#143'2'#178#210#246#23'_}'#252'*'#188#255#250#195#221'>6'#209 - +#225'p'#196')'#203'2r^'#241#152'1'#234'#'#232#167#228' >*@Z@'#211#150'o!'#24 - +#10'B4'#210'Y#'#160'R'#196'o'#24#167#171'}'#200#229'r'#133#210#233#16'L'''#1 - +'\'#134#187#251#133#22#223'JV'#238#171#195#213''#15'0'#25#185')'#190#228#249 - +#215#137'W'#255'af'#247#11#248#203'uG'#130#167#195#158#213's'#206#156#191'/' - +#28'w'#214#224#227#250#143#254#249'\h'#172#149#182'B'#176'+'#14':'#234'"X' - +#184#223#241#146#157''#235#198#213#168#29'u'#159#151' '#147#201#225#218#187 - +#254#5#6'cf'#204#148#148#185#2'4v<'#16#6#143#151'#'#129#246#166#205#224'j' - +#171'I'#169#17#192''#154'&'#216#218#230'DC'#30'O:'#29#130#233'$'#128#159#196 - +'#'#189')'#231#223'VT'#13#249'%'#213','#219#143'y'#254'u'#157#147'|'#5#219'' - +#184#161#177#246'7x'#236#174's'#179'~'#222#130#226'J'#184#228#230#193#231#247 - ,'?'#250#167#179#160#185'A'#250#10'A1'#170#167#205#131#211'/'#249#235#208#15 - +'4H'#4#253'^'#184#243#154'C{|'#188'j'#210'l8'#235#138#7'3rn'#161'N'#128#204#0 - +'J'#14#242#5#195#172'd'#152#204#0#183#199#11#205#219'V'#162#22#16'Ji'#29#166 - +'SFO'#221'i\'#224'-$'#128#208#140#25'3'#210#162#5#164'E'#2#199#205':q'#14#200 - +#228'?'#166#14#248'PC'#217#196'='#193'h'#212'%'#195'~]mv'#1#195'L'#5'x'#246 - +#129#203#160'f'#211#154#172#159'W'#161'T'#193'M'#247'<'#232#215'?p'#203#9 - +#224#180'7e'#253#186'{'#131#209#156#15'W'#255#233#13'I'#175#225#214'K'#150'v' - +#186#232#187#193'y'#203#158#128#178#202')i='#167'`'#138'u'#230#5#196'Y#Qj'#28 - +'Bu'#2#20#26'lk'#218#8#238#246':'#22'-'#16#210#131#21#242#196#191''''#231#181 - +#156#145#151#151#231'okkK'#139#22#144#30#2#152'}'#242#189#184#187'R'#156#248 - +'c'#206'+'#133#162#242#25'l'#245#167#141'l'#255#174#5'?'#195'L'#246#25#238 - +#184'|'#191#140#21#0#245#133#195'O'#186#6#230#238'y'#232#160'^{'#207#245'G0_' - +'@.'#129'4'#197#155#30#200'~'#199'$1'#250#250'>+&'#236#12'g]'#249#240#0#142 - +#216'?'#8#26'@gD'#160#179#135' +'#22'r9'#160#173'v'#245#14'EB'#165'F'#215#156 - +'Bs'#188#209'h4'#6#211#161#5#12'Y'#4#11'&'#238#167#212#24#139#235'P'#160'K' - +#196#206#191#194#242'Y`'#205'+J'#18#0'7'#211'O'#148#243#159#246#143'4'#243 - +#168#223#182#14#158#185#255'b'#201#206'o+('#131#139'oZ1'#168#215#222'u'#237 - +#161#16#238#163'y'#137#20#184#225#222#143'@'#174#148#206#17'x'#247#242#195'!' - +#24#240#246#248'8'#249#2#150#223#243'>.j'#233#175'd'#20#198#139#209#198':'#9 - +#135'"\'#15'A$'#0'2'#7#154'k'#190'g'#201'\bg'#160'F'#17#185'~'#130#205#254'x' - +#186#180#128'!'#203#225#184#217''''#29#130#135'yW'#172#254#235'tz('#157#184#7 - +#152#244#26#214#229#135'e'#253#241#171'?'#169#255#220#7#155#246#207'3'#227'x' - +'g'#197#159'a'#237#247#255#25#250#129#134#128'E'#7#158#10'{'#29'|'#230#128'_' - +#247#231#171#15'D{2'#167'F'#211'3\q'#199#27#160#207#144#163#173'?x'#232#182 - +#19#192#237#236'}B'#242#194#253'N'#130#165#135#157#147#145#243''''#135#138 - +#196'83'#128#178#3'='#188')`o'#169#1'g'#203#230'd'#219'0'#190'Jp'#213#228#188 - +#230#223#163#6#224'N'#135'/`'#168'b(C'#245#255'%'#220#159' '#174#250#179#20 - +'TAA'#233'd'#182#242#147#240#179#156'a'#180#215'0'#140#251#11'x'#152'~,'#174 - +#190#199'ig'#18#244#217#157'q'#249'#PZ9'#176'T'#218';'#175#220'/+3'#2#6#138 - +#11#174''#1'l'#133#227'$;'#255'S'#247#156#7'-'#13#155'{}'#142#193'd'#131#203 - +'n'#207'L'#253#2#203#11#192#175'%'#154#156'''@y'#1'\'#207#0#143#199#3#205#219 - +#190#235'j'#6'$'#242#180#158#189#139#140#190'_'#210#161#5#12'I'#18'Kw>'#214 - +'"W'#168#155#240'G'#169#19'w'#252')'#170#154#7#22#139#141#9#191#208#231'O' - +#205#175#254#178'a'#24#247#23'p'#215'5'#251'g'#188#250#175'?'#208'h'#13'p' - +#217'mo'#12'Hu'#206#149'k'#239#138#11'oz'#5#204#214'B'#201#206#255#226#195 - +#151#161'i'#247'K'#159#207';'#227#138'G'#161#164'<'#189#206'@'#1#226'J'#193#0 - +#211#2#194'L'#11' 3'#160#165'n'#13#248#220#246#20'3@'#173#136'>2)'#223#241 - +''''#157'N'#231#25#170#22'0'#20'I'#148#141#155'u'#210'Y('#205'O'#138'c'#255 - +':'#189#25'J&'#236#202'B~F'#3#183#250'k'#133#138'?'#129#0'2'#242'1f'#30'w]' - +#189#159#212#151#144'DE'#245',8'#233#194#254#135#208#238'^v@V*'#23#7#2#250 - +#221','#187#251#163#172#159#215#209'V'#7#171#190#252#23'4'#213'n'#128#182#230 - +'m'#253'j'#235'V5i'#23'8'#225#252#244'O?'#18'$'#150#141#26#143'u:'#3'}'#228 - +#12'D"p'#182'7'#128#189#241#215#20'3@&K'#212'M'#201'k'#222#27'??'#215'P'#181 - +#128#161#17#192#236#147'?'#199#253#18'.'#166#207'M'#248#181#22'q'#153'$'#248 - +#194'`O'#173#134'k'#244#169#16#226#254#195'P'#3#8#6#189#240#208'MGH}'#25')' - +#216#231'w'#23#194#188#197#253'k'#184'y'#239#181#7'f'#165'rq '#160'^'#7#151 - +#221#241#206#208#15#212#7#162#209'0'#252#188#242#3#216#176#230'S'#166#238'G' - +#250#209#200#181'+'#228'r'#5'\q'#231#251#236'w'#158'v'#240#209#0#193#25#200 - +'B'#130#188'/'#192#231#11'@'#243#214'o'#184#190#129#157'f'#0'X'#181#129'S' - +#202#173#190'OQ'#3#240'644'#4#15'?'#252#240#216'`'#180#128'AKb'#217#204#227 - +#170'er'#21#181#252#146#137#213#255#226#234#221#209#246'7'#166#172#254#164 - +#254#211#7''''#31#198#234'['#211'Vx'#238#190'?H}'#25') '#15#245'YW?'#133'6t' - +'y'#159#207#253#235#242#131'r'#142#0#242#138'*'#217#245'g'#18#159#189#243'wX' - +#245#5#229#26#12'='#13'z'#241'Ag'#194#238#251#156#148#145#235'$'#225#23#18 - +#131#184'"!./'#128#234#4#218#234#215#131#167#163#169'Kjp'#236#141')'#5#142 - +#171'U*'#149#203#135',1'#216#26#129#193'J'#163#172'|'#246#201'7'#226#153'n' - +#23'{'#255#141#230#2'('#172#152#205#173#254'z'#21#232#209#12#160'6'#223'*' - +#149#144#246';<'#133#159#176'm'#195'w'#240#230#179'7I}'#25';'#128'<'#232#231 - +#223#248#143'>'#159'w'#255#13#135'd'#181'x'#169'?'#216#251#240#11'`'#151#133 - +'Gf'#236#248#219'6'#174#132'7'#159#185'1m'#199#179#228#149#194#217#203'2'#211 - +#215'P'#152'+'#200':'#8'G'#185#225#162'>'#161'i'#136#179#21#218#235#215#166 - +'$'#5#201' '#209'19'#191'u'#145'\'#150'h'#195#197#215'+'#170#20#28#144#157'7' - +'h'#2'@'#245#159#234'S'#23#139'+'#255#242'J'#167#131'-'#191#12#244'zU2'#244 - +#199#138'~'#134'q'#222#191#128#159#191''#31'>~#'#251's'#254#250#131'I;/'#130 - ,#195'O'#238#157#156#30#188#241'P'#201#18#152#186#3#149'8_v'#251#191'I'#183 - +#206#200#241#253'^'#23'<y'#215')i'#14'}'#202#224#162'['#222#0#181'V'#159#145 - +'k'#238#172#15#232','#21'&G'#160'/'#16'ff@('#20'L1'#3#242#245#254'S'#138#12 - +#30#26#0#209#129#154'@p0'#253#2#6'#'#146#178#162#169#135#234'UZ'#171#3#133'_' - +'-'#206#253'/'#174'^'#128#170#191'.'#153#246'+'#228#253''''#157'9>'#232#163 - +'7|'#247#217#10#248#230#163#204#148#136#166#3#135#156'p=L'#153#181'W'#143#143 - +'?q'#231#137#224#243'8'#164#190#204'$'#170'&'#207#131'#'#207#252'S'#198#142 - +#255#244#221#167#129#219#217#146#246#227#238#182#247#137#176'`'#255'3'#210'~' - +'\'#241' '#145#168#168'y('#133#4'I'#19'ho\'#7#30'gsJm'#128'^'#21'y'#180#210 - +'b'#191#31'I'#193#129'&'#128'o0'#206#192'A'#17'@'#197#156#147#247#195#235#252 - +#143'X'#253#215#234#200#251'?'#159#9#190#1'5'#0#193#249#199#133#254#134'o' - +#234#175#128#207#223'y'#4#214'~'#155'y'#135#213'`'#161'Ti'#224#194'['#223#234 - +#241#241#247'^'#186#3'6'#175#251'R'#234#203#228#175'U'#13#231',%c+'#233#251 - +#175#222#9#155#214#14#174#129'J'#223#215#174#129#243'o~3#'#206'@ae'#167'h@' - +#152#31'$B'#137'AD'#0'N{=8'#155'6'#166#180#11'S'#202'c'#223'O'#202'k;'#7#175 - +#165'='#18#137#184'='#30'Oh'#160#13'C'#6'E'#0'h'#255#255#9#143#190'\'#28#254 - +'3'#219'*'#209#4#152#204#236''#218'h'#188#183'Z'#24#243'5'#12#171#254#186 - +#226#235#15#159#130#213'_'#190'.'#245'e'#244#138#5#7#156#9's'#23'w?'#149'w' - +#203#250#175#224#253#151#239#144#250#18#25#14';'#229'6'#24'?u'#183#140#28';' - +#232'w'#195#147'>'#161#215#2#159#161'b'#202#172#189#225#128'c3'#215'+'#128 - +#181#12#19#204#0#158#0'|^/'#180#212'|'#151','#17#230#252#0#16#156'\'#208#182 - +#31#138'Y'#227'`'#157#129#3#149'L'#217#173#183#222'*{'#226#205'M_'#225#237'=' - +#196#197'?y'#227'v'#6#171#173#152#23'~n'#245#231#188#255#178'a'#217#240#179 - +'+'#182#172#255#18'>x%s*k:'#160#213#153#224#236#235'^'#237#254'A'#252#209#252 - +#237#182#195'%o'#10'2e'#246#222#176#255#209#215'd'#236#248#159#188'q/lX'#243 - +'IF'#223#3'-|''_'#246'$s'#10#166#27'I3 '#206#153#1','''#0#9#128#136#128#8' ' - +#24#240#165#248#1#10#244#254'K'#11#13#158#143'Q'#11#176'SH'#16#181#128#240'@' - +#204#128#1#19#192#148#221'N2'#251'B'#178'v'#154#248#211'i'#255#171#160#164'z' - +'O'#208#235'u'#172#232#135#156'Z>'#245'W>'#12#219'}w'#7'ZY'#158#185#235'D' - +#169'/'#163'O'#236'{'#212#213'L'#200#186#195#11#247#158#14'^w'#187'd'#215'V4' - +'n2'#28'}^f'#29#169'O'#220'q'#20'D#'#161#140#191#23'K'#254'88'#233#210#199 - +#211'~'#220#174#237#195')'#26'@$'#16' ?@'#211#6#240'8'#27#187#250#1'^'#172'0' - +#183#223#135'ZA'#171'N'#167's'#15#180'Jp'#160#162')/'#159'}'#226'!'#9#144#191 - +'#'#216#244#20#255#215#27#172'PX9'#151#9'?'#249#0#152#250#175'$'#239#191#208 - +#244'cd'#144#192'cl'#5#205#173'l'#186#174'(.'#159#6'G'#158'so'#183#143#173 - +#252#228'9X'#253'E'#223'!'#195'L '#191'x'#2#28'{A'#250#203'j'#197#248'u'#213 - +#7#240#223'w'#30#202#218'{Zt'#240#249#176#243#238#135#167#245#152'|'#247#31 - +#212#0#184'nA'#194'hq6^'#220#209#4#142#166'_'#187#250#1#214'M'#206'o?'#31#9 - +#160#9#205#0'g('#20#242#15#196#12#24#136'X2I.'#159's'#242#221'x'#222'+'#197 - +#14'@s~%'#235#253'G'#194#175'G'#225#167'a'#31'D'#0'r'#133'l'#216#135#255#196 - +'x'#225#222'S'#193#239'u'#14#253'@'#25#132'Zk'#128'3'#175'}'#181#199#199#223 - +'x'#252'rhk'#218'<'#128'#'#14#29#214#130'r8'#254'"'#154#20#151#153#144#159 - +#128#151#30'8'#27'<'#174#244'{'#254'{'#2#245'5<c'#249#171#168#233'j'#210'v' - +#204'd'#159#0#22#13'H0'#155#223#31#136#242'EB~h'#221#246'M'#151'|'#0#136'T' - +#219'Z'#143'R'#200#162#219#200#25'H9'#1#225'p8'#132'f'#0#17'@'#159#171#213 - +#128#8#224#216'c'#143#149''#253#155#154#218#203#206#21#226#255#10'T'#243#243 - +#199#205#6#147#165#128#9'?'#179#255#169#235#15'S'#255#249#158#255'#'#4#159 - +#191'u'#31'lZ+m'#3#139#254#224#204#235'^'#239#241'GIi'#177'+'#238';'#13#194 - +'A_V'#174#165'l'#252'L8'#244'Tr>fV'#248#219#26'7'#193#191#158#186'2+'#239'I' - +#140#25#187#30#6#11#14'Jo'#134'hgRPg'#159#0#193#20'h'#175#253#129#245#8#16'u' - +#12#134'|'#157#247#230'b'#147#255'}'#148#201#22'$'#128#14'2'#3'P'#3#136'B?' - +#162#1#3#17'O'#249#132'9G'#230#133#19#250'f'#188#173'H'#150#255#170')'#254 - +#191#16#237'~'#174#233#7#197#254'5|'#242#15#215#245'w'#228'0'#128#179'm;'#188 - +#241#152't'#13'A'#250#139#197#135']'#10'S'#230#236#223#235#251'x'#243#241'K3' - +'n'#206#204'^x'#28#204#223#251#212#172#188#231#183#159#190#10'I'#224#183#172 - +#156'K'#12#149'Z'#11#167'-{-'#189#7'Mp9'#1#228#8#140'D'#184'h'#128#16#18't' - +#182'lBS'#160'N<D'#20#244#170#240'['#149#22#199#189#177'X'#172'I'#163#209'8' - +#6'b'#6#244'W:'#153#250'_'#185#203#201#191#199#243#190'!'#216#255#180#194#235 - +#141'yPP1'#135'9'#254#184#216#191#146#13#251'd'#171#191#156#31#243'='#130#240 - +#194'='#199'C$'#156'['#3'7'#187'b'#225#193#23#193#212']'#14#236#245'9['#215 - +#253#23#190'|'#239#225#140'8'#204#228#168#26#239's'#212'r'#168#156#156#153'P' - +'_W'#248#189#14'x'#229'Aj'#146'"M'#132'c'#209#161'H'#184#179#211'[)'#26#231 - +#11#132#136#0#132'>'#1'D'#2'd'#226#216#27#215#165'8'#2'U'#242#216'&4'#3'.' - +#198#219#13']'#204#128'>'#163#1#253'&'#0'R'#255#191#217#164#186')'#145#144 - ,#221'"'#216#255'4'#247#207#156'?'#30#172'E'#19#152#240#147#243#143'&'#1#177 - +#220''#249#200#178#255#5'|'#251#225'c'#240#235#170#247#164#190#140'^ '#195 - +#21#233#31'h'#154#245#175#133#213#170#207'_'#128'u+'#223#198#31'T'#223'%'#177 - +'}'#158#25'5'#194#9#211#23#194'B'#212'@2'#209'B'#171'''|'#242#250#157'P'#251 - +#219#183'Y;_W'#152'l%p'#204#5#233#27'u&D'#2#132#226' V'#27#16#138'23 '#16#8 - +'B'#203#182#175#240#251#138''''#29#129'2Y'#194'7'#173#160#245'd4'#11#234'(' - +#26'`6'#155';'#182'l'#217#18'Z'#183'n'#29#153#1#189#170'y'#253'&'#128#165'K' - +#151'*6'#187#198#189#128#231';A'#236#0#180#149'L'#7#147#181#132#9'?'#167#254 - +'wv'#254'aC?F'#152#6#16#143'Ga'#5'j'#1#180#207'E'#232#12'68'#238#146'g'#7#250 - +#174#224#251'O'#158#133#223#214'|0@'#141'@'#198#178#250'4:3'#20#148'N'#130#5 - +#7'_'#12'jm'#250#231#234#245'z'#229#236#251'8N'#210'JG'#146#135#211#174#253 - +'WZ'#143#201'R'#131'y?'#0#155#29#128#4'@'#27#17'A'#235#182#239' '#28#246#167 - +#248#1#198'['#237#231'j'#20#225#245'J'#165#178#9'W'#167'N'#167#11#244#167'6' - +#160#191#210')'#159'7o'#158#162'9:'#141'hv'#174#152#0#10'*'#230#130#201'le' - +#194#175#213't'#134#255#134's'#235#175#190#240#213'{'#15#194#230#28'u'#6#142 - +#199#21'x'#175#223#15'>'#209#134#242#29#234'6}'#7'M'#219#215#178'N4'#241'X' - +#132'U'#17#146#160#145#160'['#242#203#153#176#23'W'#236#4#230'<'#233'Zy'#9 - +#248#254#147#167'a'#253#247#131#27#156#154'N,='#234'Z'#168#154#178'g'#218#142 - +#215'u'#132#24'5'#10#9#242'Z@{'#253'Oh'#246#216'S'#8#160#216#232#249#147'M' - +#235#251#24#239'k'#196#191#237#161'P'#200#215#159#1'"'#253#145'Pf'#255#31'|' - +#240#193#170#181#141'ymx'#219'$'#142#0#20'MX'#8#6#157#134'['#253#133#226#31 - +#249#200'I'#0#234#14'$'#12#175'=tF'#214'<'#233#253#6'~'#224'G'#254#225'Q0Z' - +#138#165#190#146#172#225#229#191#158#136'Z'#203#192#27'|'#164#27#21#147'wC' - +#18#184'.m'#199'K'#13#7'v'#18#0#141#21'w4'#253#6'^WC'#151#6'!'#254#23#139#244 - +#174#151#209#254#175#139'F'#163'mj'#181#218#211#159#18#225'~'#17#0#217#255 - +#171'6'#199'+Cq'#237#214#164#3#16#237'='#181'F'#143#4#176#27#203#250'c'#171 - +#191'Z'#193#186#2')'#21'#'''#249#167''''#216'['#182#192#7'/,'#203'h'#206#249 - +'@Q9e'#15'X'#252#251#204#228#168#231'"6'#253#248#1#172#252'8'#253#217'x'#131 - +#129'Vo'#129#163'/z&'#173#199#20#210#130#201#214''''#2#16#162#1#29#246':'#232 - +'h'#221#156#18#9'0'#168'C'#159'UZ\'#15#163'|'#214#14'$'#28#216#31#17#149#163 - +#253'/'#175'q'#151#238#23#137#201#223'O'#137#0#24#242'!'#191'b'#22#155#3'@' - +#177'F'#0#138#206#8#192'H'#199#154#255#189#0#235'W'#190')'#245'e0'#144#231 - +#253#152'K^'#204#170#243'Mj'#188#249#232#217#16#240#229'Fb'#150#12''#240'''' - +'^'#153#222'b1a'#148'x'#140#207#7' '#2#8#134'#'#224#235#176#131#189#241#231 - +#148'H'#128'F'#25#253#181#218'f'#191#3#229's;'#254#221#20#12#6#157'J'#165#178 - +#207#226#160'~'#17#192#140#25'3'#148'n'#213#172#11#226#9#249#253'b'#2'0'#217 - +#202#193'V<'#25'U'#255'N'#2'H'#142#252#30#225#26#128#128#207'^'#191#29#154'j' - +#178'?*L'#12#242#190#239'}'#204'MPR5['#234#143'#kh'#220#182#26'>'#255'gnT7' - +#10'8'#225#242'W@'#158'&'#2'f'#170'='#223'#@'#240#3#176'~'#129#148#16#228#247 - +'B'#235#246#149')'#4#160#148#199#237#147#242'Z'#175'E'#185#164#9#221#13#129 - +'@'#192#238#247#251'}&'#147#169#215#226#160#190'D'#148#169#255'^'#175'W'#249 - +'K'#147#237#190'xBv'#129#152#0#172#197'S'#192#130'$'#160#161#8#128'J!'#234 - +#254#3#204#30#29#13#4'@'#248#238#195'G`'#235'/'#210'8'#5#233#251'Xr'#228#245 - +'P6a'#174#212#31'CV'#241#239#167'/'#6#143'3'#183'f'#29#238'{'#252#237'l'#28 - +'^'#186#144#236#15' '#26'%N&@'#24'5'#129#230'-_B4'#18'I'#18#0'">5'#191#229'r' - +#20#203#173#248'w]8'#28#166'Va'#30'Q'#143#128'n'#253#0#253'"'#128#182#182'6' - +#213'fW'#217';'#137#132'l?q'#17'P'#222#184#153'`4'#23#178#213#159#171#254#235 - +','#0#26'%'#178#159#196'/'#223#252#3#183'W'#135'~'#160#1#128':'#213'.8'#236 - +'*('#159#180#187#212'o?'#171'p'#182'l'#133#15'Wd'#174#164'x'#176#152#179#228 - +'t'#152'6'#255'wi;'#30'_'#23#196'|'#0#177'(?D'#20'M'#0'r'#6#182'm'#255#1#194 - +'A/'#171#9#16#136#162#210#226#252#139'A'#29#254#142#252#0#161'P'#168#197'`0t' - +#212#214#214#6'{'#203#7#232#147#0'('#254#143'j'#132#250#199'Z'#243'z<O'#149 - +'8'#9#168'h'#252#238#160#211#25#184#8#0#159#255'O'#17#0'N'#1#24'm'#20#0#176 - +'m'#221#167#240#253'G'#203'J'#205#189#181'p<'#170#253#183#128'Zk'#150#250'm' - +'g'#29#31#189#180#12#28'-'#185'5'#234#156'0q'#214#1'0'#223#244#213#5#8#161 - +'@'#202#7#136#196#249'p`'#8#9' '#24#3'{'#227'/'#16#240#180#165#16'@'#161#222 - +#243'b'#129#193#255'~<'#30#175#161'|'#0'4'#1'\('#187#254#222#242#1#250#146'R' - +#22#255'Oh'#138#205'->'#27#205#196#146'wv'#1'RB'#209#132'E'#160#165#236'?u' - +#151#177#223#195#184#253#247'PA'#171#211#234'O'#30#3'G3'#253'@'#211'O'#4'*' - +#181#30'v^t'#18'L'#154's'#176#212'oU'#18#248#221'm'#240#238#147#231'K}'#25 - +#221'b'#210#156#131'`'#151'}'#206'M'#235'1'#217'b'#194#143#14#139#242'&'#0 - +#249#1'\'#246'm'#224'm'#175'I!'#0#139'6'#240'q'#153#201'M'#163#250#182#225 - +#223#141#184'''?'#128#191#188#188'<<h'#2' '#7'`'#212'0c'#170'7'#164'Z'#203'^' - +#192#19#128'ZoB{g>g'#255#167'$'#0#201'G'#141#237#223#27#130'~'#23#172#254#244 - ,'Ih'#216#250'=2'#248#208#179#212#244'hj'#237#188#231#9'0~'#250'^C>'#214'p' - +#198''#223#248'?h'#169#253'I'#234#203#232#22'S'#230#30#206#204#128'tB'#136#4 - +'P80'#18#163'\'#0#218'"'#172'1'#136#171'ecJ2'#16#170#255'?V'#152#29#143#201 - +#229#242'mx='#154#233#237'}u'#9#234'MTY'#251#175#239#190#251'N'#245'['#171 - +'q~0'#170#254'R'#198#229#246'2'#2#208#26#242#160#160'|6'#18#0'7'#248'S'#171 - +#230#186#255#202'y'#239#255#232#243#2't'#15#250#130'~'#254#250'%'#168#221#240 - +'?'#22#178#234'o'#5#30'y'#246#13#150'"('#169#154#3#147#231#28#2'f'#155#244'Y' - +'wR#'#26#14#194#27#143#158'"y['#179#158'0}'#215#163'`'#214#194#147#211'v<!' - +#10'@{65'#136#252#0'|.'#128#183#163#13#28#141'?'#167#16#128'N'#25#222'Peu<' - +#138'/'#221#138'[m'#28#129#189#18#128#224#0#172#243#20#239#19#140'*'#223#21 - +#212'z'#5#254'8'#181#166#2#200#31#183'3'#174#254'|'#251'o'#214#0#20#197'^'#14 - +#163#214#7#208#31#184#218'j'#160#254#183'o'#160#195'^'#11#177'H'#8'b'#177'0+' - +#196'Qi'#12'`'#180#150#128'%'#191#10'l'#197#213#144'_'#154#153'A'#148#195#25 - +'k'#191'x'#1'6'#254#240#214#208#15#148'!'#204#216#243'x'#216'i'#143'c'#211'z' - +#204#206#148'`.'#31' '#20#226#250#2#248'='#14#176'7'#172'I!'#0#173'2'#178'm' - +#188#213#254#8#222#166'H@-n-'#145'H'#164'#'#136#24'4'#1#212#215#215#171#155 - +'C'#149#191#11#199#148#175#164#16#128#185#24''#164#211'A'#175'U'#163#22#192 - +#141#5#147#141#144#6#160'c'#200'M'#188#251#228'y'#16#240#218#165#190#140#30 - +#177#243#162'S`'#218#252#244'O:'#18'R'#130')'#245#151'B'#129#212#23#192#239 - +#237#128#246#186'U)'#4#160'QD'#27''''#216#218#169#239#218'Vr'#4#162#188'6' - +#163'9'#224#178#217'l'#129#158#250#4#246'J'#0'B'#4#224#151#6#195#201#225#152 - +#226'q'#193#4' '#2#208#163'Jj+'#158#194#170#0#169#11#144'P'#1#152#201#16' ' - +#133'='#26#182#172'D'#27'p-L'#156'y '#20#150'O'#207#208#153#198#144'k'#8#162 - +#249#244#246#227#233'u'#176#165#27#187',='#11'&'#239'rHZ'#143#201#133#2#249#6 - +'!T'#19'@'#131'C'#169','#216#235#131#182#218'oS'#8'@'#165#136#181'O'#180#181 - +'QSDJ'#217#175#193'}#e'#4#246#22#9#232'MVY'#4#0'_'#172#217#238'.'#249#3#18 - +#192'=b'#31#128'1'#175#10#172'E'#19'Y'#27'p'#174#7#160#156#133#6#217'A3'#192 - +#0#31'<{)s|$/'#28'O2k'#241#169'0e^'#250#226#174'c'#200']'#172#254#228'q'#216 - +#178#246'?R_F'#175'8'#240#180#251#192#156'_'#145#246#227'2'#1#231'K'#131#169 - +#24#136'5'#7#241#7#161#181#230#171#20#2'P'#202#227#238#201#249'm'#15#2#231#3 - +'HF'#2'Ps'#240#245#148#18#220''''#1'h'#17#245#254#138#171#163'q'#197#205#236 - +#5'|'#20#192'T0'#17#172#5'U'#160#215#171'Y'#18#144'0'#0'4SC@'#222'|'#228#148 - +'n;'#241#236'y'#232'UP1eAF'#206'9'#134#220#193#219#143#159#141'Z'#128'K'#234 - +#203#232#17'r'#133#10#142#185#244#149#140#28';Y'#21#136'{6+ '#16'fm'#194'[' - +#182#254'/'#133#0#20#178'x`J'#1'G'#0#248#247'6'#148#213#250#190#230#5#244'$' - +#173','#2#176'n'#221':'#165#211#233#212'm'#178#23#220#138#4'p9{'#128''''#0's' - +#209'dd'#187'r'#212#0#144#0'P'#11'P%K'#128'3'#147#2#252#250#3#199'w;'#221#150 - +#28'g'#135#156#245'HF>'#248'1'#228#14#254#249#192#9'h'#3#167's'#208'gzA+'#255 - +'A'#167#167''#230'A'#178',8'#193'E'#148#136#0'hX('#245#5'h'#221#250#5#27#248 - +'*<G&KD'#167#230#183'<'#128#183'k'#20#10#5'K'#9#22'J'#131'{j'#17#214'#'#1#8 - +'5'#0#200#28#250':_'#197#221#209#184#252'l'#246'@'#146#0#166#130#165'`'#28#24 - +'H'#3#208#168'X'#31#0'Y2'#4#152'~'#188'v'#255'1='#134''#14':'#237#254#140 - +#168'^c'#200#29#252#3#191#255'\*'#189#238#138')s'#15#131'9{'#157#153#145'c' - +#11'~'#0#214#29'('#18'e'#3'C'#3#193'0'#180'm'#251#154'uy'#22#8#128'0'#173#160 - +#249#1#178#255'qc'#161'@'#220'Z'#187#204#13'L'#137#4#244'J'#0#20#2'D'#245'A' - +#191#161'5'#239'Q$'#0'6tN '#0'K'#201'N'#172#21#152#201#200'5'#3#161#230' '#10 - +#25#215#7' '#19#12#240#218'_'#143#134#158'2'#235#138'*g'#194'^'#199#220#182 - +#195#253'[~'#250#0#218#27'~'#5#183#163#30#2#30';'#132'C\'#3#15#210#26#202#170 - +'w'#133#201's'#15#5#157'1?#_'#218#24#210#7#250#145#191#249#224#9'R_F'#175'8' - +#240#244#7'2'#183#8#145#6' '#228#2'Db'#224#245#134'9'#2#168#249#14#162#145'@' - +#10#1'L'#206'k}\'#169'H'#208#224#7'r'#4'nW*'#149#205'*'#149#170#163#161#161 - +'!8`'#2#160#16' '#18#128'a'#171#171#244#249'XB'#206'rO'#5#2#176#150#206'DA*' - +#4#147'A'#195'G'#1'd,y%SQ@'#166#2#246#208#184#146#206'{'#228#197#207#131'R' - +#205'M'#155#165't'#209'O^'#185#158#9'}_Pi'#244'P2~.'#236'~'#200#229#25#153 - +#248':'#134#161#195#217#188#25'>Z'#145#187#141'N'#20'*'#13#28'}'#233#203#25 - +';'#190#160#248'PYp'#8'5'#0#31#18#128'?D3'#2'VA$'#232'N!'#128#234'<'#251#11 - +#26'Et='#222#220#138#191#231#237'h'#2#244#26#10#236#145#0'('#4'XXX'#168'B3' - +#192#248'K'#147#237#159#241#132'l1{@ '#128'q'#179#193'h'#202#7#147#137''''#0 - +#26#3#206#183#1#207#4#9#188#243#216#185#224#239'E'#160#199#207#216#27'v?'#248 - +#18#230')^'#245#241#19#3'N'#191'U'#170#180#176#199'!'#151#193#184#201#163#171 - +#178'n8'#128'l'#223#215#239';6g3'#0#231#238's6'#211'&3'#5'!'#29#152'"'#1#212 - +#25#200#235#11#177'\'#0'J'#4#10#251#157')'#4'0'#222#234#248#135'N'#21'YK&'#0 - +#229#2#224#234'O'#205'A\'#212'$t'#247#221'w'#143#12#136#0'('#7' '#20#10#25'' - +'m-x'#27#9#128'IF'''#1#204#5#163#217#10'f'#163#22#180'H'#0#212#9'H'#166#200 - ,'\'#15#128#143'W,'#7'{'#227#198#30#31'''/l'#193#184'i'#208'Z'#251#243#144#206 - +'SX1'#3#150#28'u#j'#19#218#204#188#145'1'#12#10#239'>q'#1'x]'#205'R_'#198#14 - +#160#26#141#195#255#144#217#182'd'#9'^d'#217#192#208'p'#20'<^'#158#0#234#215 - +'B8`O!'#128'J'#139#227#13#189'*L'#197#18#201'\'#0#212#226#157'H'#6#221#230#2 - +#244'J'#0'j'#181'Z'#19#139#197#140#155#28'E'#175#196#226#242#165#236#1#222 - +#203'o+'#157#131'&@>'#243#1'P$@'#145't'#2'f'#134#1'V~'#240' l'#203'R'#211#13 - +#5#146#201#220#253#206#131#234#153#251#15#253'`cH'#11#214'|'#254'LN'#166#1#31 - +'p'#234'=`+'#158#148#209's'#8#233#192'q'#158#0#220'D'#0#254'0'#18#192#26'4'#1 - +'\)'#4'0'#193#230'x]'#171#140#172#3'>'#27#144#186#4#247'6-'#168'O'#2#8#135 - +#195#166'm'#29#165'OF'#227'r'#166#227#8#4'`)'#153#9'f['#17#24'y'#2#160'f ' - +#178#12'N'#2'j'#174'Y'#3#255'}'#237#214#140'~'#208']a+'#158#8'K'#143#187'-' - +#235#189#238#199#176'#'#130#254#14'x'#251'og'#230#212't'#230#146#9#187#192'^' - +#199#220#146#241#243#8'EAqf'#2'D'#192#235#14#129#151#8#160'n'#21'DB'#30#238 - +'9<'#1'L'#180#181#189#162'R'#196'6Q='#128#208#30#12'I'#192#209'S2PO'#210#154 - +#204#2'D'#152'6'#180#218#238#143#198#21#201'('#0#17#128#185#136#250#194#151 - +#128#201#164#3#131'^'#197#17'@'#134#235#0'^'#187#247'('#180#131#178#251#3#144 - +#203#149'0{'#233#25'0y'#222'aY='#239#24'v'#196#166#213#239#194#143#159'<!' - +#245'e0'#208#140#132'C'#207#251'{'#210#241#156#13#196#248'T`'#143''''#8'>$'#0 - +'J'#5#142#133#131')'#190#145')'#249'-+'#208#26#175#1'>'#25#8#247#13'('#252 - +#164#1#248#186#235#11#208''''#1#168'T*'#243'f{'#254#31#195'1'#197#25#236#5'<' - +#1#152#10#166#176'D '#179'Y'#139#4#160'a'#4#192'f'#1'f'#144#4#200#14#244'8' - +#26#135'~'#160'A'#192'RP'#9'{'#31''#27'h'#141'y'#146#156''#12#28'>}'#233#6 - +'h'#173#251'E'#210'k'#208#155#242#225#16#18#254',u`f&@'#28'X'#243#15'J'#0'r' - +#187#3#204#7#208'F'#169#192#209#136#152#0#18#211#11'[^'#196#191#235'I'#3'P(' - +#20#219'"'#145'H'#3#222#223'>h'#2'@5'#194'R'#227'*'#188'>'#20'S^'#200'^'#192 - +#11#184')"'#152#11#171#192'l'#210#128#209#160#3#165#138'k'#6#154#201'>'#0 - +#171'>~'#156#173#2'R'#129'Z?'#239#188#240'$'#152#177'gzK>'#135#27#220#246'zh' - +#216#252#29#11#183#6'|.'#8#5#220#172'V_'#165#214#129'Zg'#2#173#222#12'Fk)TN[' - +#12'zsAZ'#207'MCY'#222'J'#186#134#160#244#190#14'9'#231'a'#166#25'f'#11#156 - +#15#144'/'#7#14'F'#160#163'#'#192#178#1#219#182#254#143'/'#20#226#228'Y.KDP' - +#3'x'#149'R'#128#129#171#5'`'#233#192#184#136#183#187'\.ow'#147#130'z%'#0#170 - +#3#192#23'['#234#189#197#151#6'"'#202'd'#23'F"'#1#131#181#10#172'%'#19#193'd' - +#208#130#201#172'I'#154#0#178#12#134#210'i '#194#7'OH?'#158#219'd+'#133#165 - +''''#252#31#232'-'#133'R_J'#198'A'#130']'#187#225'Kh'#218#242#3'ks'#22#244 - +#216#7'4'#135#143#134#148#26#172#197'P1u'#1#236#180#240#184#180#9#206#215'o' - +#221#13'u'#191'~'#153#213#207#194'Z4'#30#14'<'#235'>'#188#149#253'|'#17#166#1 - +#160#249#27'@'#193'w'#185#209#4#240#5#161'}'#235#23#220'c<'#1'('#229#241#224 - +#164#188#214''#226'M'#154#18'L'#234#255#214#190':'#3#245'J'#0#227#198#141 - +#211#6#131'Ak'#141#211'r'#182'/'#172'Nz;H'#208#245#214'r'#176'P='#0#154#0'&' - +#147'6'#217#15' '#211'x'#253#158'c'#210'2'#201'v'#168#160#228#163'Is'#15#129 - +#185#251#159''''#245#165#164#21'm'#245#235#161'n'#195'W'#208'^'#183#142#173 - +#178#209'4'#142'B'#167#207#172#160'b'''#152#179#247#153#144'W:y'#200#199#219 - +#188#250'='#248#241#227'''3>'#168#149#186'/O'#158'8'#204#217#231#172#140#158 - +#167'W$'#184#17'a'#1#180#253']'#164#1'x}`'#223#254#13#255#16''''#207'*E'#204 - +';)'#175#157#6'%'#146#218#207'4'#0#154#24'L#'#195#245'z'#189#167#191#4#192#10 - +#129#208'VPVVVjc'#177#152'e'#187#211'|'#162'7'#172#185'+'#249#4'j'#9'f*'#5'[' - +#201'4\'#253#181','#23'@'#165'Qr'#149#128#221#229'j'#164#145#23#254#243#236 - +#149',3,W@'#17#130'=w5'#148'T'#15#207#190#252'q$'#211#205'?~'#0'5'#235'>' - +#135#142#182#154'n'#11#174'2'#129#252#178#169#176#232#152#27#217'H'#173'!]?' - +#10#255#143#31'?'#1'['#250#152#13'2M+'#240'w^>y'#15#216#253#176'+'#178#155 - +#23#210#141#12'%d'#192'f'#3#4#252'!'#232'p'#5#192#235'q'#131#163'ne'#138#3'P' - +#163#140'vT'#219#236'4'#187#190#129#132#159'6'#165'RY'#135'2'#220'6h'#2#136 - +'D"'#214#6#183#249'w'#238#144#238'!'#241#147#180#166'b'#176#149#206'`y'#0'&' - +#139#142'M'#7'b'#29#129'2'#252#217#172#251#250#31#240#243'_'#200#222#151#209 - +'O'#20#148'O'#135'%'#199#222'<,B'#134'^\'#217'7'#174#252#23#218#241#223#131 - +#223#211'.Y'#145#13#249'T'#166#237'~$'#139#178#12#29'qX'#243#201'3Hd'#255'e' - +#205'C'#134#2#133#146#146#202#166#163#224'_'#206#18'}r'#1'T'#13#24#141#198'P' - +#245#15#129#219#229#7#175#219#1#206#134#31'S'#8'@'#167#140#216#171#172#246 - +#143#128#215#0#168'"'#16#247#140#0#240#182#183#187#138#192'~'#17'@'#179#199 - +#184#175'3hxZ'#252'$'#141#161#0#242#198#237#12'F'#163#14#205#0#29'k'#14#154 - +#169'^'#0'b'#208'D'#222'7'#239#203#205#194#16#25#170#138';-8'#22'v^'#156#190 - +#198#144#233'BG{'#29#252#244#233#211#208#142'*~$'#228#151#250'rR`-'#154#0#251 - +#159'y'#218'j1(g`'#195'7'#175'C'#253#198#175#193#215'A'#3#173#251'&8r`'#22 - +'V'#238#12'Sw;'#2#138#170'fI'#253#145#236#0'z'#7#17'j'#7#230#9#162#9#224#199 - ,#247#213#14#206#198#212#238#200'zU'#184#165#210#226#248#12':}'#0#228#4#172#29 - +#18#1#144#9#208#234#209#237#209#230'7'#254'#'#229#3#211'Z!'#191'b'#14#24'P' - +#253#183#152#245#172''''#0'u'#4#202'F3'#208''#221'w'#18#251#146's'#21'Z'#163 - +#13#22#163'z'#155'?n'#154#212#151#2#174#214#26#248#238#223#247#129#179')w' - +#204#166#238'@'#159#217'!'#231#254#13#212#250#244#14':!'#19#193#217#188#21':' - +'Z'#183#129#27'I'#208#227'jfu"F['#25'X'#11'+Y'#178#151#165#184':'#183#11#193 - +'X'#166#31#215#11#192#235#13#176'('#128#199#209#12#238#150'u)O3'#170'C'#141 - +#21#22#215''#249'N@d'#2'l'#29#148#9'@'#155#216#9#232#244#171'f6z'#172')'#241 - +'7'#133'J'#7#5'U'#187#131#193#160#1'3'#154#0':'#150#11' '#239#182#31'X'#186 - +')'#225#203''#222#153'u'#15#240'`0e'#215#195'a'#222#129#210#12#177#8#251#221 - +#240#217#203'7'#131#163'i'#147#212#31'C'#191'A'#5'Y'#135#156#255'(k'#135'>' - +#154#209#157#190'B'#9'pa>'#9#168#3'M'#0'w'#251'v'#240#218'S'#167'#Y'#180#193 - +#154'2S'#7'y'#6#27#248'$ F'#0#209'ht@N@BJ'#24'P'#169'R'#149'nh-'#250#2'_'#165 - +'I>'#3#5#189'p'#194'"$'#0#29'X'#172'z'#208#27'5'#172'5x'#159'y'#0'i`'#131#230 - +'mk'#224#211#23#174#207#202#151'1T'#152#11'*'#224#128#179#238#205#170'o`'#235 - +#154#255#192#202'w'#31#206#154'C/'#157#160'<'#130#163#174'x!mSv'#135#5#250 - +#180'P'#184#28#128'`0'#2#30'w'#0#220#168#1#184'Z6@'#160'#5)'#174#208#224']W' - +#160#247#253'D'#137'@d'#2#240#142#192#186'A'#135#1#137#0#240#133#164#147#21 - +'ov'#20#191#30'K'#200'''$'#159#129#4#144'W1'#15'L'#22#27'j'#0#6'V'#19#160#226 - +'C'#129']'#223'O&'#140#130'W'#238'8l@'#241'h)A'#177#240#189'N'#184'%+'#145 - +#130'O'#158'_'#14'-5'#185'99'#167#191#176#20#141#135'C'#207'T'#234#203#144#4 - +'='#5#209'('#2#16#12#132'Q'#248#253#168#5#4#192'Q'#207#149#2#139'Qnv'#174'D3' - +'`'#19'%'#255#8#26#128#144#8#132#154#128'o'#192#4' '#164#2#227#193#138#183'8' - +#10#30#12#199#20#139#196#151'E]'#129','#249'e`'#178#234#192'd'#212#129'ZC~' - +#128#236#204#5'x'#239#209#11#192#217#178'-+'#231'J'#23#166#237'q$'#204';(}' - +#195'#S'#128'6'#226'{'#191'h'#216'}&=a'#194#236'}a'#193#145#185'7'#1'X'#10 - +'P'#18#16'E'#0'('#4#200#17'@'#16#218#183'}'#3#177'h0'#229'y'#19#243#218'>U+' - +#226#164#254#215#163#224'3'' j'#2#245#129'@'#192#142'f'#128'w'#208#181#0#248 - +'wQ'#141#211'v'#173'?'#162'Jq'#191#27#242#171#217#20#27#179'Y'#159#12#5#210 - +'x'#176#158'k'#12#211#247#161#172#253#244'yX'#251#249#138#236''#27'C'#196'n' - +#135']'#12'Sv;<'#189#7'E'#251#240#237#135#207'cN'#174#145#3#25#28'~'#201'c`)' - +#172#146#250'B'#178#131'^'#204#0'j'#6#18#165'n'#192'>'#206#254#247'z'#252#208 - +#182#245#139#148#16#160'\'#150#136'N'#201'o'#249#8#5#159#194#30'uB'#30#0'nD' - +#8#246#129#214#2'$'#203#129#241#128'fd'#143#194'F'#143#229#20'wH'#151'B'#201 - +'ZS'#9#228#143#155#1'&'#179#14#204'V=h'#132#178#224',('#1'>W'#11#188'q'#239 - +#169#153'?Q'#154'AYeG]'#189#2't'#166#244#21#21#189#247#247'K'#192#222#176'q' - +#232#7#202'1'#152#242#199#193#17#151'?#'#245'eH'#10'a&'#0#245#1#240'y'#3','#7 - +#192#237'p'#160#9#240'Cj'#18#144'"'#234#169#206#179''#129#247#181#161#204 - +#210'J'#192'f'#3#12#182#28'8'#165#31#0#218#15#133#142#128'ai'#139#215#248#160 - +#248'Ij'#141#5#10#198#207#3'#O'#0','#18#160'R'#176#142'A'#137','#12#7'}'#233 - +#214'Cr"-x'#160'`?'#236'+'#158'K'#203#177'H'#19#250#9#183#145#138'='#143#184 - +#10'&'#205#31#157#163#208'9$ '#30#163#8#0#239#0't'#250#193#213#214#0#238#214 - +#245')'#4#128#182'K'#133#197#245#3#222'l%'#199#31#240#229#192#168#193#147 - +#167'pp'#13'A'#168'%'#152#223#239'7!'#131#20'D'#19#170#201'['#157#133'o'#136 - +'_'#163'P*'#161#176'z1K'#6#162'H'#128#206#168'ERP'#246'/'#23' '#13#252#240 - +#206#131#231#177#24#239'p'#196#188#131#207#131#25#139#143#27#210'1'#236#13 - +#191#193'{'#187'(g{'#229#165#3'r'#133#18#142#185#246#21#208#26#173'R_J'#250 - +#209#143#175#141'u'#2'B'#251#159#28#128#140#0'P'#3'p6o'#1#175'c[J'#6'g'#158 - +#206#191#173#200#224#166#196#128'VJ'#254#17'z'#2#146#6'0'#232#150'`BO@T'#31 - +#10#240'GV'#190#201'Q'#242'J<!'#179'%'#159'$'#151'AA'#213#2#180#255'M'#168#1 - +#24#152'&'#160'V+'#185#17'a'#252#140#192'LF'#4#214'|'#252#28#219#134'#J''' - +#205#133#3#207#185'gH'#199'x'#227#238'S'#193'mo'#144#250#173'd'#28#230#194#10 - +'8'#234#170#225#249'='#247#7'='#201'H'#130#31#6#18'#'#2#240'q'#14'@'#234#5'`' - +#175#255#5#130#238#212#254#136#165'&'#247'z'#171'6'#176#25#229#148#8'`;'#240 - +'-'#193'P{oB-'#222'IMA'#7'D'#0#212#22#28#247#234#182#182'6'#3#238#11'P'#149 - +#24#183#201#158#255'P$'#166'HN'#228#164#149#222'6n'#14#152#243#138':'#9#128 - +#250#3#138':'#4'g'#18#164#254#191'x'#227#193'9'#213'&'#170#191'0'#229#151#193 - +#209#203#6#239#196'l'#173'Y'#7#239'=*}it'#182'0c'#241#177#176#235'a'#23'J}' - +#25'YE'#130#239#4#28'A'#251#159#28#128#180#250#251#144#0#218'j~'#128'p05'#19 - +#182#202'j'#255'^'#167#140#144#186'O'#204#192#8#128#136#0'I'#160'iPm'#193#133 - +#193' '#10#133#194#128#7#200#195#139#25'W'#227#202#187')'#16'Q'#237#147'|'#18 - +'M'#8'*'#156#2#150#162'*'#230#3'0RJ'#176'^'#205#252#0'T'#232#145#141#128#224 - +';'#15#254#1#218#235#135#159#3#140#230#17#156#242''#239#13#250#245'o'#222'{' - +'&'#184'FH'#200#175''#144#193'.'#7#156#9's'#246';M'#234#11#201#26#136#0'b' - ,#188#253#239#247'p'#234#191#207#19#132#214#173#255#195#197'/5'#201'kj~'#203 - +#231'2Y'#194#137'2'#217#132#175'c'#4#128#219'v'#148#223'f'#220':jkk'#131'(' - +#211#3'#'#0#26#13#134#194#175#15#4#2'y'#184'/'#173'w['#206#243#132#180#157 - +#174'w'#234#11'`.'#131#188'q'#211#209#12#208#179#141#252#0'*'#181#146'o'#29 - +'&'#154#18'$'#214's'#210#200#12#219''#249#2'>}'#246'F)'#191#167'A'#129'j' - +#227#207#184#251#179'A'#189'6'#28#244#195#138#27'G'#167'c'#172#176'r''8'#248 - +#194#7'Xr'#213#136'@'#143#250'?O'#0#188#253'O+'#191#199#229'C'#18'p'#130#189 - +'v%'#211#12#4'('#229#177#224#228#252#246#175#240#249#2#1#212#0#231#4#164#209 - +'`-h'#198#187#131#136#1'M'#6#18#15#7#141'F'#163'6'#180'%J'#28'~'#221#190'->' - +#243#29#226''''#170#212'z('#172#222#19#140'&'#29#152'mD'#0'\B'#144#208'&<'#27 - +'j'#192#11#215#31#8#145'P'#250#26'Wd'#3#244#3'>'#253#174#143#7#245#218'_'#254 - +#251'*'#172'|{'#244#14'DUi'#13#176#223#153'd~'#148#145#138#4'_'#0'D'#241#255 - +#160'?'#4#30#180#255#189#184'9[j'#192#221#250'['#138#227#215#160#14#219'+' - +#204#142#213'('#252#14#138#251#211'<'#0'>'#19#144'*'#1'['#7'3'#28#148' G'#155 - +#129'U'#4#250'|>'#139'F'#163')'#142#199'a'#194'F{'#225#243#137#132','#217#29 - +#129#170#168#24#1'X,'#204#15'`'#160#210'`'#230#7'P$'#29#129';'#156'-'#205'N' - +#235'_'#191'z'#19#190#249#231'_%'#249#162#6#11's'#193'88'#230#250#193#141#147 - +#254#247#131#23'@k'#141#180#141'1s'#1'TA8m'#193#239'a'#206#1#167'g'#181'G_Z' - +#209#131#134'L+<'#133#255#200#254#15#160#253'/'#16'@{'#221'Z'#8#186'[S'#8#160 - +#208#224#221'R'#160#247'Q'#185'g;'#17'@'#235#0#196#167#239#14';d'#3#226'V' - +#185#197#158#255#167'PL1#'#249'$'#20'rK'#241'N`-'#174#0#147'U'#143#4'`'#0#173 - +'A'#13'*%'#154#1#138#204#205#10#236#138'Wn;'#10'|'#206#214#236#156','#13#152 - +'0go'#216#231#140#255#27#212'k'#159'_~'#0'D'#130#185'U'#211'/%'#168#15'C'#233 - +#164']'#160'z'#151'}'#161'j'#230#226'a'#31'2dFz'#156'S'#255'#A'#222#254#167 - +#30#0#184'o'#222#252#5#196'#'#225#20#2#24'o'#181#175#210#169#162'-x'#179#29#6 - +#144#5#200'>'#187#222'>W'#241't d'#146'B4'#5'*'#154#188#182#179#221'!'#237 - +#209#201'''Q'#131'PK)'#228'W'#206'dQ'#0#163#197#0':'#147#150#21#6#201#149#217 - +#233#15'@h'#217#250#19#252#251#129#225#227'%^t'#194'r'#152#186#231#224'R'#130 - +#159#185'b'#201#176')'#132#146#2#148';'#160'3'#231#225#162'4'#30'J'#170'g' - +#195#196'y'#251#131#9'5'#174'a'#131#4'7'#9#184#211#254#199#213#159'e'#0#182 - +#131#189#230#7'6"L'#144'c'#133'<'#30#153#146#223#246'5'#10#186#7'e'#141'V@' - +#178#251#5#2'h'#196#197#219#217'S'#18#16#161'W'#2#16'&'#4#163#253'o'#212#233 - +'t'#249#241'x'#188#188'#'#168']'#220#232#177#220#156'|'#18#10#184'R'#163#131 - +#226#137#11#153#31#192'h3'#128#158#18#130#180#252#184#176#12'N'#12#238#138 - +#183#239#251#3#180'n'#27#218'l'#192'l'#225#212'?'#0#154'A6'#191'x'#242#210 - +#133#144'v;j'#132#131'HAo)'#128#252#242')P>mw'#152#186#224#240#156'4'#27#216 - +#234#159#224#212#255'('#170#255'd'#255#147#240'3'#251#191'y+'#218#255#155'Xn' - +#128#0#163':'#212'^aq'#145'='#216#129#178#216#140#143#17#1#176#8#0'.'#220#212 - +';'#189#199#193#160#132'>'#9'@'#28#10#196#251#202#240#160#213#155#28'%'#143 - +#196#19'2'#29'{'#18'k'#5'.'#135#194#9'{'#128#201'f'#227#205#0#174'.@AZ'#0#171 - +#14#236'>)'#168#219#147#14#225#195#11#184#29#240#210#141#135#231'|^@~'#197'T' - +'8r'#217#179#131'z-E'#0#158#191'f_'#169#223#194#176#135'J'#171#135#165#167 - +#222#2'U'#179#150'd'#252'\'#253#253#221#211#243'h'#177#140#199'x'#245'?'#20 - +#129#128'7'#136#194#239#227#226#255#219''#132#128#167'-%'#2'Pd'#240'l'#201 - +#211#249#182#163#28#186#144'8'#154#132'$ '#218#227#202#223'b0'#24'X'#8'p'#221 - +#186'u'#20'7'#140'ww'#222#30#175#137'"'#1#223'}'#247#157'*'#16#8#232#208#20 - +#176#161#9'P'#138'D0~'#155'3'#255#250'`T9C8'#4#249#1#172#165#211'Q'#229#170#2 - +#163'@'#0'z'#13#168#132'h@'#182'T'#0#196#202'='#12'k?z1k'#231#27'('#232#179 - +'8'#254#246'7'#192#152'W:'#168#215#7#189'Nx'#241#218#209#25#2#204#4#138#170 - +'g'#194#129#23#220';hm,'#221'`'#222'Z'#253#163'Q'#8#163#250'O'#246#191#151 - +'%'#0#249#161#5#237#255'h'#23#251#191#218#214#190'J'#173#136'R'#248#207#129 - +'6R'#8#144#239#7'H'#154'@k$'#18'q{<'#158'Pw!@B'#175#4'@'#155#16#9#160#230 - +#160#212#23#0#239#171'j'#246'ZNv'#5'uG'#8'O$'#2#208'['#202' '#175'b&'#18#0 - +#154#1'f'#3'W'#23'@'#141'B'#217#200'0'#25'd'#205#14'@'#188#255#224'%'#208#176 - +'ae'#214#206'7'#16'L'#156#191'?'#236'}'#214#29'C:'#198#147#23#238'.'#245#219 - +#24'Q '#243'`'#223's'#239#204#138'6'#208#27'HB'#168#244'7'#30'E'#2#8#161#250 - +#31#8#129#223#205#169#255'n{+'#180'o_'#197'Z'#131'u'#14#2#137#133'&'#229#181 - +'}'#143#194#239#195'?'#237'|'#8#144#217#255#168#181#215#247#214#12'T|'#206'^' - +'?'#27#161'3'#144'^'#175'7'#1#31#9#240#132#212'{'#214'uX'#151''''#15'"''?' - +#128#22#138'''.b'#171'?e'#4#234'L|8'#144#175#13'`'#166'B'#22'?'#204'Wo>'#10 - +#220'm'#245'Y<c'#223#160#216#255#25#247'}'#142#164'84'#219#243#169#139#23#12 - +#203'v_'#185#12#234#251''#250#189#159#14#249#187#25','#18#252'?d'#190#10#222 - +''#10#255#249'X'#7'`?8'#154'6'#131#167'u+'#155#15'('#20#1#153'5'#193#214'2' - +#147'k'#3#202#150#151'/'#3#174#23'"'#0#168#177'7'#4#131#193'^#'#0#132#190'd2' - ,'%'#18#160'T*'#11'('#18#128''''#154#176#177#189#232#238'xBf`OB'#225'&!'#167 - +'&'#161#198#252'<F'#0'z'#150#22#140'f'#128#150#239#22#204'r'#2#178'G'#1#209 - +#144#31'V,?'#20#213'(o'#214#206#217#23#22#157#180#28#166'/9z'#200#199'y'#238 - +#138#189's'#234'}'#141#20#140#159#179#20#246'?'#255'niN'#158#224#230#255#177 - +#226#159'p'#20'B'#168#254#7#188#1#240#241#249#255#237#181#171#248#177'l'#157 - +'2\bto'#182'j'#253#212#253#199'ME@'#192'G'#0'('#17'H'#165'R5'#225#177'z'#141 - +#0#16#250'$'#0'!'#18#160#209'h'#12#168'N'#144'#p'#28#158'`|'#141'3'#239#202 - +'@T'#181'3'#247','#25'('#228'r0'#21'V'#131#173'l2'#232'id'#184#5#9#192#160'e' - +'Z'#128#156'z'#4'p'#163#131#179'i'#9#160#6#208#0#175#221'|tN'#172#150#187#31 - +'s'#25#204': ='#13'L'#158#191'b_'#8#249'r'#183'-'#250'p'#198#239#151'?'#195 - +#252#2'Y'#5#231#250#231#212'J'#254#9'E '#228''''#245'?'#0'~'#150#0#228#134 - +#214'-_A'#12''#199'b'#7#224#196#188#182#31'T'#242#152#155'w'#0#178'" >'#3'p' - +';'#202#27#253#221#209'S'#17#144#128'~'#17#0#213#4'x<'#30#180#2#244'Vr'#4'"' - +#17'T5{'#244'G8'#2#198'dA;'#9#184'Zg'#132#162#137'{2'#2' '#13'@'#199#178#2'5' - +#172'6@'#208#2#178#146#27',B'#243#230'5'#240#254'}'#23'B4'#18#202#234'y'#197 - +#216#237#168'K`'#246'Ag'#164#237'x/\'#181#31#174#6'C'#155'~3'#134#238'A'#157 - +#154'N'#185#247#163#172#159#151#171#252#227#212#255'p0'#12'Ao'#144#9'?'#173 - +#254'T'#244#229'j'#222#200#156#131#130#253#175'VD'#253#19#243#236'?'#226#223 - +#148#17#230'@'#18'`'#14'@'#222#254#167#134' }:'#0#9#253#145'F9'#146#128#18'I' - +#128#210''#205'h'#10#20'#'#9'T'#198#19#138')'#155#157#133#183''''#18'2V'#149 - +'A'#194'M'#13'B'#10'*'#231#161#25'P'#8#6#19#18#0#211#2#136#0'T'#160#16'*'#4 - +'3<B'#188';'#132#253'^x'#235#174'3'#192#217#152#253#230'!'#187#29'y1'#204'9$' - +#189'C%_'#188#250#0#252'q'#180'g'#253#189#140#22#28'}'#211'K'#144'_'#153#165 - +#161'.'#178#4'_'#247#207#173#254#156#250#31#130#128''''#192#28#128#20#5'h' - +#223#246'='#254#237'J'#177#255#11#244#190#218#2#189#135'B~^'#20#254'v'#222 - +#254#23'j'#0#26#250'J'#1'N'#158#190#31#151#200#28#129#249#249#249'j'#161'=' - +#24'%'#4#145#31#160#182'#'#239'|D'#205#244'%'#230#228#195'U'#222#148'W'#9'y' - +#21#211'Q'#11#224#252#0',+PK%'#194#157#206'@.'#190#144']'#18' |'#243#242#221 - +#240#203#199'/C6'#146'h'#200#169#180#224#164'e0u'#241#145'i?'#246'KW'#31#4'>' - +'gK'#198#223#195'h'#197#180'%G'#193#226#211'o'#202#206#201#184#170#31#142#0 - +#248#216''#208#23'd'#130#207#212''#167#19#9#224';'#212#12'"b'#251'?1)'#191 - +'}'#21#170#255#228#8'"'#251#159#26#129#214'R'#248#15#23'h'#234#1#208#136#194 - +#239#232#203#254''''#244'G'#10#153'#'#16#15#166'"?'#0#10'p>'#158#168#140#252 - +#0#206#128'vi'#179#215#194#21'h'#147'#PFf'#128#14#205#128#5#140#0'H'#3#224 - +#162#1#252#244'`j'#24'*'#151'u'#27#17#200#240'P'#225'$'#26']'#9#31'>|%D'#130 - +#190'L|'#157'L'#203#153#186#232#8'Xr'#234#13'd'#23'e'#228#28'O'#253'a7'#246 - +#131#24'Cf`*('#131#19#239'zw'#200#199#233#235'7-<'#206#21#254#196#208'LE'#2 - +' '#231#31#18'@'#160#131'['#253#157'M'#155#192'M'#222#255'h4'#169#254#235'U' - +#225#142'J'#139'c'#29#202'Q'#0#239's'#1#215#4#164'V'#176#255'qk'#17#154#128 - +#160#240'G'#249'S'#13#158#0#186#250#1#144'eJ'#240'~'#234#215'\'#189#201'Q' - +#180'<'#22#151'S'#136#144#17#0'e'#255#217#202'g'#130#185#176#140'#'#1'3o'#6 - +'h'#133#204#192#206'nA;'#148'Bw'#185#154'L'#182#186#219#248#191'7'#224#199'w' - +#159#2'O['#250'Zj'#149'M'#155#15#251'^xOF'#139'Q'#234#214'~'#9#31#220'?z:'#1 - +'I'#1'J_?'#231#137#31#134'D'#224#221')'#184']'#207','#251'/'#193'y'#254#185 - +#213'?'#138#230'j'#136'y'#255'I'#253#15'P'#243#143'-'#223'@'#200#239'I'#137 - +#255#151#24#221'[l'#186#0#165#249#250#241'>'#150#0'$'#148#0#227#177#234'PKo' - +#235#143#253'/\C'#192#252#0#168#5'h'#220'n'#183#5#217#165#8'OXIf@'#131#219 - +'r'#130';'#164#221#141#29#140#198#131'+'#20#172'8('#175'j&o'#6#232#144#0't' - +#160#210#169#147'Z'#128#16#17#200#5'8'#27#182#194#183'/'#255#5#26'~]'#201#134 - +'F'#14#4'T'#133'f-'#29#15#19#230#239#15';'#239'2h'#12#153#207'&{'#239#238'?' - +'@'#195#250'o'#165#250#184'F'#13#246#189#240'n'#168#222#245#128#140#158'#!' - +#168#255#148#249#23#137'B4'#200#169#255'd'#255#7#220'T'#252#211#10#142#237 - +#171'!'#134#143#197#249#231#202'e'#137#216#228#252#182#213'2'#136#7#248#248 - +'?9'#131'X'#252#31#229#170#6'o7'#160#156#182'['#173'V__'#246'?'#161#223#4#128 - +'f'#128'\'#156#15#128#251'rd'#155#9#193#152'v'#238'v'#151#237'\v02'#3'P'#184 - +#149#26'5'#20#146#25'`6'#161#9#192#145#128'Z'#199#229#4#144'/@&'#242#5#236 - +#176#234#15#225'"'#135#2'R'#177#234'~'#254#10#26#214'}'#11'm'#219#214'1'#205 - +#128'&'#203#138#175#130'VvKq'#5's'#16#149'M'#159#15'e;e?#'#239#233'sw'#131'h' - +'X'#186#136#198'hA'#213'.K'#225#192#203#31#24#212'k'#251'j'#132#155'L'#250 - +#137'sC?c'#164#254#211#234#31#8'A'#136#188#255'l'#245'G'#245#191'a=x'#237#245 - +')'#225'?'#179'&'#216'^fr'#209#196'W'#26#11#228#22'*'#0#137#0#132#30#128#129 - +'@'#192#137#230'z'#159#246'w'#215#214#227#243#132'|'#0#178#1#168'0('#26#141 - +#150'Q8'#16#31#155#176#197#145'a8'#166','#226#252#0'\4'#192'R6'#13#204'EU,$' - +'H~'#0#202#9' -@'#201#198#135')'#144#4#184#196#160#28'Q'#4#134#5'>z'#248'*' - ,#216#246'}'#246'CT'#163#17#182#242'Ip'#236#31#223#200#216#241')'#233''''#17#3 - +'V'#214'M+<e'#254#133'|!'#8'2'#245#31'5'#0#175#31#218#182'|'#9#225'`0%'#252 - +'Wnv'#254'j'#210#132#169#243#15'i'#0#20#11'n'#166#30#128#148#2#204#183#3'o' - +#161#30#128'F'#163'1'#216#151#253'O'#232'7'#1#8#133'Ah[h'#145'e'#168'.'#160 - +'D0'#3'Z}'#166'C'#28#1#195'^'#236#137#188#25'@'#241#212#252#170#249','#10'@Z' - +#0#17#128#218#160'a'#218#1#27#30'"'#20#9#245#196#0#217#152'0:'#140#176#253 - +#199#207#225'?'#247']"'#245'e'#140#26#232','#5'p'#202'C'#3#236#217#216#31#245 - +'5'#1#201#142'?$'#216#204#249#199#135#254'h'#245''''#2#8#184#131#224'io@'#13 - +#224#231#20#245'_)'#143#135'Q'#253#167#216'?M'#195'a'#249#255#184#177#22'`' - +#212#2#156#230#0#226#194#220'F-'#192#236'v{'#184'/'#251#191#187#203#235#13')' - +#29#130'h\'#24'2M9^'#204#248'h\9m'#139#163#224#194#4#249#1'y3@'#161'V1'#2'0' - +#216#242'QuF'#13#128'i'#1'd'#6#16#9'(QK'#16#21#9#141#169#1#189'"'#26#244#195 - +#11#23'-'#193#31'Jp'#232#7#27'C'#191'@'#181'-g>'#249'C'#250#15#156#224#226 - +#254'@'#158#255'8'#231#249#143#6#195','#243#143#169#255#168#250#19#9#216'kV' - +#163#25'`gY'#172#130#250'o'#211#249#27'K'#140#30'R'#247#131'|'#250'/'#27#3'F' - +#241'R'#255#169#1#8#154#19#142#190#242#255#197#24#136#228'%'#195#129'V'#171 - +#213#24#12#6#169'Sp'#25#17#0#238#171'j'#156'yg'#4#162#170'*'#193#12#160#162 - +#10#163#173#4'l'#21#179#144#0#180'<'#9'hy_'#128#154'9'#3#169'T'#24'dBX'#176 - +#135#14#194#144'$'#205'~^e'#250#191'3)A~'#136#215#151#31#1#174#198#209#212#2 - +'\zP'#133#224'9'#207#241'c'#214#135#26#141#226#139#253#19#252#129#216#234#207 - +'l'#170#250#139#176#208#31'9'#255'H'#240#131'H'#0'>G'#27'8jWC$'#28'I'#241 - +#254'O'#176#182#175#213#170'b'#30'R'#255#241'O'#23'5'#0#1#222#254''''#239'?' - +#223#16#164#163#183#6' '#221']Z'#191#223#134#16#14#164#254#0#248#183#21'U'#13 - +#150#21'H='#2':'#130#186#5#141#30'3+'#17'f'#177'~'#185#2'Tj5'#20'T'#239#10'z' - +#139#141'E'#2#136#0'4d'#10#176'f!*'#166#5'0_'#128#140#159'(<'#194#132'w'#168 - +#160#130#159#215#151#253#14''#16'cI?'#217#134'B'#165#129#179#158']'#157#222 - +#131#242#131'>'#133#142'?'#204#246'G'#2#8'3'#225#231#9#0#247#142#186#159#192 - +#239'la'#142'iA'#253#215#169'"'#238#241'V'#199#175#188#250'O'#9'@'#164#254 - +#179#240#31'%'#255#144#250'O'#225'?\'#160'='#253#9#255#9#24#16#1#208'&'#152#1 - +#8#19#153#1#148#21'H'#4#128#143'Umu'#22#156#25#142')'#11#133'.A'#20#247'7' - +#228#149#129'm'#220#206'I-@C$@CDY'#179#16'!/'#128'F'#137'e?E8'#151'AQ'#136'7' - +#174';r'#172#234'O"'#168'tF8'#227#201#244#246#148#16#135#253'('#238'O'#158 - +#255'H'#16'U"'#0#15'n>'#170#254#179#163#250#191#10#205#189'0'#243#15#8#234 - +''#185#217#185#193#168#14#145#211#143#169#255#192#13#1#173#23#17'@'#147#160 - +#254#163'lF'#250#10#255#9#24#168#196'13'#160#176#176'PE#'#195#168':'#144#6 - +#134#224#133'T'#145')'#224#10#234#23'4{'#205#7#2#175#210'S'#21' '#211#2#198 - +#239#14':'#139#5'4F'#29's'#10#170#13'|'#207'@'#181#138'=G!'#231'g'#9#202#248 - +'K'#234'O'#154#224'`'#223#193'0@'#235#166#159#224#223#183#159'6,'''#31#143#20 - +'h'#205'yp'#234'c_u'#255'`_b'#213#205#239#151#21#251#240#163#190'H'#176#227 - +#201#213#191#211#249'G'#171#191#179#254'g\'#253#155'8'#231#31#175#254'k'#149 - +#17#239'x'#171'}='#10'{'#132#138''#200#251'O'#237#191#240#200#181'|'#2#16 - +#169#255#164'&v'#168'T'#170'`w3'#0#251'{'#169'}>_0'#3'('#26#128'['#168'K'#16 - +#158#188#130#8#0#168'm'#184#179#240#244'HLa'#21'j'#3#148#168#234#27#243#202 - +#193':n:'#168'Q'#11#160#193'!D'#4'j'#22#18'$'#18'P'#240'aA9?V|'#16'W5'#130 - +#176#229#235#247#224#243'G'#174'a'#182#223#24#164#3#133#1#143#190#251#157#244 - +#28#140#15#252'S."'#202#175#254'h'#223'G'#130'a^'#253#15#176#172#191'@'#135 - +#19'W'#255#239#185#213'?'#130'$'#193'1'#7#140'3w'#252'f'#214#4')'#244#23'F' - +#185#242#240#201'?'#228#253#167#240'_'#13#202'"'#205#4'l'#31#136#247'_'#192 - +'`D-'#165'8'#8#184#193#161#204#25'H'#154#128'#`X'#208#234'3-'#21'F'#131#145 - +'3P'#169#209'@'#225#132#221'@k'#178'0'#19#128#242#2'Th'#6#8'Z'#0#171#20'Lv' - +#16#150'%'#157'&'#253'm$:R'#176#254'?+'#224#235'g'#239#200'l'#14#244#24#250 - +#133#233#251#159#8#11#207#186'eP'#175'M'#249#221#242'!?'#230#253#143'q'#5'?' - +#156#237#31#134#8#31#250#163#212'_'#218';'#27#214#129#223#209#8#209'H$'#25 - +#251#215'('#162#254#9#182'v'#234#250'K1}'#10#3'Q'#247#223#22'~'#213#175#193 - +#191'k)'#249#167#175#246#223#189']'#235#128#223#159#208'-'#24'm'#13'='#178 - +#143#141#204#0#188#191#146'i'#1'2Y'#229#22'{'#193'I'#145#184#194#196'r'#2#228 - +#10'P'#170#149'`'#200#175#2'k'#233'T\'#253#181'H'#2#168#5#232#185#196' V)H' - +#179#4'),'#168#232#140#10#176#19#245#148'" zk'#178#228'?'#195#27#171'^{'#8'V' - +#191#254#176#212#151'1'#6#30#135#223#186#2'J'#166#207#239#246#177#148#223'_/' - +#191#189#4#223#232'#E'#245#167#213'?'#24'a5'#255#17'?'#231#252#11#177#216'' - +#7#180'o['#9'Q$'#6'z'#14#181#6#163#215#149#153':'#182'X'#180'A'#154#248#19 - +#225'{'#255#145'&@+>'#9'>'#133#255#234'q'#223#170#211#233#220'='#13#0#237#13 - +#131'"'#0'J'#10'B'#150'a'#205'B'#3#129#128#153'J'#132'c'#177'X'#5'e'#6'rZ' - +#128#17#181#0#227#158#130'3'#144'T|'#138#255#231'O'#216#3'W'#19'G'#2#212'4T' - ,#207#151#10#147'CP'#221#233#16#148#241#166#128'l'#7#233#30#204#202#152#251 - +#236#240#213#147#183#192#175#31#189','#245'e'#140#129#7#253#6#207'~'#249#215 - +#129#190'Jt;'#145#12']'''#189#254'd'#207'Gx'#199#31#173#254#254'P'#210#251'O' - +'{W'#227#175#224'u'#212#177'~'#0#148#31'@i'#194'jE4Pmk'#251#5#229'!'#138#199 - +#160#252'o'#15'nT'#250'['#207''''#255'lG'#217'#2p'#244#167#246#191#175#171#30 - +#208#187'%g'#160#201'dR'#163#218'a'#196#191'Yj0'#229#3#224#237'*|'#175#228#11 - +'8>'#154'P'#232#153'3'#16'Ww'#188'P0'#20#146#22'0'#133'9'#1'Y8'#144'6=_)H)' - +#194'h'#6'P'#18#145'L('#22#202#206'lQIa'#175#217#0'o.'#251#157#212#151'1'#6 - +#17'J'#166#205#135#195'niH'#199#232'n'#245#143#161#240#179#184'0'#196'9' - +#255'|A'#206#1#232'qC{'#205'w'#168#25#132'x'#207'?'#183#250#151#24#221#219 - +#172'Z'#155#200#249#151','#253'%'#225'GY'#169'C'#185'k'#182'Z'#173'.'#148 - +#195#192'@'#156''#2#6'M'#0#130'3'#16#153'G'#231'r'#185#200#233'W'#140#23'T' - +#1'\'#153'pe'#155#207#184#208#30'0'#206'Kj'#1'H'#2'*'#141#22#242'(='#216'bE' - +#2#208'$'#137#128#180#3#133'P($T'#11#138#251#6#140'`'#22'x'#251#250'c'#161'u' - +#211#26#169'/c'#12'"'#28'q'#215#155'PP'#189#243#160'_'#207#249#252'D'#141'>H' - +#168'#'#188#227'/'#192#173#254'L'#248'}AF'#4#174'z'#180#253#157'h'#251'S'#211 - +#15'z.'#190'T%'#139#134'&'#230#183#255','#227'l'#255#16#18#0#139#253#163'l4' - +#144#240#227'mr'#0#178#206'?]b'#255#253'V'#255#9#131'&'#0#232#146#19#128#23 - +'H'#163#195'h'#0'[%nU1'#210#2#28#133#199#198'P'#180#217#160'p'#210#2'P'#192 - +'u'#150'"'#176'U'#204'fu'#1'I'#2'`'#14'A$'#1'V.'#204#153#2#192'L'#7#254#242 - +'Fh'#170#176#223#217#10'/'#159#183'p'#204#233#151'C0'#20#148#194#9''#255'b' - +#208#175#151#241#169#190#204#8#136'w'#198#252#185#130#31'A'#245#199#205#31'`' - +#197'?'#1#23'e'#253#173#225'<'#255#209#206#213#191#216#232#169#177'i}'#173 - +#252#234'O'#153''#212#5#150'B}'#245'T'#246'KN@Z'#253#145#0#156'~DSw'#184 - +#222'!|V'#201#10'A'#179#217#172'#g '#222#199'B'#130#184#175#162'=j'#1#11#218 - +#253#134#185#204#169'Ge'#194'D'#2'j'#13'X'#199#205#0'}~)S'#255#213#6#29#211#6 - +'Tz'#13#31#22#228'{'#6'$'#253#1#248#142'd'#217'm)'#158'-|'#244#231's'#161#246 - +#135'O'#165#190#140'1'#136'p'#240#205#207'C'#217#172#133#131'|5'#159#236#203 - +#219#253'B'#177'O'#156#217#254#188#234'O'#4#224#193#149'?'#192#217#254#142 - +#237'?@'#200#211#193'&'#1'1'#2#192#255'T'#242'hp'#162#205#142#182'"'#202#135 - +#254#216#234'/'#196#254#129#235#250'['#143'2'#215'N'#206'?'#170#252#235#173 - +#243'oo'#24#18#1#144'3p'#221#186'u'#172'QH '#16'0'#161#6'P'#128#23'Im'#195 - +#153#22#128'W2n'#155#179#224#247#225#184#202'"h'#1#148#2#172#209#27#208#20 - +#216#13#212#148#19' '#242#5'(u'#184'Qr'#16#159#27' h'#2'2'#161#157'xOq'#193 - +#158#222'E'#142'/'#172#207#158#176#19#174#12'c'#181#253#185#130#202#249#251 - +#192#254#215'='#193#253'1'#136#223#148'X'#248'i'#229#135'8'#151#238'K'#142'=' - +#177#227'/B'#234'?'#222#246#180#214#128#167'e'#243#14#171''#185#197#181#209 - +#168#10'R'#184#143#250#190#177#208#31'n'#173'(_'#245'|'#209#15'%'#0'5S'#230 - +#31'j'#224#254#193'8'#255#4#12'uYMf'#6':'#28#14#189'R'#169#180#225'E'#22#227 - +'F'#137'AL'#11#240#133'5'#211#235#220#182'}'#4'-@'#193#180#0'5'#24#11''''#128 - +#185'x"'#175#5'p$'#192#180#0#161'u'#24#223'>'#172#147#4#248#203#29'A'#138#192 - +'sD'#0'c'#21'~9'#1#26#22'z'#242#243#171'Q'#251'T'#15#238#0#188#25#215#185#242 - +#243#170''#152's'#252#145#131#143#179#253#3#16#246'"'#17'x=,'#233''''#18#12 - +'B'#140#250#253'E'#185#196#31#20'|G'#133#197#181#5#15#21#19'V'#161#237#23 - +#169#253#192#245#252'k'#224#203'~'#221'}'#245#253#239#11'C&'#0'A'#11#160#182 - +#225#168#9#144'/'#160#0#137#128#249#2#144#8'H'#19'(k'#240#216#246#246#132#181 - +#149'r'#150#29#168'`5'#0#10#10#11'V'#206#3#141#201#10#26'#g'#10#168#146#17#1 - +'5'#243#7#176'b!'#218#152'#'#145'+'#27#30'I'#166#192#138#211'v'#129#144#215 - +'%'#245'e'#140'zP'#225#218#161'w'#188#10'E'#211#230#13#234#245#9#174#212#143 - +#247#250#199#217'l'#191#4#223#230'+'#30#140'$W'#138#251'3'#199#31#222'v5' - +#252#2'AWK'#202#234'/C'#218#168'F'#213'_)'#143#134'zZ'#253#5#207'?n'#142#254 - +#182#253#234#245#189#167#227#243#19#215#7'h4'#26#214'4'#148'O'#15#166#188#128 - +#242'h\^'#177#213'Yxp'#2#215'uA'#11'P'#168#212#160#181#20#176'ra'#181#174'S' - +#3'P'#11'Z'#0#229#6#8'Q'#1#190#155'0'#136#202#134'G'#130'_'#240#213's'#22#130 - +#175#189'Q'#234#203#24#213#160#197'e'#191#27#158#132#10'T'#255#7#140#132'x' - +#166#31#231#244'#Afv?'#173#252#225#8'K'#250#137#6'B'#172#221'W'#132#247#250 - +'S'#165#159#139#154'}'#160#240#211' '#16'a'#245'/'#212'{'#234#10#244'>'#10 - +#243'u]'#253'i'#236'7'#181#253#222#142#130#223'@UH'#8#238#129#230#253'w'#251 - +#254#211#241#25'v'#213#2#240#194#10#132#136#128#160#5'8'#2#198'9'#173'~'#211 - +'l'#210#2'@'#161'`'#14'A'#5#154#2#150#178#157'@'#159'W'#202#146#130#152#240 - +#147#22#160#227'H'#128#181#15'S'#241#237#196'EY'#130'2y'#186'.]Z'#252#235#210 - +#3#193'Y'#187'Q'#234#203#24#189#192#223#210#146#203#255#10#19#247#26#194#236 - +#6#22#231#7#214#224#143#229#250#243'N'#191'X'#132#19#254#8#175#250'G|'#156#6 - +#16#246#243#142'?'#159#155#21#4#9'q'#149'<'#26#168#206'k_'#143#199#139#241 - ,#171'?y'#254'Y'#213#31#173#254#184#175'U('#20#181#180#250'k'#181'Z'#7#146#192 - +#144'W'#246#17#164#235#163#236'N'#11' _'#0'p'#17#1#26'$R'#180#213#153#191'' - +'8'#166#178#10#217#129'$'#220'j'#189#145#229#6#168#13'z.)'#136'O'#17'V'#178 - +#220#0#222#20'`'#141'D'#249#14'Bra'#196#24'$'#213#128#225'J'#5#31#222'r24' - +#174#249'R'#234#203#24#149' '#13't'#191#27#159#134#178'9'#139#7#252'Za'#213 - +#231#179'}x'#187#159#19'~Z'#209'i'#245#143#133'"'#201#176#31#229#252'GI'#248 - +'q'#239'i'#217#10#222#214'm'#140' '#152#237'O9'#255#248'_'#165#217#177#193 - +#160#14'S'#166#31#9'4y'#134#133#145#223'Md'#251#147#240#163'L'#213#167's'#245 - +''''#164#141#0#186#243#5#0'7H'#180#146#15#13#150#249'#'#234#9#181#238#252'%l' - +#29'Wt'#154#2#134#252'r0'#151'N'#229'j'#3#152#6' r'#8'R'#247' '#149#146'+'#24 - +#18#154#137#178','#193#225#239#24'l\'#243#5#252#231#230#147#165#190#140'Q'#7 - +'CA'#25#28'~'#223#187'h'#130#230#15#252#197#157#195'y:'#139'|'#226#156#205'O' - +#182'?'#173#234'L'#245#167#132#31'a'#245#199'-'#26#8#254'{_'#22'kYv'#158#181 - +#246'x'#230's'#238'XsWwW'#187#219'v'#187#227')'#241#0'v'#136#193#145#8#194'F' - +#8#5#148#4')'#145#176'P'#132#132#20#241#18'!9'#188#0'/H'#188'!'#224#1'x'#2#9 - +#17#144#128'XHH$`x'#128#7#144#172'$'#216#198#221#158#186'k'#174'[w:'#243#176 - +#7#254#239'_'#255'Z{'#237'}'#207#173#186#213']w'#170#190'K'#218'w'#15#247#12 - +#251#236#189#191#239#159#255#165#166#253'='#181#243#222'wT2'#155#209'k'#23'\' - +#25#8#159'A;'#158'n'#223#232#238#161#229#19#192#159#152#184'?:'#254#162#230 - +'_'#233'Yn/'#145#254#0#255#7'*'#27'}'#158#208')E'#4#136#173'V'#137#165'.' - +#139#22#128#174'A'#215'Q:|o'#208#251'b'#214'x'#153'+'#255'B"'#129' d'#144 - +#247#174#146')'#176'zUH@'#8#160#225#248#3#184'V@'#155#2'l'#14#248#158'T'#15 - +#150'I'#158#23'?'#201#243#206'x'#28#144#198#191#254#181'O'#169#217#197'D' - +#159'''3'#232'ay'#245#203'_S'#191#240#219#255#248'H/_'#250','#229#198#225'/' - +#137'>'#22#252#186#208''''#227'2_m'#247'c'#129#218#191#24'C'#19#152'p'#155 - +#175#249#184#207#182#191'Q'#253'=z'#247'k'#171'['#28'x)'#138'}JY'#180'_<' - +#255'h'#251'u'#239'yK'#254']'#207#243#242'.'#203#11#160#31'q'#21'~'#0#163#5 - +'dyp'#229'G{'#27#191#152#229'~'#13#0#246'E'#11#136#234#13#181#242#210'gT'#173 - +#211#213'&'#128#144#0#182#131'Z'#173'0'#5#196#31'P'#174#28'<'#191'Z'#192'w' - +#254#229'?P'#244#187#23'U'#128#199'=z/}D'#253#153'o'#254#11#213#189'~'#235 - +#253''#8'W'#248#228',ssW'#242'['#187#159'l'#250#217'\KV'#253#167'"'#253#231 - +'j'#255#222#247#213'd'#239'>{'#253'a&(N'#249'e'#199#223'{'#235#205#17'z'#251 - +'C'#250#163#3#12#247#251#163#5'Y'#128','#253#197#7#240'p>'#159#239':'#158#255 - +#15','#253'1'#158'7ll'#227'Pd'#7#146#22#128'9'#178'.!'#18' Q'#1#172'/'#239'O' - +'ko'#220#31#174'~'#206#19#135#160'1'#5#226#246#138'Z}'#233#211'l'#2'@'#250'G' - +'-q'#8'J'#243#16'?'#150'Ta3'#211#176';'#229#184#167'='#186#207#214'A'#244#244 - +'G'#150#204#213#191#250'K'#175'?'#243#172'D'#23#227'h'#163#181'y]'#253#220'_' - +#251#166'z'#229#231#223'g'#193#149#17#252'z'#30#175#178#211#15#246#187#145 - +#252'H'#248#129#228'g'#2#152#217#176#31'r'#255'G'#219'w'#200#246''#135'$' - +#255#140'I"Ou'#185'o=\'#244'_'#233'm'#191#157#231#252#137'\'#239'o'#26'~@' - +#250#211#254'm4'#251#132#244#199#12#192#181'Z'#13'>'#130#217'Q'#27'~'#30'e<w' - +#2'0'#165#194'd'#10'`'#18#145'6i'#2'0'#180#174#144#9'pC'#180#0'D'#7'6'#239#15 - +#186#159#221#159'7_e5'#222#15'X'#186'C'#213'o'#174#222'P'#157'+o'#20'Z@C'#155 - +#2'Q'#3#25#130#226#15'0'#165#195#156'#P'#20#14#217'_t'#206'b'#132#255#229'w~' - +'U'#221#255#206#255'8'#237#211'xq'#6#221#255#205#143'~V}'#254'7'#255#158'Z' - +#227'S'#31#236#179'rG'#245'W'#134#0#164#179'O'#166#139'|'#24#252#28#242'+' - +#164'?l~'#238#246#219#223'U'#251'w'#255#144#247'a'#247's'#216#143#136#195#247 - +#211#197#173#149#199#223#13#253#12#30#255#212#244#250#151'v_'#8#5'b'#210'J' - +#164#253#222#165'}h'#3'{'#200#249''#150'~G'#186'T'#199'q'#249'M'#195#144'$' - +'I'#26'q'#28'w'#137#197'6'#209'5'#8#254#0'D'#4#232#7']'#165#179'_w'#227#203 - +#179'$^'#209'*'#189'N'#19'F'#135#160#238#213#143#171#230#218#21#142#4#128#4 - +#160#1#128#8'8*'#0#240#219'.B'#1#147#7#28#131'6G'#192'w'#136' '#247#158#173 - +#155#0#212#187#252#228#201'c'#255#246#15#213#239#253#141'_'#184'('#10#250#128 - +#195'''-'#242#229'/}M}'#238'7'#255#174#170'u'#215#138''#28#225#190'.}N<'#29 - +#232#231#2#31'%'#157'}8'#214#159#234'L?'#168#252#0#255'L'#171#254#139#169#145 - +#254#180#158#234#148#223#221#219#223#161#253'>'#251#6'x'#138'/'#237#248#203 - +'otw'#223'i'#199'3'#132#249#172#227#143#176#129#164#159'-H|'#241#252#179#227 - +#143'0'#180'3'#164#177#183#183'7{'#214#134#31'O'#27#199'B'#0'J*'#5#209'6'#140 - +#206#187#213'h4`'#10'\VZ'#250#223#148#245#165'E'#26'\!'#18#248'R'#166#194#136 - +'I'#128#147'~"'#235#15#136#219#29#173#1#136'C'#16#25#130'a='#226#181'o'#138 - +#134'l'#142#128#152#3'b'#18#148#148#128'e'#191#242#164#177#246#148'+'#253'?' - +#255#225'o'#169#31#253#193#191'='#225#147'z1'#6#188#249#31#251#139']'#253 - +#204'_'#254#155#207'>'#163#239#147#158#13#145#250#182#194#207#218#252#162#250 - +'/t'#184#143#19'z&s'#209#0'D'#253#167#237'>'#236#254#253#251#252#154'<Ix'#14 - ,'@'#152#14'k'#141#209#189'K'#205#193'=Q'#253'M'#216#15#234#253#142'8'#254'P' - +#241#7#231#31'&'#250#216'B'#193#207'h4'#154'<k'#187#175#247#251#243#159#203 - +#231#154'~'#1#180']CX'#144'~'#216':'#28#130#240#3#208#177#155#178#189'1X4_' - +#185'7'#232'}'#6#0'F'#251'0'#246#7#144#132#143#218#171'j'#229#198#167'8"'#192 - +#154#0#231#6#196#188#246'k'#210'@'#132'5'#6#9#15'J~'#128#206#24#244#139'l' - +#193'\'#251#7'Nk'#210#209#163#14'<\'#191#251'W>'#174#230#163#254'i'#159#202 - +#185#25'+/T}'#246#27'G]'#255#220'W'#223#215#251#151#206'>g5'#254#188'P'#251 - +#179'\l~'#29#235#207#165#188'W'#199#251#181#218#159#10#248#19'&'#130#153#26 - +'o'#223'S'#253#135'?`'#211' [,'#172#215#191#17#206#251'/'#147#221'/'#223#4 - +#213#31'&'#192#152#22#132#130#224#12#180#170'?-'#152#231#15#14#193#145#211 - +#236#243#185'I'#251#155#143'iX'#135' &'#20'%'#192'cZ'#241'Mt'#14#130'/'#0 - +#166#0#189#230#10'-k'#15'G'#189#183'v'''#205#151#141'w_;'#250'b'#213'\'#135 - +'?'#224'u'#21#213'$1'#168#17'["'#240'M[q'#19#30#180'='#5'i'#9#138#217#134'r' - +#201#21'8'#15'n'#129'{'#255#231#191#169#255#250';'#191'z'#218#167'q'#166'Gs' - +#253#138'z'#233#203'_S?'#243'+'#191#165#234#171#155#207#245#179'M#'#15'//'#8 - +#0#21'}'#153#16#0#219#253#146#230#11#149#30'E> '#0#150#254#0#255'Tk'#1#179 - +#193#158#218#187#253#135#244#191#194#238'W'#176#251#189'd'#241#234#202#246 - +#247'B'#178#255#149#168#254#180'L'#137#4'0'#203'/:'#253#222#147'f'#159'w$'#1 - +'h'#219'8'#254#158'W'#216#175':'#142#149#0#224#16#252#214#183#190#197'MC'#136 - +#201'Z'#244#195'`'#152']'#22'-'#0'$pM!J'#160#188#149'w'#247#214#190'0Kk=eH'#0 - +#210#157'H'#160's'#249'5'#186#233'/IRP'#205'j'#1#8#13#194#28#240'b'#157'#' - +#160#195#131#18'"4'#25#131#158''''#10#192#249#9#19#254#254#223#254'e'#245#240 - +'";'#176'4'#26'kW'#212#205#159#255#154'z'#235#24'@_'#26#206#196#157'6'#209'G' - +'&'#241#204#165#161'g.'#177'~V'#253#167#218#238'O'#5#252')'#183#249#30#178 - +#211'o1'#30'j'#233#159'&'#186'4'#152#237#254#189'w'#218#209#180'oTz>9'#230 - +'Ok4'#250#228'f'#31' '#0#244#249#203#178#12#190#128#253#227'p'#252#185#227 - +#184'aa'#29#130#244#163'j'#164#1'tMn'#0'H@'#136#0#29#133#215#147'<'#218#248 - +#233#222#250#23'3'#130#189'q'#10#194#31#0'{'#191's'#245#227#170#209#187',' - +#192#143#181'?'#192'5'#7'b]>\'#20#14'A'#11#208'D'#144#151#10#136#156#159#251 - +#164'_~'#18#254#129'C'#190#31's'#1#254#222#175'A'#141#30#221'9'#129#147'8' - +#187#163#177'vYK'#250'_'#251'['#31#12#244'O{'#194#141#202#159#231#142#237'o' - +#192#159#217#226#158'\'#178#252'8'#140'7[X'#192'C'#242#167'Sc'#255#143#9#252 - +''#164'f'#163'}Q'#253#19#235'7Xo'#142#238'm6'#251#144#240#244'Hz'#166#216'g' - +#12#144'#'#227'O'#21#210#255'.'#225#4#181#254#187#180' '#230'?'#251#250#215 - +#191#158'>O'#199#223#179'\'#158#231#241#249#236#16#188'v'#237'ZD?'#166'I?' - +#142'M'#1':~]'#242#2#224#16#132'V'#176#218#159'7n'#222#31#174'|'#138#253#1 - +#220#16'$'#212#243#6#196#177#234'^{K'#213'z'#235'L'#0'69'#168')'#249#1'5m'#10 - +#232'D'#161'P'#207'1`'#252#2#158'8'#6#13#1'x:'#195#235','#155#4#217'|'#170 - +#254#227'o|^M'#182#31#156#246#169#156#232#208#160#255#243#234#19#4'zl'#31#247 - +'0A'#23#207#216#251#210#196#147#255'!'#192'5Y~'#185#128#31#146'?'#19#181'!$' - +#144'Ng'#236#245#239#223#253#174#154#13'w4'#248#209#223#15#170''#134'x?'#236 - +#254#157#183#229#27'!'#253#145#245'7'#161#239#178'1I'#246'A'#143'?T'#254'm#' - +#227#143#204#229#217'q8'#254#220'q'#18'0`-'#128#214#193#189'{'#247'0'#181'x' - +#11#141'C'#148#6#189'!'#1#248#5'6i{e{'#210'}'#227#241#164#253#154'.'#24#242 - +#25#208' '#129#176#214'P'#221#171'oq'#255#128'@'#204#129#192#248#3#140'S'#208 - +'!'#1#27#29'p'#27#140'B#p'#253#1'v6'#226#167'\'#134#147'L)'#150's'#193#148 - +#224#223#250#245#159'S'#211#189#173#147#251#238#19#30'A\S'#221#151'^W'#215 - +#190#240#139#234#245#191#240#13#213'XN'#160'?'#202#253't<'#253'&'#191'7wm' - +#254#212'H'#254#2#252#25#131'_'''#251'X'#181#31#14#192#217'L'#245#31'|_M'#247 - +#183#216'<p'#237#254'0XL'#9#252'?'#8#189'tQI'#248#225'R_'#153#225#23#141'>' - +#239#186#170''#179#217#156#16'^'#22#199#225#248'+]'#138#227#248#208'e'#223 - +'cL'#1#218#174#199'q'#220'Y,'#22'k'#196'vp'#2#26#18#192#246#6#189#180#247'`' - +#216'{s'#214#188'a'#253#1'a'#164'g'#24#170#183'T'#239#198'[*nuu'#211#16'h'#2 - +#13#153'h'#148#246#217#31'P3'#230'@A'#2'z'#242'Q_'#151#17'{^'#209'r</'#251#6 - +#242#202#229#240'N'#185#167'XBv'#228''#250#198#159'T'#227#199#247'O'#245'<' - +#158#215#128'F'#215#190#246#138#186#242#179'Z}'#228'k'#191#161'z/'#244#216 - +#190#235'i'#247#210#230#243#155'F'#30#166#176'G'#156'}'#214#219#15#135'_R' - +#128#159#157'~'#211'"'#236#135#253#193#131#183#9#252#247#11#143'?'#252#4'Y' - +#170'B/'#153#223'$'#240#199'~2sC~2'#193#7#167#251'*'#173#250#223#145'\'#255 - +'GF'#245''''#201#143#196#160#244'8'#165#191#190'.''3'#216#20#248#202'W'#190 - +#226#147#25#16#175#172#172'4'#8#252']'#250#161#27'2'#185'('#204#0#16#1'G'#5#8 - +#176#157#187#131#149'O'#14#231#141'K'#202')'#29#230#28#129'f['#245#174''#146 - +#236#255#150#245#9'0'#17#196#146#31' '#154#128'I'#27'6'#181#3#158#211'_'#176 - +#152's@'#251#8#220'^'#131'O'#157#233#229#20#198'w'#254#233'7'#213';'#255#254 - ,#159')'#253#12#157#159#1#226'mn^W'#151'>'#253'e'#245#234'/'#253'U'#181#249 - +#214#23#143#241#203#158#208#198'/'#175#188#206'H{'''#179'O'#247#242'3'#224 - +#207'X'#234'g'#21#201#207#241'~'#2'}'#6'{_'#136#0#165#189#147#157#247#24#252 - +#186#181#151#158#212#211#207#210#228#229#149#237#31#212#130#197#196#181#251 - +#149#14#249#193#238#223#18#213#31's'#252'q'#200#143#182'w'#8#15#195#231'Y' - +#236's'#132#203'vb'#195#166#9'_'#191'~'#29#154'@'#11#181#2#240#7#208#15#191 - +'*aA'#204'1'#8'=p5'#247#252#206#157#254#234#167#198'I}'#213#23#167#160#23#232 - +','#192#184#217'c'#18#8#26'uM'#0'l'#18'D'#146')'#136#16#161'4'#18'A'#152#208 - +#180#21#243#29#18'0'#221#133'<!'#0#229#2#255'9D'#12#142'a6'#227#225#253#159 - +#168#255#254#219#191#172#134#247'~z'#130#183#236#217#6#8#186#190#178#169#214 - +'>'#254#179#234#213'?'#251'+'#234#218#159#248#165#163#191#249#176'k'#246'<' - +#158'PG'#218'kG'#191#201#231#207'm#'#143#194#230'/'#210'{y'#153#27#169#191 - +#208#192#135#148#135#250'O'#132'0'#217#190#163'F[?"'#146#152'Ks'#15']'#223 - +#143'?7'#187'{o7'#194#217#208#177#251#231#244'l'#163#197#23#18'='#182'%'#215 - +#159#193'O8'#128#25#176#141#132#159#157#157#157#169'x'#253#13#248'_'#24#2#224 - +#239's'#19#132#8#248#173'$I'#216#31'@'#11'B'#130#208#4#216#31'@'#235#149'\' - +#249#157'w'#247#214'?='#205#162#14#146#132#152#4#184'7'#0#145'@gMu'#174#189 - +'%'#229#194#162#1#152#252#0'h'#1#146'-X8'#6#203#154'@a'#18#248#197#228'#N1' - +#209'Y'#237'='#248#255#254#205'?R?'#248'w'#255'DMw'#30#158#234'y'#4#245#166 - +'jn\S+'#175'}'#130'$'#252#159'R7'#190#244#231'T}'#253#202'i_'#158#202#200#203 - +#210'_Rz='#227#236#147'r^'#6#191#163#246'['#240#139#202#15#224#179'z'#207'*' - +#191#246#1#204'v'#31#168#193#195#183#233'usn'#238#193#239'Cyo'#154#229'W'#187 - +'{?'#236#196#147#190#168'l'#214#238#23#167#223'.'#236'~ZX'#245#135#211#143'0' - +#240#152#222#187'O'#166#241#4#9'?'#199#233#245#175#142#211'x'#202'}'#152#2 - +#180#6#9'X'#0#217'?'#151#29'S'#0#26#1#252#1#221'$'#243#187#239#246#215'?' - +#179#200#163#134'o'#204#1#216#247'$'#233#153#4#174'|'#156'H'#160'nA'#207'f' - +#128'h'#4'L'#2#145'&'#1#21#233#168#2#147'@h'#8'@r'#5'|'#129';'#214'2)'#225'Y' - +#159#166'|'#240#222#219#234#143#255#249#223'W'#15#254#247#31#208'C99'#190'/' - +#162'k'#17#183'{'#170'u'#245'e'#181#250#209#207#168'+'#159#251#170#186#250 - +#249#175#210#245#175#159#246'%x'#250'03L'#219#130#30'c'#231#23#253#251#140 - +#167#223'H~V'#225#231'I'#1'x'#6#255#156#155'{'#178'&@'#255#155#238#222'U'#163 - +'G'#210#206'{Q'#168#253' '#146#203#173'}L'#231#181#131'/q'#156'~H'#245'E'#188 - +''#15#133'='#180#141'F'#144'P'#251#225#244'{D'#207#245#222'I'#218#253#238'8' - +#141'G|'#169'?@f'#22'b'#167#160#210'Z'#0#182#215#233#1#236#204#147'`'#229#189 - +#254#250#167#19#21#198'<w`'#16'J'#235#240'HE'#173#158#234'^}'#147#204#129#166 - +#206#12't4'#128#192#201#22#132#230#224#217#254#130'E'#132#128#9'AL'#129'R' - +#171'1[S'#228'-'#255#5#238'8'#229#26#158'{'#255#235'?'#171';'#223#254#15'j' - +#239'G'#255'W'#141#31#222'V'#139#241#224#169#14#11#238#197#16#197','#201#163 - +'v'#151'C'#172#245#181#203#170'y'#233#6#131#189's'#253'#'#170#251#202'Gy'#251 - +#204#141#167'\'#255#220#252#205#197#225'o[w)V'#249'mr'#15'@+'#9'>'#28#234'#p' - +#231'R'#217#151'I'#184#15#142'>'#14#251#137#31'`'#188#253#30'-'#239'jR'#224 - +'i'#188'S'#142#22#0#252#27#205#193#237#245#198#240#145#11'~z'#182#140#221#15 - +#240'o'#153'J?z'#230#239#201'4'#223#232#240#131#6' '#199#150#237#247','#151 - +#242#196#190#215#245#7#208#197'h'#18#248'{'#164#10'm'#18#192#175'Hx'#144#179 - +#4'iY'#3#9#204#210'x'#237#246#254#250'''3'#207#15'Yr#<'#24'j'#18#8#27'm'#213 - +'!'#18#136#154#29#2'~h'#181#0#228#8'x'#236#20',H'#128#181#129'J'#152#208#212 - +#17#24#18'`'#231' t'#148#220#153#144#164'`'#132#234#147#246#244'+y'#10#4'1' - +#219'{'#172#230#253#29'5'#31#236#210'zO-'#134#251'\9'#217'}'#249#13#213#185 - +#249#250#217#149#224'G'#189#142'K'#239'C'#17#222's[u'#219#16#159#11#254#170 - +#212#151#220#254'L'#154'z'#148#9'@K'#255#209#214#143#213'd'#239#30#189'n'#206 - +#145#1'~'#159#228#248#175#214'F'#15#174#180#251'w'#243#2#252#200#244#131#221 - +#15#240#195#238',!'#191'{R'#223#15#27'n{:'#157#14#136#0#160#194'%''e'#247#31 - +#245'r'#31#251'wW'#243#3#200#28'@3'#209'Md'#10'*'#237#16#196#26'$'#176'J m' - +#141#23#181#205#187#253#213#183#136#4#2'm'#207#11#9'p'#4#160#201#230'@D'#234 - +#170#213#4#156#181#23#11#17#24'M'#128'L'#2#21'j'#191#130'2s'#17#26's'#192'M' - +#30'2UEn'#163#145'3l'#26'|'#232'F^'#10#226#8#224'M'#21#159#210#158'}'#137#239 - +#187'i'#189#218#230#215#128#207#13#248#173#228'_0'#200#217#241'7'#211#161#190 - +#225#163#183#213#172#255'H'#146'|'#140#218#175'k'#251#187#209'd'#235'zw'#239 - +'=m]d'#153'x'#252#145#236#3#240'#'#151'['#233'y'#253#24#252#216'F'#178#207 - +'I'#198#251#15#27#167#253'('#151'R'#133'I'#11'h#'#25#136#180#128'K'#208#4'h' - +#31'Z'#0'H'#0#164#176'B@lM'#147'x'#237'N'#245#19#169#23#198#218#179'/>'#1 - +#174#12#172#17#9'|L'#197#157'uM'#10#198#15'`'#192'/m'#198'=!'#2'_z'#10'('#19 - +'%'#16#231#160'2'#230#128#239#21#4'`'#174#214#139'>]'#241'y'#25'.'#234']sGl|' - +'O'#202'wm'#3#143#170#179#207'8'#250#0'f'#212#243'/'#28#240#27#231#31#175#167 - ,'j'#240#240#7#164'ImK'#140'?'#209#196#145'j'#240#175#213'F'#247'/'#183#145 - +#226'k'#193#159'I'#154'/g'#250')]'#226#251'H'#230#245#131#228#199#164#30#143 - +'1'#171#207'|>'#31#31'w'#170#239'Q.'#227'i'#223'F'#207'8'#5#209'@'#4']'#132 - +#208'P'#212#9#15'"O'#224#138#201#20#4#9'$y'#220#189#189#191#250#214'BE'#245 - +#18#9#136's'#176's'#249#13#21'w/'#177'/'#192'3'#181#2#177#244#16#176'&A'#164 - +#253#2'b'#14#168#208'/'#146#134#140'i '#13'F'#172'F`'#175#152#163#10#156#159 - +':'#163#23'f'#148#146'xl'#171'.}'#140#193#159'i'#201'oc'#251#169'n'#226#1#240 - +'+'''#204#151'K'#190'>'#171#244'3'#241#250#211#177'\B'#201't'#162#6#247#191 - +#175#18#228#246'''s'#157#21#232#128''#163'1'#184#189#217#28'>'#210#138'F' - +#150';'#146#31'*={'#252#149#238#237#7#240#223#151'p'#223'cH'#254#197'b1'#186 - +'v'#237#26#156#131''''#234#244#171#142#179#240#236'Z'#18#232't:1'#177'b'#157 - +'.T'#27#149#131#232'$'#4#2'0$ '#173#198'A'#2#205'L'#133#237#219#253#181'7gY' - +#220#214'}'#0#196#174#143't)qk'#243#150'j'#172'^'#215#146#222#1#189#217'f-' - +#160#166#147#139'8Dh'#219#142#7#133's0'#240#10#141#192'I rs'#137#205'f~'#224 - +'''='#225#10'?'#211#173'>'#175']'#130#142#248'h='#245#250','#191#178#185#211 - +#151#223#5#191#246#238#235#248#190'J'#181#202#175#4#176#156#155#159':'#246 - +#254'\'#131'?'#183'j'#191'd'#242#17#25'$'#147#161#234#223#255#158'Ji]H~'#157 - +#225#167#210','#191#220#217#255#201'j<'#217#21#193'o'#193#175#138#190'~'#166 - +#190#255#1#164#191#132#254#30#163#190#159#198#168#209'h'#204#143';'#207#255 - +'9'#222#165#227'?'#15'S:'#140#162#161#241'x'#140#134#162#29#169#25#184'd4'#1 - +#165#251#7#160#177'H'#143#174'X'#147#16#218#188#211'_'#251#216'8'#173#173'x2' - +#239#160#206#254#211#18#191#190'rU'#181#214'oiM'#192#250#1'B'#157',$d'#224'I' - +'%'#161#138#138#190#2#166#152'Hq'#132#192#244#23#240#203#145#2#167#243'P.' - +#161#195#234#21#245#202''#158'q'#228'O'#220'='#179#195'{'#234#129#167#255 - +#238'|'#201#207#21#144#27#129#175#14#11#237'9'#133'<*-b'#251#197#162#129#207 - +#210''#190#144'p'#223'B'#180#129#5#255'o>'#216'a'#155'?'#157#142'u'''#31'Z' - +#148#128#223'#'#241''#181#187#247#163'n8'#27','#3#191#210#225'>'#128#31'-' - +#188#31#144'&'#203#14'?'#228#248'#'#220'G'#175#27#161#200'G'#157#146#211#239 - +#131#222#157'c='#23#248#3'~'#252#227#31#251'h%F'#154'@'#195#228#8'`V!'#164#12 - +#131#8'h'#141#164'!4'#26#237#210'Uk'#17#240#234#247#6'k'#175#15#23#245#13'c' - +#14'0'#17'DP'#241#145':'#220'Sm2'#9'PG'#224'K?'#193#2#252#142'6'#192#254#0'c' - +#14#232'm'#155'8Ti9f'#195#134#158#201'(tr'#207#171#253#7#204#173'=KW'#250',' - +#143#202#245#202#157#166#156'%'#208#27#162#200#164'eW'#150#29'H'#231'Ui'#1'z' - +#227#237'O%'#212#151#207#231#142#195'O|'#0'D'#2#147#221#219'j'#178#253#158 - +#222'w$?>'#223#167#15#184#222#219'{'#167#21#204#199'K'#192'ob'#253'h'#234#137 - +'4_+'#249#233#153'}L'#160#223#165#231#23'5'#0' '#137'3'#1'~'#231'2'#159#153 - +#193'$'#240#189#239'}/@x'#16'$@Z@W'#26#137#184#154#0'"'#3#235'('#28#162#27 - +#129#18#227#248#254#160'wko'#222#188#234'['#245']k'#2#200#26#244#227#26#153#4 - +#31'Q'#181#206#166#150#248#174#244#23#223#128#206#17'0'#249#2#129#205#30'TN' - +#6#161#237'@'#236#251#5#1'8m'#201#221#26#3#235#164#170#22#27#157#181'+~F'#134 - +'Wr'#228#185#155#249#1#240'k'''#159'H'#251#180#144#250#165't^'#168#251#137 - +#238#215#175#9'@<'#254#226#237'7'#26#0#19#0#166#233#154'N'#213#136#164#254'|' - +#180#171#227#251'IR'#2''#160#146#217'K'#221#221#31#214#131#197'T'#192'o'#28 - +'~F'#242's'#129#15'r'#252#209#220#3#146#31#26#0#212#254'('#138'v'#7#131#193 - +#136#180#218#19'M'#243'='#210'u?'#237#19'XvN&<'#184#181#181#21#211'Ek'#208#5 - +'4$'#128'lA'#180#24#135')'#192'$@'#251']'#248#4#232'X'#237#209#168#243#210 - +#246#180'}SO8bH@2'#0#137#8#234#189#171#170#185#241'*'#131#222's'#181#129'H' - +#147#129'''-'#199#149'q'#14'F'#21'm'#192'u'#16#154'z'#2'&'#3'eg)'#202#29'2p' - +#181#255#220'!'#133#15'f'#26#188'(C'#210'u\'#137#159#23#181#249#202'sT}'#199 - +#201'W'#168#249'"'#245#197#214#231'2'#222#180'p'#242'1'#232#165#148#215'z' - +#252#141#234'/'#170'>'#219#254#201'B-'#250#187'j'#184#245#14#29#27#219#200 - +#128#174#229#151#137';'#189#249#228#165#238#222#15#185#170#143#235#8'l'#168 - +#143'S|'#149'd'#249')'#237#224'C'#184#15'R'#255#1#212'~'#178#249'w'#233#249 - +#29'6'#155#205')i'#183#201'iz'#252#151#141#179#250#4#150'H'#160'Ac2'#153#160 - +#145#8#155#3'B'#2' '#3#209#4'T'#151#128#136#190#131#181#253'Yc'#243#225#168 - +#251'Z'#166#194'PKn'#169'!'#8'C1'#9#186#164#13#188#193#213#132#0'>'#28#129 - +#158'D'#4#220#16#161#155'9'#168#156'Z'#2#229#18#129#231#23#179#22#251#198'I' - +#232'j'#3'E'#30'a'#238#148#25'z'#213#146#195#3#153'mg'#245#182#188#223#187 - +#249#4''#134#233#191#167#156#198#28'|'#188#2'zP'#168#3'~'#211#173#135'U}'#9 - +#239')'#167'O?'#188#245#249#28#251#11'+'#249's)'#238#201'%'#207'?'''#240#143 - +'wn'#171')-'#153'y]*R_f'#238'iG'#147#237#171#173#253#219#129#151'&:'#151'(3e' - +#189#240#246'3'#248#149#206#242'CO?'#246#248#195#230'G'#168#15#224#167'eH' - +#166#236#20#177#254#179#6'~}'#205#207#238#176#133'C'#4#254#136'.b'#147'L'#130 - +'.H@:'#10']6'#225'A%$'#128#16'!H`'#158#133#237#187#131#222#235#179#172#214 - ,#230'y'#3#140'o'#192#132#10#163#186'j_zM'#197#221#205#2#248'f1'#4#16'W'#142#7 - +#186#211#144#178#243#19#6'em@'#26#143#148'#'#6#158#149#244#172#29','#137#30 - +#152#249#11#142'zG'#206#204#147#227#222#168#167#189' ?'#252#128'i'#197#229'y' - +#14#248'3'''#150#175#28#27#223'Q'#247'y'#31#182#190#27#222'K'#11#137#159#27 - +#208''':'#181'7/'#145'@'#194#245#19'#'#146#250#243#225#142'h'#11':'#188#167 - +#138#30'~'#217#165'F'#255#246'Zc'#244#152'S{%'#212#167'tz/O'#223'-s'#248'A' - +#237'7'#137'>'#15#207#19#248#143't'#239'N'#251#252#12#9#244#251#253#184#213 - +'j5'#232#226#162#197#248#154#132#4#225#16#188#12#173'@i'#18#232'1'#9'('#175 - +#158#7'~'#252'p'#208#189#185'?o]-'#146'|t'#184#15'&'#129'oL'#130#245'W'#8#236 - +#177#205#9#240'L'#5'a'#172'5'#6#171#9'p'#10'qPT'#21#210':'#15#220')'#202#140 - +'V'#224#21'D'#160#138#237'"JP'#172'K%'#200#206'f^9'#236'='#235#173'z'#158'w' - +#245#153#31#215#188#152'>'#219'='#149#188#188#145';/'#178#245#249'n'#18#143 - +#177#243'MG^'#145#250'*u$'#191'c'#235#27'/?'#219#237#12#244'E'#161#254#207#11 - +'R'#192#255#23#240#242'?'#254'!i'#1#19#177#247'u|'#223'|n'#228'/&'#215#218'{' - +'?i'#132's'#174#229#23'G'#4#236'}'#147#222';'#145'P'#31#171#253'J'#194'}'#178 - +'F'#169#239#222'y'#0''#233#254#156#209#193#231'W%'#1'd'#12'"Y'#136'.'#178'!' - +#1#152#5#151'0'#247#128#210'y'#2'm'#186#212'u'#146#184#241'p'#222'X{0^'#185 - +#149#145#236#215'@-'#155#4'H!'#6#9#196#157#13#201#7#144#136#128#1#191')$'#10 - +'+$ '#11#147#138'h'#6#156'N,'#145#130'\'#162#6'V'#27'x'#26#17'8'#221#137'rG;' - +#240#202'^'#196''''#220#177#147#184#149#249#225#135'+'#167'i'#140'{'#175#244 - +'6'#167'<'#23#0'WO'#0'~^'#150#248#202#22#239'8N>c'#239#187'v'#191#149#252#139 - +#18#240'Y'#19#152'N'#213'd'#251#167'j6'#220'b'#130'@'#179#15'+'#241'3=__''' - +#154'<'#190#210#234#223'!'#149'?'#205'm'#147'@]'#210#235'd'#248#245#165#178 - +#15#222#254'G'#146#215#255'H4'#129'}8'#252'666'#206'<'#248#237#253'9'#227#195 - +#146#128#18#159#128#146'2b"'#130#21'h'#2#146'%h'#178#5'9Y'#136'$r'#155#192 - +#211#160';'#23'''Y'#212#184';'#236#189'6Kk'#29'S'#0'd'#27#140#8#184#163#214 - +#26#19'APo'#21#17#1#167#138#208's'#251#10'<'#137#8#220#148'b'#153#194#220#134 - +#14#205#196'%'#165')'#204#202'k7'#132'h'#230'4'#176#135#14#132#20#143'n:<' - +#151'q'#152'*o'#156'v'#246#144#145#236#230''#249#146#216#189'~'#157#14#227 - +'I'#230#158'!'#2'k'#227'/Q'#247'S'#145#248#2'|'#181#208#149'|J'#236'w'#215 - +#222'7'#206'?E'#251#147#254'=5'#221#189#173#19''#210'%*?}'#209#229#198#224 - +#189#149#198'h'#199#203'MI'#143#14#243')'#1'?'#173#199#180#223#151#154'~4k' - +#132#196'GE'#31#182#183'I('#161#145'''{'#251#201'\=s'#14#191'e'#227'<'#16#128 - +'=OC'#2#251#251#251#17']'#228':]x'#174#29#136#162'h'#157#246'a'#6'\'#18'''' - +#225#134#212#14'th'#27'}'#4'b'#210#0#162'G'#195#206'K{U'#147' '#212#201'C' - +#138#171#11'#U'#235']S'#245#181#27#156'M'#200#14'@'#199#4#0#1'(''Dh'#136#192 - +#148#24'+'#153#181'X'#249#229#198'#'#182#21'Y%'#179'P'#231#15'-'''#3#207#209 - +#255#139#222#4#166#151#161'{UN'#136#4#170#143#176#19#182'+m'#229#5'Y'#149'Cx' - +'N'#165#158#27#198'3N='''#150'oA'#159#154'm'#0'>#b'#0#160'u'#17#143'Z'#20#13 - +';'#173#199'?IJ>'#0#132#0#147#209#158#154#236#252'D-&'#131'B'#213#135#169#128 - +#207'pU'#254#238#238'O'#234'^2'#245't'#229'@'#213#211'?'#147#22#222#168#234 - +#219'q'#193#15'O'#24#134#232#235#223'_,'#22#227#25#141#243#2'~'#140#243'B'#0 - +#246'\'#221'<'#1'8'#252#232#194#183#232'p'#143'.'#254#186'8'#7'A'#4#156',' - +#132'V'#227'$'#129';$u'#27#180'_#'#240'E'#131'i}'#237#193#164#247'j'#150#235 - +#249#7#180#147'0`'#21#222#244#16#132'Y'#208'X{'#133#139#138#188#208#137#6#200 - +#220#3#134#8'4q'#4#162#29#136'&`{'#13'8]'#137#131#138#163'P'#8#161'hF"'#19 - +#152#28'H76'#161#195#162','#185#240#15'T='#3'G'#200'1'#248#160'%'#203'y^yi' - +#25#240'z'#229'4'#218'TE|'#159'%'#189#153'l'#195#190#230#160#154#207#246'~' - +#154#149#218'r+'#150#250#153#6#173#0'_'#137'='#175#164#131#143'2'#4#144'h' - +#239':''u'#231']5'#239'?'#210#173#186'L'#26'p'#146#10#209'h'#149#31#149'|' - +#151'['#253#187'P'#249'9'#188#143#254'}'#160'!m'#239#219#24#191#1#191#210'e' - +#189#15'e'#198#222#199'R'#207'? '#1'4>K'#25'~G'#29#231#137#0#236'9'#155#180 - +'a'#218#142#26#141'F'#13#17#2#164#7#27#231#160'8'#5'/'#217#218#1#29'&'#132's' - +#176#6#147' Sa'#252'h'#220'~'#169'?onz@'#166#137#20#216'f!Z'#226#195',h'#192 - +', B'#176'R'#223#1#188'Bd '#22'B'#16'M@9'#25#132#214'I'#24'>A+8'#204'GP'#154 - +#242#188#186'v:'#26'['#13#193#161#130#165#190#130'CB'#143'O'#200#187#183#160 - +'.%'#224#23#26'H.'#234'})Q'#199'%'#0#215#147'/'#158'}'#163#226#219'p'#158'q' - +#240'Y'#208#167'b'#231';'#158'}l'#11#192#181#202#175#215':'#175#223'8'#2'%vO' - +#235#217#222'}5'#222#131#186'?'#211#199#229'3y'#157#167'"'#245#147#201#149 - +#230#254#237'f<'#27'z'#26#247'8'#15#146#250'%{'#223'&'#248#208#178'C'#210#254 - +'1'#9#153'G'#210#213'g'#155#128#191'K'#160#31#160#149#23#13#152#8#231#10#252 - +'K'#158#138's3,'#9#208#197#15'k4'#200#12'h"B'#16#4#193#170#227#23#128'F'#0#18 - +'X'#5'A B &A'#148#19#196''''#139#176#253'p'#178#242#202','#141'ZE'#235#240 - +#162'('#200#147'9'#10'k'#221'+'#170#182'rM'#5'h'#162#17#154'y'#8#3#199#28'pM' - ,#3#9#17#134'RK`'#10#140'l'#14'A Z'#129#201'$t'#218#146'U'#137#160'j*'#148#202 - +#146'+'#21#137#21'G'#162';'#242''''#146'A'#165#216'f'#217'c'#235'Jw'#222#168 - +#172'e;7'#137'<'#153#27#190'3'#137'<'#142#164'wc'#249#2'|'#29#203'/'#192#175 - +#137#192#9#241#217't^'#13'r'#237#3'H'#10''' -'#139#225#14#219#249#9#171#251 - +#186'I'#167#146'L>'#179#246#232#207'j}t'#189'1x'#228#177#180#207'3'#147#214 - +#171'$'#190'O'#203'\j'#249#135#226#233#135's'#15#210#30#249#252'['#178#191'O' - +#207#220#160#223#239'O'#187#221#238#156#8' =o'#224#175'>'#9#231'm'#148'J'#137 - +'I'#19#136'%u'#152#157#131't'#140#181#1#144#0#250#11#210'z'#141#253#2#202'k#' - +'}'#152#182'czDc rw'#218#186#180'=m_'#207'H'#164#235#226#31#237'$'#132'fP' - +#168#249#145#170#181'/'#169#6#17#129#31'7'#180#244'7j'#191'k'#14#24'@'#224 - +#164#17#187#166#129#201'!p'#29#133#198'<'#176#4#224'W'#18#139#180'G'#192#246 - +'.'#180'='#11#141'iP'#152#3'n'#194'Q'#217';'#239#29'XU"s'#202'Az'#217#181#144 - +#187#161'='#137#215#231'y'#197#214#151'B'#29#163#226'gy'#145#167'oSu'#139')' - +#183'\'#201'_H'#253#20'M5'#197'VO-'#9#184'a'#190#220#170#251#198'!H'#18'' - +#184#173#166'{wU:'#27#22'f'#129#11'~'#241')'#180#162#217'.I'#253';'#152#156 - +#211'C/`}n'#172#242'+'#221#187#207#228#244#143#140#167#159#182#183#197#219 - +#191'Ej'#254'6'#9#24'h'#2'}'#147#215#239#244#241'3'#192'?7'#224'w'#30#135's;' - +','#9#16#1#4#8#19#18#27#195'9'#216#154#205'f=h'#3#244''#244#26'D'#171'1h'#2 - +#240#11#160'r'#144#211#135#17'% '#192'D'#244#17'A'#146#249#241#163'I'#247#230 - +'`'#214'X'#247'l'#241'O'#1'^?0 '#142#184#166#0#206'B'#244#211'3'#166#0#167#13 - +'['#208#23#161'A'#27'!'#136#180#244#207#131#224'@wb'#235'#p*'#15#11'"P:'#164 - +'h'#211#139#165']'#153'*L'#4#151#24#150'^"'#175'r'#197#170#26#192'!'#14#190 - +#18#15#184#245#246#216#204#242#146#189'o5'#0'7W'#223#128#221'z'#244#141#202 - +'/'#246'~'#166#167#219'V'#226#228#179#128#183#29'{'#202#224#183#197'='#236 - +#253'_'#168#217'`K'#205#246#239#17#240#199#146#16#148'X'#147#1#251'J:'#246'D' - +'^2'#187#212#26#220'n'#215#166'}'#248#28'<'#143'U~V'#252#233#146#177#163'Oz' - +#247'Me'#178'Nk'#239'C'#237#151#153'{9'#190#15#149#159#20#206'1b'#252#245'z' - +#29#164'a'#234#249#237'U:O'#227#188#19#128#249#13'H'#24#194#154'#'#4'p'#14#18 - +#1'4E'#27'X5'#190#1'!'#1'6'#9'h'#191#7#191#0'm'#195'A'#8'm'#128#222#231#251 - +#163'$'#238'>'#28'uo.'#178#168#233#154#5#202'I+V'#226''''#136#219#27#170#222 - +#187#174#130'F'#235#160#212'7'#170#191'C'#4#134'L'#148'K'#2#2'z'#229'U'#157 - +#133#14')x'#166#10'Q9~'#130'J7cq$'#30#152#241#232#176#154#3'[m'#231#188#196 - +'m'#180'a6'#189'B+'#240#158#2'zW'#213#247'*'#222'|'#29#215#207#172'Df'#7'`R' - +#168#251#158#1'}'#226'H~'#163#13'd'#134#8'2'#142#223#207#6#143#24#248#217'bb' - +#237'~'#227#216#203'\u'#159#132#252'J}'#252'p'#179'9x'#224#209#142#199#165 - +#130#224#29#218'f%'#198':'#250'f'#210#187#207't'#238#133#167#223#130#31#19'v' - +#160#137#7#254#143#254'}'#163#209'h^'#241#244'+u'#14#193''#200#147'q.'#135 - +#141#16#160#156#248#214#173'['#225#214#214'V'#141#14#213#137#173#17'*'#236 - +#145#250#134'b'#162'u''u'#24#251#200'#hC'#27#160#237#26#251#6'</'#204'r'#143 - +#204#130#246#229#221'Y'#243'J'#154#135#145'1'#11#236':'#8#172#175#0'N'#191 - +#184#181#166#234'+D'#4#245'N'#17#1'p{'#10#28#0#190#16#131#235#24'tk'#11'l' - +#152'R'#230'3t'#29#134'Nv!k'#7'jIU'#162'\'#17#175#148'\T'#190#213#182#143'A' - +#245'B'#26#233'_r'#240')I'#218#145'}3}'#182#19#194#179#224#183#251#6#240'N"O' - +#213#203'/'#241'}'#227#236'c'#144#27#147'@r'#251#141'6'#144'%s'#246#232'O'#25 - +#248'3'#171#254'g'#198#163#239'F'#17'h'#187'I'#234#254#229'F'#255'^'#28#192 - +'3O'#146#158#5'>'#199#245'3'#201#231#183'R'#159#22#19#226'c'#149#31#160#199#2 - +#149'_'#8#1#29'|'#198#244#182')'#217#253' '#140#132#180#206#236'<'#132#249 - +#142#4#156#23'h'#176's'#240#219#223#254'6'#251#5#232#166'E'#4#234':'#28#132 - +'259'#155#4'XD'#27'0$'#208#165#183#146'>'#175#26#4#167#24'f'#1#129'='#160#199 - +'%'#220#157#183'6'#247'&'#173#203#137#10#226#3'D'#224#23#246'>4'#130#176#209 - +'!'#173'`'#147#8'a]'#249'a,m'#198#140#3#240'0'#167#224'!'#5'F'#146'3'#224#149 - +'|'#3#166#23'A1'#169#137'%'#2#153#224#196'LuV'#10#18#10'7'#148#156#132#14#3 - +'xF'#210'Wd'#153#145#246#7#28'{N'#252#222#149#252#182'B'#207#237#200'c'#192 - +'/'#132#160'\'#167#159#149#242#142#211#207#233#224#131#255'-'#166#251'j1'#220 - +#162'e'#155'I w'#205#128','#147#150'_'#236#220#195'~'#222#138'g'#187#27#141 - +#193#131'Z'#184#152#150#212'}'#199#214#151#182']'#166'c/l'#249'}i'#226#193 - +#158'~Z?Fl'#159#164#252'>='''#3'2/'#199#244','#205'{'#189#30#192'n'#237#253 - +#165#128'9'#237#19'8'#166#223'T2'#9'h N'#211'diO'#128#167#155'j'#137'@'#204#3 - +#236#19#9#168#14#1#174'A'#251'u:'#30#25#179#128'@'#21#236#205'Z'#27#187#227 - +#230#229#133#10#235#218'F'#247#203#211#141#17#1'('#223#183'ZA'#173#177#170'"' - +'"'#131#176#185#162#163#4#230#181#142'3'#208'H~'#255'0-'#192#152#8'&'#147#208 - +#248#7#248#23'V2'#12#221#28#130#220#137#12#8#27#152#196#220#131#209#130'Jz' - +#174#167#172'j/'#255'V'#226#245'+Iyc'#227#27'{'#223'4'#227#176'Z'#128#1#186 - +#172#165#192#166't'#220'M'#237'u'#215'('#203#157#13#31'3'#240'S'#132#242'2''' - ,']W'#166#220#206'e'#209#192'O'#243'vm'#182#189#217#24'>'#140'|H|'#28#23#191 - +'>'#171#251#220'%'#208#205#232#227#240#30#242#249'i'#189'/'#237#187#182#205 - +'"'#206'?'#28#199'k'#166'F'#229''''#205'2;'#207#246#254'a`y'#17'G'#201'$@' - +#168#176#213'j'#197#2'lT'#12'v'#197#25#184'J'#140#143#232#0#22#248#5'D'#27'P' - +'-!'#2#152#5'a'#206'd'#128#174#225#190#191'7k'#174#237#142'[W'#230'y'#216#176 - +#206':'#3'|'#207'H'#242#162#2#17#154'@'#212#222' '#173'`C'#5#141#142#227#252 - +#11#184#231#160#231'8'#2#139#168'@u'#10'3'#207#2#222'F'#5#2#209#4'\'#237#192 - +#151#144#161#149#252'U'#7'`%'#159#192#12#183#149'6'#239'+'#235#28#176#213'zF' - +#229'O'#171#234'~&fA'#230'T'#234#21#206'?~'#189#209#0'*a'#191#18#9','#230'j6' - +#218'f'#208''''#211'A'#161'!'#152'L@'#199'l0}'#0#16#210#235#198#147#199#27#4 - +'|x'#246'M'#190#241#19#212'}k'#235#211#210#151#238'='#12'~'#186#207#144#254 - +#216#222#15#130#128#19'{h'#153#145#244'_ '#196#247#162#168#252'K'#129#242#2 - +#15'k'#18' J'#128#178'b'#186#185'54'#25'!R@'#227'Q'#152#5'+ '#1#133'Y'#137'5' - +#9'@'#27#232#209#210#17#179#160#238'{~Lk'#16'A'#8'"'#240#9'}'#251#179#250#234 - +#206#164'ue'#150'!'#135#192#128#177#144#218#5#176#3'K'#10'a'#212'Tq'#7'Z'#193 - +#154#10'j'#141'2'#184#157#210'bo'#9'!'#184#9'C'#133#25#224#21'~'#2'''R`o'#173 - +'u'#11'H'#180#192'M$'#146'W'#185'~'#128#210#172#185#178'*'#194'}'#174#244#207 - +#156#233#180#225#240#203'd'#178#205#138#183#191'*'#245#179#188#18#1'H'#212'b' - +#178#175#230'P'#241#199#187#156#216#147';6'#189#251'~K '#244'}>1B'#183'6f' - +#224#7#200#15'6'#192#231#138#221'2'#240#149#6'?'#128#143'N'#189'#'#201#229 - +#223#23#240#195#185#135#25'y'#25#248#240#240'+M'#14#211'N'#167'3G'#3#143'7' - +#223'|'#243#133'R'#249#15#0#228#180'O'#224#132'~#'#155#4#208#6'h;Z]]'#141#160 - +#13#200#140'D0'#11'z'#162#17#172#233#244'aM'#4#180'&'#18'Pm'#186#235#220'l'#4 - +'&'#129'o'#136' '#131'F'#224'y'#195#164#209#219#153'4/O'#211'Z'#7#200#180#192 - +'tH'#160'4'#231#128#152#0#232'I'#16#212'z*jt'#201#168#232#210'~'#173#210'[`I' - +#231'!G'#213'/'#171#254'x'#173#18#159#128#241#234'{'#182#152#168#8#4'x'#149 - +#181'*7'#222'PE'#218#190#178#13'8'#157#146#221#204#13#241#21#234#190'w'#192 - +#23'P'#201#233'7'#145#0#2'v2'#27#170#20'v'#253#164'O'#219#131'"'#219#207'x' - +#239#197#137#231#153#254'~v;'#231'p^'#167'6'#217'^'#175#143#182'|'#143'sy' - +#213'2'#224';'#237#185#173#147'O'#233#9':'#160#214#239#25#240#3#248'$'#12#246 - +#200#222#135#3#16'z'#254#132'l'#253#217#139#230#232'{'#26'8>,'#227#128'6P' - +#171#213'bz^0G'#22#166'*G'#159#129#30#28#133#240#17#28#212#6'T'#211#152#5#4 - +#180#18#17'(.'#25#11#163#253'yc}0k'#172#205#210#168'Y'#0#180#172#198#27#245 - +'>'#247#202#196#0'B'#8#27'+*'#170'wT'#0'B'#128#19#209'!'#0#27#14#148#162#162 - +'jc'#210#146#9#224'4"'#177'w'#185#218#140'D'#194'~n'#233'n)'#12#232'j'#2#198 - +#9#232#168#251'n'#184'/'#207'+I?yA'#0'H'#206'I'#166'}'#150#244#201'tH'#255'J' - +#10''' '#155#20'i)4'#200'Q'#0'K&'#184#184'Y'#138#4#158#149#218'x'#167#25#209 - +#135'i'#204#231#154#160#24#248#0'<'#235#252'b'#231'/*'#234#254'H'#226#250#198 - +#214'G'#136'oW'#188#251'}'#186#215'C'#168#251#176#245'www'#23'd'#231'sl'#255 - +#19#159#248'D'#254#162#131#223'yB>4'#163#164#13#192'7'#176#178#178#18#17#25 - +#208'f'#204'}'#6'h'#1#216#225'(\1'#218#128#210#26#2#250#18'"'#164'h'#136#0'Y' - +#132'1a.'#160'g=dg!'#20'r'#210#11#166'i'#220#216#155'5'#215'G'#179#218'Z'#162 - +#130#200'S'#5#25#20#146#220#169#7#240#141#243#207#28#15#148#31'7Y;'#8#226#22 - +'}B'#131#9#130#29#141#202'5'#3#156#181#141#18#136#170#159#171#194'$'#200#171 - +#209#1'U'#16#130#155#244#227'6'#222'4'#7#149#201#4#212'$'#192' u'#18'}'#12 - +#232#177'dp'#188#211#146'/'#198'l'#199'c'#201#200'4/'#1#190#186'm'#181#137 - +#204#250#20'`'#195'7'#195'y'#191'S'#159#236't'#163#233#158#199'I'#5'b'#143 - +#232#188#131'Ll|l'#1#244#236#224's'#128#143#134#29'#'#153#153#135#195'{'#198 - +#222'7'#14'>'#216#249't'#175#199't'#159#167'('#224'[b'#235';W'#224#197#29#31 - +'6'#2#176#191#217'h'#3#155#155#155#254't:'#13'1S1'#252#3'0'#11#232#223'-'#178 - +#7#225#12'D6'#225#138'8'#7'W'#196'y'#168#157#132#158#215#164#199#177'A'#159 - +'V;@'#4#30#224#166'Q:'#152#215#187#253'Yc}'#148#196'+'#185#22#233'V'#133'7)' - +#191'E'#135#225#162#243#176'K'#8#185#152#0#164'x'#16#25#212#153#28#130#176 - +#206')'#201'>'#214'd>'#148#10#139'J'#191#244'`'#219#242'"G'#160#236#237#247 - +'Jf@^$'#7#25'3'#1#248'K'#209'H'#19'@'#159'p'#197']'#150#152#245#212#206#198 - +#163#242#195#0#15#252'f%'#147#1#239'1'#251#181'`1&'#21''#167'GK'#232#193'!' - +#160'L'#237'0'#147'B'#154'iu'#223#177#241#171#18#223#216#249#3#1'?'#146'w'#0 - +'~^'#144#194'K'#210#30#197';'#172#238'w'#187#221#217#214#214'V'#226'x'#248'_' - +'X['#255#176#241'a$'#128#210'o_f'#22#208#225#26#217#130#28'6$'#2#128'F'#208 - +'s'#23#209#6#208'k'#160'm'#136#128#128#140'(CL'#251#16#211'Rc'#204#157#238'|' - +#180'*O'#9#206#251#179#198#234'h^'#235'M'#211#168#157#230#162#25#24#199#158 - +'r'#10#131'J'#132'`'#156''#190'>ig'#187#152#199#16']'#144#27'*'#8'"q:'#134 - +'tX"'#17#158#164'"'#243#233#232#181'/Z'#134#150#190#137#216#218'R)'#151'9' - +#139's'#28#146#28#18#158'_'#207#225'7'#7#196#182#196#183#240#1#184#0'W'#165 - ,#130' 1#8T'#167#242#154#191#24#213#163#249#176'G*>Zn'#23#17#8#145#246'8'#3#2 - +'>x'#18#206'}''o'#223#134#244#164'K'#207#184#2'|'#187#208'=d'#224'#'#172#135 - +'L>"'#2#246#238'#'#149#151#8' '#251#176'I}w|'#152#9#192#140#3'f'#1'B'#244'p' - +#196#198'QH'#255'o'#9#216#187#244' '#245#184#150#128#136'@4'#2'v'#20'*'#204 - +'T'#164#188':4'#2')4'#10#173'V'#224'#'#29'E'#251#10#176#13#208#18#9#212#137 - +#12#186#147'E'#220#157'$q'#139#224#16#26'S'#161#232''''#232';'#205'D+'#246 - +#190#152#2#150','#148#155'*l~'#150#252'@.'#30':$$'#232#216#255#182'EW'#169'1' - +#191#201#250'S'#165'\'#0'['#221#167#138#148'`'#6'u'#166'T'#158'/'#7'<'#214 - +#181' '#25#215#131#249#176#25#207#251#237'h6'#242#149'8'#243'<61'#140'moA' - +#175't'#202'.'#24#133#213'|Y'#230#198#179'oT}'#19#211#151#5#158#254'>'#128'O' - +'D>'#140#162'h'#12#137'O'#199#167't'#23't<'#249'0'#170#251#203#198#5#1#20 - +#195's'#205#130';w'#238#4#198'?'#128'9'#7#232'!B'#205'@'#203#241#19#244'$' - +#140#200'k'#248#7#20'"'#6'9W'#26#214#161#209#18#216#17'm@BQ'#24#248#30#200' ' - +#160'c>'#180'^F'#165'/'#229'<'#244'wL$@'#132#208#153'.'#162#206'4'#141#219 - +#153#14#8#138#237'^T'#4#22'e'#194#170#168#10'T'#254'R'#240#219#191'U'#207'' - +#241#207'J'#246'_'#197#7'`Gn'#205#0#3'z'#183#145'g'#225'G(^g'#0#31#251#201 - +#164#22#206#135#173'h>h'#147#164#15#252'4)^c'#153#197#128'^Y'#21#255#160#180 - +'/'#169#250#144#248'p'#224')'#1#187#164#242#150#164#189#1'~'#171#213#154#141 - +'F#'#246#236#147#169#151'~X'#213#253'e'#227#130#0#202#227#128''#192#16#1'=H' - +#28':'#164's2'#17#173'['#244#0#162#216#168'+'#26#129#171#13#176#143#0#175 - +#229#228'#'#223#135'Ya'#200' 8@'#6#128#174#158'`'#132'a'#142'|<"'#132#246'x' - +#17#183#146'4'#168#207#179#176#190'H'#131'Z'#170#160#219'k'#251#221'8'#245 - +#140'K'#191'T'#13#184'$'#239#223#141#8#228#203'~t5'#20'h'#134'['#251#239#185 - +#210#221#253#191#6'n'#228#167#211#208#207'fQ'#144#204#234#225'bL'#18'~'#16'.' - +#3#188#233#18'"'#182'C'#174''#145#168#248'E'#214#30'-'#11'g'#218'-'#171#234 - +'+'#157#190#203#224#167'{'#224#18#192#144'#'#179'I'#194#210#254#2#248'O'#31 - +#23#4#176'|,%'#2'Z'#135#244'@'#177'F'#0'`'#211#186'I*f'#139#236'J'#214#10#132 - +#16#12#9'X"@'#19#18#218#6'y'#212'@'#6' '#2#218#14#133#12'|'#218#151#194#2#206 - +#177#169#16#130#178#128'N'#179' '#154'fQm'#158#16')'#164'!'#19'C'#146#250#181 - +'E'#22#214'r'#195#2'y'#17#247#207#221#219#235#29#248'y2'#242#165#155#252#170 - +'R'#11'0'#253'7'#240#178'E'#236#167#179#200'O'#166'Q'#152#206#226' '#153#214 - +'BZ'#252'd^'#10#31#28#4#188#212#222#235#20']Ro'#178#220#168#19#185'JM'''#30 - +'U'#150#246'('#207#157'J?>'#11'|'#168#251't'#157#135#0#191#0#30'j>&'#221#132 - +'I0E'#6#159#249#172'%'#192'_'#242'K?'#188#227#130#0#158'<'#150#18#1'|'#4#237 - +'6'#201#183#217#140';'#20#19#1#160#203#144#169'5h'#201#186'm'#246#141#198#0 - +#243'@i'#13#2#4#2'2`"'#144#133#181#2'!'#4#175'B'#8#162#229#235'F'#0'E*'#191 - +#201#243#247#2'"'#129'('#205#252' '#205#149#159#229'~'#0#19'"'#203#184#160')' - +#160#181#143#207'&'#158#241#233'8'#142#249'x'#15' '#233'{y'#202#139#162'Wy' - +#10#219#153'g'#142'y'#244#137'~'#158#6#4'N'#218'N#?K0C'#14''#181#237#249#167 - +#247#204'1}Z'#185#20#11'j'#213#222#145#240#12'B'#237#197#183'q{+'#237'Mi'#174 - +#210#192#159'HW'#158#145'*'#128#15#169#143#184'='#239#147#186#143#10#189'1' - +#173'!'#241#209#140'snl'#252'G'#143#30'e'#23#192''#250#184' '#128#163#13'K' - +#4#223#253#238'w=4%'#5#25#208#161#144#164'N'#212'l6'#1'd'#214#10#232#129'lxz' - +#174'B'#180'('#3#240#153#4'h'#223#144'C'#211#209#10'jB'#6#136#30'DJG'#16't' - +#20'A'#19#2#242#10#8#139#220#2'Dr'#130#217'y^'#164#249#225#143#239#219'm'#140 - +#220'sN'#250#224#207#240#14#252'#?'#176#225#202#240#138#242' '#249#0'z;/'#210 - +#133'9'#25#152#147'!3'#237'P'#212#21'x'#158#199#217#3'.'#224#233'X"'#221'vy' - +#150#29#167'@'#7#224#7#152'a'#223#143' '#229#165' '#135#215't|'#4#208'C'#210 - +#3#244#240#230'c!'#2'^'#192#171'O'#199#211#11#224'?'#219#184' '#128'g'#27'%"' - +'@'#212#224#210#165'K>I'#164#144#30'D&'#3#180''''#4#168#145'a'#8'2'#192#2'2P' - +#152#202#220'Y'#132#8#140'i'#192#230#1'k'#5#30';'#14']B`2'#160'c'#1'b'#8#162 - +#29'0'#17#152#22' '#24'y'#145#223#167'r'#183#11'`'#165#17#192#129'y'#9#151 - +#254'J'#227#225'We'#253'_)'#227#164#211'h'#215'L$'#130']'#25#231#157#150#242 - +#136#238#231'6Vo='#248#6#240'R'#142'k'#194'x'#0#255'd'#137#212#231'E'#18'vX' - +#189#23#21''#14#208'c'#166']'#196#241'WVV2x'#245#137#148#243#11#224'?'#219 - +#184' '#128#247'7l^-'#194#135#244#16'z'#198'<'#160#135#18#128'E'#143'B6'#17 - +#224'/'#160'}'#16'B'#131#164#22#8#129'5'#0#172'}'#157'Q'#216#144'F'#165'L'#4 - +'R'#177#200#239#177'D'#160#29#136#161#199'A}'#144#129'v '#210#182#246#31'(]' - +#151' '#219#170#152'}'#132#165's'#209'4'#200#156#244'!, '#160'Vy^v'#3'j'#183 - +#164'W'#184#253#10'U>7'#157't'#217#129''''#222'{'#165#165#188#5#190'S'#207 - +'^|'#165#165'=/p'#236#161#209#6#253'~'#168#243#19#172'%'#166'?!'#160'c'#31 - +#196'0C=>'#169#247' '#142#132#142'!y'#7#128#207#156'8'#190#156#253#5#232#159 - +'e\'#16#192#7#27#150#8#240#0#186'Z'#1'I'#168'`gg'#7'R;'#132#137#0#243#20'd'#0 - +'p'#139#218'_'#23#240'#'#215#192#128#159'5'#2'G30D`4'#3#215'g`'#9'Ai'#13#1'$' - ,#16'pI?'#231#28's'#242#156'''`'#247#12#17'('#19';8D'#19'p'#192#207#0'/'#8'AK' - +'~'#165'{'#230#27#213'>'#173#0#222#196#233#141#138'o'#156'y'#0#191#241#228'O' - +#5#220'v'#27#199#1'~'#243''#168#245'x}'#20'Es'#0#31#210#30#234'='#17'jz'#227 - +#198#141#20#160#135#180'w'#242#245#249'|O'#251'a8'#143#227#130#0#158#223'8' - +#160#21#12#6#3#175#211#233#4#180#248#244#0#179#153#0'I'#14'S'#129#30#234#152 - +#30'n'#164#31'C'#202#215#232'X'#141#142#25'S'#192#152#5#150#4#232#189#186'eY' - +#161#21#132'UBP'#154#20'x'#246'$!'#4#156#147'/'#224#247'DK'#176#219#149#243 - +#182#170#188'2'#210#221#181#225's''>/'#210']z'#234#25#208'/'#28#208'W'#213'|' - +'+'#245'E'#221#231#181#144#194#12'*=IwH'#249#5'@O@_'#200'g&'#0'=T|c'#219#11 - +#232#205'y'#186#235#139#241'>'#198#5#1#28#207'Xj"'#208#218#7#25'@3'#160#7#157 - +'M'#5','#4'nL'#142#233#206'"'#151#16'|'#157'?`4'#6#248#22'P'#189#24#11#17'D' - +#134#16'@*'#244#186'RD'#1'D@'#175'E'#196#194#19'2`"'#192#182#28#243#212#146 - +'x`'#201#142'/'#0#159#27#21'_'#21'j=7'#212#196'B'#223#195#192#167#207#157#211 - +#246#1#2#192'B'#191'k'#230#2#158#200#14#210#29#239'Y@'#194#131'H'#232'xj$' - +#189#1'=]'#175#188#162#226#187#235#139#241#1#199#5#1#28#239#176#215#215'8'#14 - +#141'f'#0'3aoo'#143#9'!@i'#218'`'#16#192'l'#160#151#134' '#3#172'A'#8' '#7':' - +#206'k'#128#158'^'#202'k9'#30#154#227'J'#252#4#202'1'#13#180'I'#192'f'#128'o' - +'H'#0#231#148'!.X'#144'@q'#178#5#208'sQ'#237'-'#240#177#166#247'X'#201#175 - +#196#161''''#4#176'0'#251#134#0'd!<'''#6#228#144#236#176#223#23'Fk'#0#224#137 - +#3#210#181#181#181#148'~?'#212#250#140'L'#168#28#160#191'p'#232#157#204#184 - +' '#128#147#27#165'D\'#233'Y'#168'@'#8#208#12'n'#222#188#233'-#'#4#12#2'M ' - +#160#14#5#212'au'#219#172#149'&'#14#214#0'0'#0'v'#1'<k'#0#206#218#19#141'@r' - +#141'<'#211'T'#131'%?'#182#245#219#249#205#25'}fN'#167'bl'#254'$/'#231#230 - +''''#6#212#244'r6'#11'2]'#166#155#212'j5k*'#0#240#147#201'$3'#128'o4'#26#185 - +#145#242#142'M'#143#211#185#0#253#9#141#11#2'8'#189'q$B'#216#216#216#128#198 - +#128'jE'#159#192#227#19#160'|'#146#164'L'#18'D'#16#216'f'#130#192#2#174#152 - +#207#231#188'&'#172#225#0#131#29'=3'#176#157'qk'#252#220#163#247'yf'#187'tB' - +#4'~'#240#3'Im'#128#157'5'#1'l'#227#24#246'IZ'#231'd'#186#0#199'<y&i*P'#217 - +#193#17')'#22#218#207#232#251#179'V'#171#5'5>k6'#155#217't:'#205#232#156#243 - +'e'#18'~'#9#224#171#219#23#227#152#199#5#1#156#141'Q'#186#15#198'\'#192#182 - +'1'#25#8'H'#30'H'#129'H'#192'#py'#134#24#8#148#30#200#129#128#232#25'r'#160 - +'}'#143'@j'#23':'#206#128#199'6>'#19#219#4'V'#187#141#181#207#253'L'#148'"'#0 - +#27#192#243'>}/o'#155#133#190'?'#7#208#137'Dr'#128#156#190'+'#235#247#251'9' - +#129#222#2#29#146#189#221'n'#231#6#236#248#156#11#192#159#205'qA'#0'gsT'#239 - +#139''''#192'Q.1`'#13'r'#184'u'#235#150'7'#28#14'-9'#224'8'#8#2'k'#179'O'#192 - +#230'5'#8#3#235#245#245'u'#187'm'#6#128#140'5Iy'#11'J'#2';o'#3#220'X?~'#252 - +'8'#199'6@'#142#253'e@'#199#250#16#176'/'#219#191#24#167'8.'#8#224'|'#141'e' - +#247#235#0'9`'#24#130'0'#3'D'#177#236#3#161'Y`]'#175#215#15#0#211#128#218#29 - +#6#224#24#0'9'#214#135#0#253#176'c'#23#227#12#141#11#2'x1'#198#179#220#199 - +#163#190#246#168#224#189#0#249'9'#30#23#4'p1.'#198#135'x'#252'p'#251'ut'#3 - +#215#244'"'#0#0#0#0'IEND'#174'B`'#130'('#0#0#0#128#0#0#0#0#1#0#0#1#0' '#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1#0#0#0#1#0#0#0#1#0#0#0#1 - +#0#0#0#1#0#0#0#1#0#0#0#1#0#0#0#1#0#0#0#1#0#0#0#1#0#0#0#1#0#0#0#1#0#0#0#1#0#0 - +#0#1#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1#0#0#0#1#0#0#0#2#128#128#128#2'UUU'#3 - +'@@@'#4'333'#5'III'#7'@@@'#8'999'#9'999'#9'MMM'#10'FFF'#11'FFF'#11'FFF'#11'M' - +'MM'#10'999'#9'@@@'#8'@@@'#8'UUU'#6'333'#5'UUU'#3#128#128#128#2#0#0#0#2#0#0#0 - +#1#0#0#0#1#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - ,#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1#0#0#0#1#128#128#128#2'UUU'#3'UUU' - +#6'@@@'#8'FFF'#11'III'#14'<<<'#17'III'#21'EEE'#26'DDD'#30'DDD"EEE%AAA''DDD)A' - +'AA+AAA+AAA+DDD)AAA''GGG$FFF!DDD'#30'==='#25'@@@'#20'@@@'#16';;;'#13'FFF'#11 - +'@@@'#8'333'#5'UUU'#3#128#128#128#2#0#0#0#1#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#1#0#0#0#2'UUU'#3'+++'#6'999'#9'NNN'#13'CCC'#19'BBB'#27'GGG$' - +'DDD-CCC5DDD<DDDDBBBMCCCTDDDZEEE]DDDaBBBdDDDfCCCgDDDfBBBdBBB`EEE]BBBYDDDSDDD' - +'KEEECAAA;AAA3AAA+DDD"GGG'#25'GGG'#18';;;'#13'@@@'#8'333'#5'UUU'#3#0#0#0#2#0 - +#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#1#0#0#0#2'UUU'#3'UUU'#6'@@@'#12'GGG'#18'BBB'#27'CCC&BBB2DDD@EEENCCC\DDDiDDD' - +'tCCC~DDD'#136'DDD'#143'CCC'#149'DCC'#153'DCC'#155'EED'#158'DDC'#160'EED'#162 - +'EED'#162'EDD'#161'DCC'#160'EDD'#157'CCC'#155'CCC'#152'CCC'#148'BBB'#142'CCC' - +#134'DDD|CCCrDDDfEEEYDDDKCCC=EEE0GGG$EEE'#26'<<<'#17'MMM'#10'UUU'#6'UUU'#3#0 - +#0#0#1#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'UUU'#3'+++'#6'FFF'#11'GGG'#18 - +'FFF'#29'FFF,DDD<AAANCCC_DDDpCCC'#129'DDD'#142'EED'#154'DDC'#163'DDC'#171'DD' - +'C'#178'DDC'#183'EDB'#188'EDB'#190'EDB'#192'FFE'#192'FFE'#192'EED'#193'EED' - +#194'EED'#194'EED'#193'EEB'#193'FEC'#192'FEC'#192'DDC'#190'DDC'#189'DDC'#187 - +'DDC'#182'EED'#177'EDD'#169'DDD'#162'CCC'#152'CCC'#140'CCC~DDDmCCC\DDDKBBB:D' - +'DD)BBB'#27'@@@'#16'UUU'#9'333'#5#128#128#128#2#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'UUU'#3'III'#7'DDD'#15 - +'EEE'#26'DDD)EEE;DDDODDDfBBB{CCC'#141'DDC'#156'DDC'#168'DDC'#178'EDB'#187'EE' - +'D'#192'FEC'#195'FFC'#198'HFD'#200'HFD'#201'GGC'#202'HFC'#203'IGD'#203'IGD' - +#203'JHC'#203'IHD'#204'IHD'#204'IHD'#204'IHC'#204'IGD'#204'IGD'#203'IGD'#203 - +'GEC'#203'GEC'#203'FFC'#202'GFD'#201'FEC'#199'FFE'#196'EDB'#194'DDC'#190'EED' - ,#185'EDD'#177'DDD'#166'DDD'#153'DDD'#139'DDDxDDDbDDDKFFF7CCC&CCC'#23';;;'#13 - +'UUU'#6'UUU'#3#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'UUU'#3'@@' - +'@'#8'@@@'#16'BBB'#31'BBB2BBBIBBBaCCCyDDD'#142'DDC'#160'DDC'#175'FEC'#186'FF' - +'E'#192'GFD'#197'HFD'#200'HFC'#202'IGD'#203'JID'#204'OF>'#211'SC6'#219'W@0' - +#227'[>+'#235']<('#238'^<'''#240'_<%'#243'`<#'#245'a;"'#247'a:!'#248'a;"'#246 - +'`<$'#245'_<%'#242'^='''#240'\=)'#237'Z=,'#233'V@1'#225'RD8'#217'NG?'#210'JH' - +'D'#204'IHC'#204'HHD'#204'GEC'#203'FEC'#202'EED'#198'EED'#195'DDC'#190'DDC' - +#183'DDD'#172'DDD'#157'DDD'#138'DDDtCCC\DDDDCCC.@@@'#28'DDD'#15'III'#7'UUU'#3 - +#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#1'UUU'#3'@@@'#8'KKK'#17'HHH CCC5CCCPDDDlCCC'#134'DDC'#156 - +'EED'#172'FEC'#185'GFD'#193'GFC'#198'HFC'#202'IGD'#203'PE;'#213'VA0'#227']<(' - +#239'a;#'#246'd9'#30#254'g:'#29#255'g:'#30#255'i<'#30#255'j='#31#255'j>'#30 - +#255'k>'#30#255'l?'#31#255'l@'#31#255'mA'#31#255'nA'#31#255'm@'#31#255'l@'#31 - +#255'l?'#31#255'k>'#30#255'j>'#30#255'j='#31#255'i<'#30#255'g:'#30#255'f:'#29 - +#255'd9'#31#252'`<#'#245'[=*'#236'UA3'#223'MF?'#210'IHC'#204'HFD'#204'FFC' - +#203'FFE'#200'DDC'#197'EED'#191'DCC'#182'DDD'#170'CCC'#152'CCC'#129'DDDfDDDK' - +'DDD1FFF'#29'@@@'#16'III'#7'UUU'#3#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#1'UUU'#3'III'#7'@@@'#16'@@@ FFF7DDDRCCCoCCC'#140'DDC' - +#163'EED'#180'EED'#191'FFC'#198'IGD'#201'ME?'#208'UA2'#224'\<('#238'b9 '#250 - +'f:'#29#255'h;'#30#255'j>'#30#255'm@'#31#255'oB '#255'qD '#255'sF '#255'uH!' - +#255'|L#'#255#128'O$'#255#133'Q%'#255#136'S&'#255#139'V'''#255#143'X('#255 - +#145'Y)'#255#142'W('#255#139'U'''#255#135'S&'#255#131'Q%'#255#128'O$'#255'yK' - +'"'#255'tG!'#255'rE '#255'pD '#255'nA '#255'l@'#31#255'j='#31#255'g;'#30#255 - +'e9'#29#255'a;!'#247'Z=+'#234'RB5'#221'JGC'#207'HFD'#204'FEC'#203'FFE'#200'E' - +'ED'#196'DDC'#189'DDD'#177'CCC'#159'DDD'#135'CCCjBBBMBBB2FFF'#29'III'#14'UUU' - +#6#128#128#128#2#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2'333'#5';;;'#13'BBB' - +#27'AAA3BBBQDDDqDDD'#142'EED'#165'EDB'#183'FEC'#194'HFD'#200'IFB'#205'SA3' - +#223'^;&'#242'e8'#29#255'g;'#30#255'j>'#31#255'mA'#31#255'pD!'#255'tG!'#255 - +'~M#'#255#141'W('#255#151']*'#255#160'c-'#255#169'i0'#255#178'n1'#255#181'q2' - +#255#182's3'#255#183't3'#255#184't3'#255#184'v3'#255#185'v4'#255#186'w3'#255 - +#185'v3'#255#184'u3'#255#184't3'#255#183't3'#255#182'r3'#255#181'q2'#255#175 - +'l1'#255#166'h/'#255#158'a,'#255#149'\*'#255#138'U('#255'zK"'#255'sF '#255'p' - +'C '#255'l@'#31#255'i='#31#255'g;'#30#255'c8'#31#252'[<*'#237'RC8'#218'IGD' - +#205'HFD'#204'EED'#201'DDC'#198'EED'#191'DDD'#179'CCC'#161'DDD'#136'CCCkEEEJ' - +'DDD-@@@'#24'FFF'#11'@@@'#4#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - ,#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'UUU'#3'999'#9'FFF'#22'CCC*DDDGDDD' - +'iCCC'#138'EED'#165'FEC'#184'FEC'#194'HHD'#200'QC9'#216'[<*'#237'c8'#31#253 - +'g;'#30#255'j>'#31#255'nA '#255'sF!'#255'|M#'#255#141'W('#255#158'a,'#255#173 - +'k1'#255#181'r3'#255#185'v4'#255#187'y4'#255#189'{4'#255#191'~5'#255#193#129 - +'5'#255#195#131'5'#255#196#132'6'#255#197#134'6'#255#198#134'6'#255#199#136 - +'6'#255#200#136'6'#255#200#137'6'#255#199#136'6'#255#198#135'6'#255#198#134 - +'6'#255#197#133'5'#255#196#132'6'#255#195#131'5'#255#193#128'5'#255#191'~4' - +#255#188'z4'#255#186'x4'#255#184'u3'#255#181'q2'#255#168'i/'#255#153'_+'#255 - +#137'T'''#255'yJ"'#255'rE '#255'm@'#31#255'i<'#31#255'f:'#30#255'b9!'#249'X>' - +'.'#232'MD>'#212'HFD'#204'EED'#202'EED'#198'EED'#192'CCC'#180'CCC'#160'DDD' - +#132'DDDbCCCACCC&CCC'#19'@@@'#8'UUU'#3#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'333'#5'III'#14'>>>!DDD<DDD^CCC'#129'EED'#158 - +'DDC'#180'FEC'#194'IGD'#200'RB7'#219'^9%'#244'e8'#29#255'i<'#31#255'mA '#255 - +'rE!'#255'|L#'#255#149'[+'#255#170'j1'#255#181'r3'#255#185'v4'#255#188'z4' - +#255#192'6'#255#196#132'6'#255#199#136'6'#255#202#138'7'#255#204#142'7'#255 - +#206#145'7'#255#209#147'8'#255#211#150'8'#255#212#152'9'#255#213#152'9'#255 - +#213#153'9'#255#214#154'9'#255#215#155':'#255#215#155'9'#255#215#155':'#255 - +#214#154'9'#255#213#153'9'#255#212#152'9'#255#211#151'9'#255#210#149'9'#255 - +#208#147'8'#255#206#143'8'#255#203#141'8'#255#201#138'7'#255#198#135'7'#255 - +#195#131'5'#255#191'~5'#255#188'y4'#255#184'u3'#255#180'q3'#255#165'f/'#255 - +#142'W('#255'xJ"'#255'pD!'#255'l?'#31#255'h;'#30#255'd7'#30#255'Z<)'#239'NC<' - +#214'HFD'#204'EED'#202'DDC'#198'EDD'#191'DDD'#176'DDD'#153'CCCzCCCXBBB6FFF' - +#29'@@@'#12'@@@'#4#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1#128#128#128#2 - +'@@@'#8'@@@'#20'FFF,AAANBBBtDCC'#149'DDC'#174'EED'#191'GEC'#199'R@5'#220'_9#' - +#245'e9'#30#255'i='#31#255'nB!'#255'wI#'#255#142'W)'#255#165'f/'#255#181'q3' - +#255#186'x4'#255#191'~5'#255#195#131'6'#255#199#136'7'#255#203#140'8'#255#207 - +#145'9'#255#211#150'9'#255#214#154':'#255#216#157':'#255#219#160';'#255#221 - +#162';'#255#223#164';'#255#225#167'<'#255#226#168'<'#255#226#169'='#255#227 - +#170'<'#255#228#171'<'#255#228#171'='#255#228#172'='#255#228#171'='#255#227 - +#170'<'#255#227#169'<'#255#226#169'='#255#226#168'<'#255#224#167';'#255#222 - +#164';'#255#220#162';'#255#218#159':'#255#216#157':'#255#213#154':'#255#210 - +#149'9'#255#206#144'8'#255#202#139'8'#255#198#134'7'#255#194#129'7'#255#189 - +'|6'#255#184'v4'#255#178'n2'#255#159'b.'#255#135'T'''#255'tF!'#255'mA '#255 - +'h;'#31#255'd8'#30#255'\;('#240'OC;'#216'GGD'#204'EDD'#201'DDC'#197'DDD'#187 - +'DDD'#170'DDD'#143'BBBlDDDGAAA''KKK'#17'UUU'#6#0#0#0#2#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'U' - +'UU'#3'FFF'#11'BBB'#27'FFF7EEE]DDD'#132'DDC'#164'FEE'#186'GGD'#197'OA8'#216 - +'_8#'#246'e9'#30#255'j= '#255'oB!'#255'zK$'#255#151'^,'#255#176'n4'#255#184 - +'v5'#255#189'|6'#255#194#131'7'#255#200#137'8'#255#205#144':'#255#210#149';' - +#255#214#154';'#255#217#158';'#255#221#163'<'#255#225#167'='#255#227#171'=' - +#255#229#173'='#255#231#175'>'#255#232#177'>'#255#234#179'?'#255#236#181'>' - +#255#236#182'?'#255#237#183'?'#255#237#183'?'#255#238#183'@'#255#238#184'?' - +#255#238#185'?'#255#238#184'?'#255#238#183'@'#255#237#182'?'#255#237#183'?' - +#255#236#182'?'#255#236#181'>'#255#234#179'>'#255#232#176'>'#255#230#175'>' - +#255#229#172'='#255#227#169'='#255#224#167'='#255#220#161'<'#255#217#157'<' - +#255#213#153';'#255#209#148':'#255#204#142'9'#255#198#135'8'#255#193#129'7' - +#255#188'{6'#255#183't5'#255#171'k2'#255#142'W)'#255'tH"'#255'nA '#255'h<'#31 - +#255'd7'#30#255'[:('#240'KD?'#211'GFD'#204'DDC'#201'EDD'#195'CCC'#182'CCC' - +#159'CCC}EEEUBBB2@@@'#24'999'#9'UUU'#3#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - ,#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'333'#5'III'#14'BBB#EEECCCCkEEE' - +#145'DDC'#174'FFE'#192'KE@'#206'Z;)'#238'c8'#30#255'i<'#31#255'oB!'#255'|M%' - +#255#153'_-'#255#178'o4'#255#186'x6'#255#192#128'7'#255#198#135'9'#255#204 - +#142':'#255#209#149';'#255#214#155'='#255#219#161'>'#255#223#167'>'#255#227 - +#170'>'#255#230#174'?'#255#232#177'@'#255#235#181'A'#255#237#183'A'#255#238 - +#185'A'#255#240#187'A'#255#241#188'A'#255#242#189'B'#255#243#190'A'#255#244 - +#191'B'#255#244#192'B'#255#244#191'B'#255#244#192'B'#255#244#192'B'#255#245 - +#193'A'#255#244#192'B'#255#244#192'B'#255#244#191'B'#255#244#192'B'#255#244 - +#191'B'#255#243#190'A'#255#242#189'B'#255#240#187'A'#255#239#186'A'#255#238 - +#184'@'#255#237#183'A'#255#235#179'@'#255#232#176'?'#255#229#173'?'#255#226 - +#169'?'#255#223#165'>'#255#218#160'='#255#213#153'<'#255#208#147';'#255#202 - +#140':'#255#196#133'9'#255#190'~7'#255#184'v6'#255#173'l3'#255#145'Y+'#255'u' - +'H$'#255'm@ '#255'g;'#31#255'b7'#31#253'U>/'#230'IEC'#206'FFE'#202'EED'#199 - +'CCC'#189'DDD'#170'DDD'#139'BBBdCCC=BBB'#31'@@@'#12'UUU'#3#0#0#0#1#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'UUU'#6'KKK'#17'DDD)EEENDDDxDC' - +'C'#157'DDC'#182'GGC'#197'T=1'#225'a7 '#252'g;'#31#255'l@ '#255'wI$'#255#154 - +'^.'#255#179'o6'#255#186'x7'#255#193#128'9'#255#200#137':'#255#207#146'<'#255 - +#212#153'>'#255#217#159'>'#255#222#166'?'#255#227#170'@'#255#231#175'A'#255 - +#234#180'B'#255#236#182'B'#255#238#185'B'#255#240#187'B'#255#242#190'D'#255 - +#243#191'C'#255#244#192'D'#255#245#193'C'#255#246#194'D'#255#247#194'D'#255 - +#247#196'E'#255#247#196'D'#255#247#196'D'#255#248#197'D'#255#248#197'D'#255 - +#248#197'D'#255#248#197'D'#255#248#197'D'#255#248#197'D'#255#248#196'D'#255 - +#247#196'D'#255#247#196'D'#255#247#195'E'#255#246#195'D'#255#245#193'D'#255 - +#245#193'C'#255#244#192'D'#255#243#191'C'#255#242#189'C'#255#240#186'C'#255 - +#238#184'C'#255#236#182'B'#255#233#179'B'#255#230#174'A'#255#226#169'@'#255 - +#221#164'?'#255#216#157'>'#255#211#151'='#255#205#143'<'#255#198#135':'#255 - +#191'8'#255#184'w7'#255#174'l4'#255#144'X+'#255'rE"'#255'j> '#255'e9'#30#255 - +'_8"'#247'O@8'#219'GFD'#204'DDC'#201'CCC'#194'CCC'#178'DDD'#151'DDDqDDDGGGG$' - +'III'#14'@@@'#4#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2'III'#7'@@@'#20 - +'CCC.DDDVCCC'#130'EED'#165'DDC'#189'LB='#207']7$'#244'd7'#31#255'j> '#255'qE' - +'"'#255#140'W+'#255#174'l5'#255#186'x8'#255#193#129':'#255#200#138'<'#255#207 - +#146'>'#255#213#155'?'#255#220#163'@'#255#225#169'A'#255#229#174'B'#255#233 - +#179'D'#255#236#182'D'#255#239#185'D'#255#241#189'E'#255#242#190'E'#255#244 - +#192'E'#255#245#194'E'#255#246#195'F'#255#247#195'F'#255#247#196'G'#255#247 - +#197'F'#255#248#197'F'#255#248#197'F'#255#249#198'F'#255#249#198'G'#255#249 - +#198'G'#255#249#198'G'#255#249#198'G'#255#249#198'G'#255#249#198'G'#255#249 - +#198'G'#255#249#198'G'#255#249#198'G'#255#249#198'G'#255#249#198'G'#255#249 - +#198'F'#255#248#197'F'#255#248#197'F'#255#247#196'F'#255#247#196'F'#255#247 - +#195'F'#255#246#194'F'#255#244#193'F'#255#243#191'E'#255#242#190'E'#255#240 - +#188'E'#255#238#185'D'#255#235#181'D'#255#232#178'C'#255#228#172'C'#255#223 - +#167'B'#255#218#161'@'#255#212#153'?'#255#205#144'='#255#198#135';'#255#191 - +'9'#255#183'u7'#255#167'g3'#255#130'P('#255'oB"'#255'h< '#255'c7'#30#255'X:' - +'*'#237'HFB'#207'EEE'#202'DCC'#197'DDD'#184'CCC'#160'CCCzEEENFFF(@@@'#16'333' - +#5#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2'III'#7'FFF'#22'BBB2CCC\DDD'#136'DDC'#171 - +'EED'#192'Q?5'#219'a6'#31#252'f:'#31#255'mA!'#255'N'''#255#164'f3'#255#182 - +'u8'#255#190':'#255#199#138'='#255#207#147'?'#255#213#155'A'#255#220#163'B' - +#255#225#170'D'#255#230#177'E'#255#234#181'E'#255#237#184'F'#255#240#188'G' - +#255#242#189'G'#255#243#192'G'#255#244#193'H'#255#245#194'H'#255#246#195'H' - ,#255#247#196'H'#255#247#197'H'#255#247#197'H'#255#248#198'H'#255#248#198'H' - +#255#248#197'H'#255#248#198'H'#255#248#198'I'#255#249#198'I'#255#249#198'I' - +#255#249#198'I'#255#249#198'I'#255#249#198'I'#255#249#198'I'#255#249#198'I' - +#255#249#198'I'#255#249#198'I'#255#249#198'I'#255#249#198'I'#255#248#198'I' - +#255#248#198'H'#255#248#198'H'#255#248#198'H'#255#248#198'H'#255#247#197'H' - +#255#247#196'I'#255#247#195'H'#255#246#195'H'#255#245#195'H'#255#244#193'H' - +#255#243#191'G'#255#241#189'G'#255#239#186'G'#255#236#183'F'#255#233#180'F' - +#255#229#175'D'#255#224#168'D'#255#218#161'B'#255#211#153'@'#255#205#144'?' - +#255#196#134'='#255#188'{:'#255#180'r8'#255#155'_/'#255'wJ$'#255'j? '#255'e8' - +#30#255']8#'#246'LB<'#213'FEE'#203'EED'#199'DDD'#188'DDD'#165'DDD'#128'DDDSF' - +'FF,GGG'#18'+++'#6#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2'III'#7'CCC'#23'BBB6DDDaCCC'#141'DDC'#174 - +'GDB'#196'V:+'#233'c7'#30#255'h; '#255'pC#'#255#146'Z-'#255#179'p7'#255#187 - +'{;'#255#196#133'='#255#204#144'?'#255#212#154'B'#255#219#163'E'#255#225#170 - +'E'#255#230#176'G'#255#234#181'H'#255#238#185'I'#255#240#189'I'#255#242#190 - +'J'#255#243#192'J'#255#244#193'J'#255#245#195'J'#255#246#195'J'#255#246#196 - +'J'#255#247#196'K'#255#247#196'K'#255#247#197'K'#255#247#197'K'#255#247#197 - +'K'#255#247#197'J'#255#247#197'J'#255#247#198'J'#255#247#198'J'#255#247#198 - +'J'#255#247#198'J'#255#247#198'J'#255#247#198'J'#255#247#198'J'#255#247#198 - +'J'#255#247#198'J'#255#247#198'J'#255#247#198'J'#255#247#198'J'#255#247#198 - +'J'#255#247#198'J'#255#247#197'J'#255#247#197'J'#255#247#197'J'#255#247#197 - +'K'#255#247#197'K'#255#247#197'K'#255#247#196'K'#255#246#196'K'#255#246#196 - +'J'#255#246#195'J'#255#245#195'J'#255#244#194'J'#255#243#191'I'#255#241#189 - +'I'#255#239#188'I'#255#236#184'H'#255#233#180'H'#255#228#174'G'#255#223#168 - +'E'#255#217#160'C'#255#210#151'B'#255#202#141'?'#255#193#130'<'#255#185'x:' - +#255#174'm6'#255#134'S*'#255'mA"'#255'f:'#31#255'a6'#30#253'R>4'#224'FFE'#203 - +'EED'#200'CCC'#190'DDD'#169'CCC'#133'CCCXAAA/CCC'#19'+++'#6#0#0#0#1#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'III'#7'CCC'#23'BB' - +'B6CCCcCCC'#144'EED'#177'JC>'#202'[6"'#244'c8'#31#255'j?!'#255'xI&'#255#162 - +'d2'#255#183'u:'#255#192#129'='#255#200#140'@'#255#209#150'B'#255#217#160'E' - +#255#223#169'G'#255#229#176'H'#255#233#181'J'#255#236#185'J'#255#239#188'K' - +#255#241#190'K'#255#242#192'K'#255#243#193'L'#255#244#194'L'#255#245#195'L' - +#255#245#195'L'#255#245#195'L'#255#246#196'L'#255#246#196'L'#255#246#196'L' - +#255#246#196'L'#255#246#196'M'#255#246#196'M'#255#246#197'M'#255#246#197'M' - +#255#246#197'M'#255#246#197'M'#255#246#197'M'#255#246#197'M'#255#246#197'M' - +#255#246#197'M'#255#246#197'M'#255#246#197'M'#255#246#197'M'#255#246#197'M' - +#255#246#197'M'#255#246#197'M'#255#246#197'M'#255#246#197'M'#255#246#197'M' - +#255#246#197'M'#255#246#196'M'#255#246#196'M'#255#246#196'M'#255#246#196'L' - +#255#246#196'L'#255#246#196'L'#255#246#196'L'#255#245#195'L'#255#245#196'L' - +#255#244#195'M'#255#244#194'L'#255#243#193'L'#255#242#192'L'#255#241#190'K' - +#255#238#188'K'#255#236#184'J'#255#232#179'I'#255#228#173'H'#255#222#166'F' - +#255#215#158'E'#255#207#148'B'#255#198#137'?'#255#190'~<'#255#180'r9'#255#150 - +']/'#255'qE$'#255'h< '#255'b6'#30#255'W9*'#235'GED'#204'DDC'#201'DDD'#192'CC' - +'C'#172'CCC'#137'DDDZAAA/GGG'#18'333'#5#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#1'+++'#6'III'#21'CCC5DDDbCCC'#145'DDC'#178'LB;'#206'^6 ' - +#249'd8'#31#255'l@!'#255#131'Q*'#255#172'k7'#255#186'z;'#255#196#133'?'#255 - +#205#146'C'#255#213#156'E'#255#220#165'G'#255#227#173'I'#255#232#180'K'#255 - +#236#184'L'#255#238#187'L'#255#240#189'N'#255#241#191'N'#255#242#192'N'#255 - +#243#193'N'#255#244#194'N'#255#244#194'N'#255#244#194'O'#255#244#195'O'#255 - +#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#195'O'#255 - +#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#195'O'#255 - +#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#195'O'#255 - +#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#195'O'#255 - +#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#195'O'#255 - +#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#195'O'#255 - ,#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#195'O'#255#244#194'O'#255 - +#244#195'N'#255#244#194'N'#255#243#193'N'#255#242#192'M'#255#241#190'N'#255 - +#240#189'M'#255#237#186'M'#255#235#182'L'#255#230#178'K'#255#225#171'I'#255 - +#219#163'G'#255#211#153'D'#255#202#143'B'#255#192#130'>'#255#183'u:'#255#164 - +'f4'#255'yK&'#255'i=!'#255'c7'#30#255'Z8%'#241'GEC'#205'DDC'#201'CCC'#193'DD' - +'D'#173'DDD'#136'BBBYDDD-KKK'#17'333'#5#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#1'333'#5'CCC'#19'BBB2CCC_DDD'#143'DDC'#178'M@8'#211'_5'#31#251'e9'#31 - +#255'lA"'#255#144'Y/'#255#179'r9'#255#188'}='#255#198#138'B'#255#208#149'D' - +#255#217#161'H'#255#223#170'K'#255#229#177'L'#255#234#181'M'#255#236#186'N' - +#255#239#188'O'#255#240#190'P'#255#241#191'P'#255#242#192'P'#255#242#192'P' - +#255#242#193'P'#255#243#193'P'#255#243#194'P'#255#243#194'P'#255#243#194'P' - +#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P' - +#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P' - +#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P' - +#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P' - +#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P' - +#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P' - +#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P'#255#243#194'P' - +#255#243#194'P'#255#243#193'P'#255#242#193'P'#255#242#192'O'#255#242#192'P' - +#255#241#192'P'#255#240#190'P'#255#238#188'O'#255#236#185'N'#255#232#180'M' - +#255#228#175'L'#255#222#167'I'#255#215#159'H'#255#205#147'D'#255#196#134'@' - +#255#186'y<'#255#173'm7'#255#130'O*'#255'j?!'#255'c8'#31#255'[7#'#245'HDA' - +#208'DDC'#201'CCC'#193'CCC'#172'CCC'#134'AAAVAAA+@@@'#16'@@@'#4#0#0#0#1#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#1'@@@'#4'<<<'#17'CCC.CCC[CCC'#140'EED'#176'O>5'#213'_5'#30#253 - +'e9'#31#255'nB#'#255#151'\1'#255#181's:'#255#191#129'@'#255#201#142'C'#255 - +#211#154'G'#255#219#164'K'#255#226#173'M'#255#231#179'O'#255#234#184'P'#255 - +#237#187'Q'#255#239#189'Q'#255#240#190'R'#255#240#191'Q'#255#241#192'R'#255 - +#241#192'R'#255#241#192'R'#255#242#192'R'#255#242#192'R'#255#242#193'R'#255 - +#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255 - +#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255 - +#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255 - +#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255 - +#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255 - +#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255 - +#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#193'R'#255 - +#242#193'R'#255#242#193'R'#255#242#193'R'#255#242#192'R'#255#242#192'R'#255 - +#241#192'R'#255#241#192'R'#255#241#192'R'#255#240#191'Q'#255#240#190'R'#255 - +#238#189'Q'#255#236#186'P'#255#234#182'O'#255#230#177'N'#255#224#171'L'#255 - +#217#162'J'#255#208#150'F'#255#198#138'C'#255#188'}>'#255#177'o:'#255#136'S,' - +#255'k?"'#255'c8'#31#255'\6!'#248'JC?'#211'DCC'#201'CCC'#193'CCC'#171'BBB' - +#131'DDDRAAA''777'#14'UUU'#3#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'UUU'#3';;;'#13'AAA''CCCTBBB'#135'DD' - +'C'#174'N=5'#213'`4'#29#254'e9'#31#255'oC$'#255#156'`3'#255#181'u<'#255#193 - +#131'A'#255#204#146'E'#255#213#157'I'#255#221#167'M'#255#227#175'O'#255#232 - +#180'Q'#255#235#184'R'#255#237#187'S'#255#238#189'S'#255#239#190'S'#255#240 - +#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240 - +#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240 - +#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240 - +#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240 - +#191'T'#255#240#191'T'#255#240#191'T'#255#234#187'R'#255#202#161'G'#255#232 - +#185'R'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240 - +#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240 - +#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240 - +#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240 - +#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240#191'T'#255#240 - +#191'T'#255#240#191'T'#255#240#191'T'#255#239#189'S'#255#238#188'R'#255#236 - ,#186'R'#255#234#184'Q'#255#231#179'Q'#255#226#173'O'#255#219#165'L'#255#211 - +#154'H'#255#201#142'E'#255#190#128'@'#255#179'q:'#255#140'V.'#255'l@"'#255'c' - +'8'#31#255'\4'#31#250'JC>'#211'CCC'#201'DDD'#192'CCC'#168'CCC~DDDKFFF!MMM'#10 - +#128#128#128#2#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#128#128#128#2'333'#10'@@@ EEEJCCC~EDD'#169'L?8'#208'^4'#30#253'd8' - +' '#255'pD%'#255#159'b4'#255#182'v='#255#194#132'C'#255#205#147'H'#255#215 - +#160'L'#255#222#169'O'#255#228#176'Q'#255#232#181'S'#255#235#185'T'#255#236 - +#187'T'#255#237#188'U'#255#238#189'U'#255#238#190'U'#255#238#190'U'#255#238 - +#191'U'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239 - +#190'U'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239 - +#190'U'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239 - +#190'U'#255#239#190'U'#255#181#144'@'#255';/'#21#255#21#16#7#255#9#7#3#255#2 - +#2#1#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1#255#7#6#3#255#17#14#6#255#31#25 - +#11#255'2'''#18#255'SB'#29#255#157'}8'#255#233#186'S'#255#239#190'U'#255#239 - +#190'U'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239 - +#190'U'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239 - +#190'U'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239 - +#190'U'#255#238#190'U'#255#238#190'U'#255#238#190'U'#255#238#189'U'#255#237 - +#188'U'#255#236#186'T'#255#234#184'T'#255#231#180'S'#255#226#175'Q'#255#220 - +#166'N'#255#212#156'K'#255#202#143'F'#255#190#129'A'#255#179'r<'#255#145'Y0' - +#255'k?"'#255'c7'#31#255'[4 '#248'IC@'#209'DDD'#200'CCC'#190'CCC'#163'DDDtCC' - +'CAEEE'#26'III'#7#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#1'III'#7'==='#25'AAA?CCCsBBB'#162'K?;'#202'^4'#30#252'c8 '#255'oB' - +'%'#255#160'b5'#255#183'v>'#255#194#134'D'#255#205#148'J'#255#215#161'M'#255 - +#223#171'Q'#255#228#177'T'#255#232#182'U'#255#234#185'V'#255#236#187'V'#255 - +#236#187'V'#255#237#188'W'#255#237#189'W'#255#237#188'W'#255#237#188'W'#255 - +#237#188'W'#255#237#188'W'#255#237#188'W'#255#237#188'W'#255#237#188'W'#255 - +#237#188'W'#255#237#188'W'#255#237#188'W'#255#237#188'W'#255#237#188'W'#255 - +#237#188'W'#255#237#188'W'#255#237#188'W'#255#237#188'W'#255#237#188'W'#255 - +#159'~:'#255#16#13#6#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#1#0#0#255#16#13#6#255'9-'#21#255'y`,'#255#208#165'M' - +#255#237#188'W'#255#237#188'W'#255#237#188'W'#255#237#188'W'#255#237#188'W' - +#255#237#188'W'#255#237#188'W'#255#237#188'W'#255#237#188'W'#255#237#188'W' - +#255#237#188'W'#255#237#188'W'#255#237#188'W'#255#237#188'W'#255#237#188'W' - +#255#237#188'W'#255#237#189'W'#255#237#188'W'#255#236#187'V'#255#235#186'V' - +#255#234#184'U'#255#231#180'U'#255#227#175'R'#255#221#169'Q'#255#213#157'L' - +#255#202#144'H'#255#191#129'C'#255#180's='#255#145'X0'#255'j>#'#255'b6'#31 - +#255'Z5#'#245'GDB'#206'DDD'#200'CCC'#186'DDD'#154'EEEhBBB6@@@'#20'333'#5#0#0 - +#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'@@@'#4'GGG'#18'AAA3DDDf' - +'CCC'#152'IA>'#195'\4 '#250'c7'#31#255'm@#'#255#156'_5'#255#182'v?'#255#194 - +#134'E'#255#206#148'K'#255#215#161'O'#255#223#171'S'#255#228#178'V'#255#232 - +#182'V'#255#233#185'X'#255#234#186'X'#255#235#187'X'#255#236#187'X'#255#236 - +#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255#236 - +#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255#236 - +#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255#236 - +#188'X'#255#236#188'X'#255#236#188'X'#255'9-'#21#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#6#5#2#255'$'#29#14#255'ZH"'#255 - +#169#135'@'#255#234#186'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255 - +#236#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255 - +#236#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255#236#187'X'#255 - +#235#187'Y'#255#235#186'X'#255#234#186'X'#255#233#184'W'#255#231#180'W'#255 - +#227#176'T'#255#221#169'R'#255#213#158'N'#255#203#145'J'#255#192#130'D'#255 - +#179's='#255#139'U/'#255'i="'#255'a5'#30#255'X7%'#242'FDC'#204'CCC'#198'DDD' - +#180'CCC'#144'CCC[AAA+III'#14'UUU'#3#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - ,#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#128#128#128#2'@@@'#12'AAA''CCCWCCC'#140'GB?'#185'Z4!'#247'a6'#30#255'j?"' - +#255#151']2'#255#181't>'#255#194#133'F'#255#206#149'M'#255#215#161'Q'#255#223 - +#170'T'#255#228#178'W'#255#231#181'Y'#255#232#183'Y'#255#233#185'Z'#255#234 - +#185'Y'#255#234#186'Y'#255#234#186'Y'#255#234#186'Z'#255#234#186'Z'#255#234 - +#186'Z'#255#234#186'Z'#255#234#186'Z'#255#234#186'Z'#255#234#186'Z'#255#234 - +#186'Z'#255#234#186'Z'#255#234#186'Z'#255#234#186'Z'#255#234#186'Z'#255#234 - +#186'Z'#255#234#186'Z'#255#234#186'Z'#255#234#186'Z'#255#234#186'Z'#255'C5' - +#26#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1#255#20#16#8#255'K;'#29 - +#255#189#150'I'#255#234#186'Z'#255#234#186'Z'#255#234#186'Z'#255#234#186'Z' - +#255#234#186'Z'#255#234#186'Z'#255#234#186'Z'#255#234#186'Z'#255#234#186'Z' - +#255#234#186'Z'#255#234#186'Y'#255#234#186'Y'#255#234#185'Y'#255#233#185'Z' - +#255#232#183'Y'#255#230#181'X'#255#226#176'W'#255#221#169'T'#255#213#159'O' - +#255#203#145'K'#255#190#129'D'#255#178'p='#255#134'Q-'#255'g;"'#255'`4'#29 - +#255'U7('#238'CCC'#202'DDD'#195'DDD'#173'BBB'#131'CCCL@@@ 999'#9#0#0#0#2#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#1'III'#7'BBB'#27'CCCEDDD|DDD'#170'V6%'#238'`5'#30 - +#255'h<"'#255#145'Y1'#255#179's?'#255#192#132'F'#255#205#148'M'#255#215#161 - +'R'#255#222#171'U'#255#227#176'X'#255#230#181'Z'#255#231#183'Z'#255#232#183 - +'['#255#232#184'['#255#233#185'['#255#233#185'['#255#233#185'['#255#233#185 - +'['#255#233#185'['#255#233#185'['#255#233#185'['#255#233#185'['#255#233#185 - +'['#255#233#185'['#255#233#185'['#255#233#185'['#255#233#185'['#255#233#185 - +'['#255#233#185'['#255#233#185'['#255#233#185'['#255#233#185'['#255#233#185 - +'['#255'd1'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#13#11#5#255'N>'#31#255#193#153'L'#255#233 - +#185'['#255#233#185'['#255#233#185'['#255#233#185'['#255#233#185'['#255#233 - +#185'['#255#233#185'['#255#233#185'['#255#233#185'['#255#233#184'['#255#232 - +#184'['#255#232#183'Z'#255#231#182'Z'#255#229#180'Y'#255#226#175'X'#255#220 - +#168'U'#255#213#158'R'#255#201#144'K'#255#189'D'#255#175'n<'#255#128'N+'#255 - +'e9 '#255'_3'#29#255'Q;1'#227'CCC'#201'CCC'#191'CCC'#163'CCCrAAA;FFF'#22'333' - +#5#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#1'@@@'#4'<<<'#17'AAA3EEEhCCC'#156'R:/'#220'_3'#29 - +#255'e: '#255#134'Q-'#255#178'p>'#255#190#130'F'#255#203#145'M'#255#213#161 - +'S'#255#221#170'W'#255#226#176'Z'#255#229#180'Z'#255#230#182'\'#255#231#182 - +'\'#255#231#183'\'#255#231#183'\'#255#231#183'\'#255#231#183'\'#255#231#183 - +'\'#255#231#183'\'#255#231#183'\'#255#231#183'\'#255#231#183'\'#255#231#183 - +'\'#255#231#183'\'#255#231#183'\'#255#231#183'\'#255#231#183'\'#255#231#183 - +'\'#255#231#183'\'#255#231#183'\'#255#231#183'\'#255#231#183'\'#255#231#183 - +'\'#255#180#142'G'#255#1#1#1#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#15#12#6#255'VE"'#255#215#171'V'#255#231#183'\'#255#231#183'\'#255#231 - +#183'\'#255#231#183'\'#255#231#183'\'#255#231#183'\'#255#231#183'\'#255#231 - +#183'\'#255#231#183'\'#255#231#182'\'#255#230#181'['#255#228#178'['#255#225 - +#175'Y'#255#219#168'W'#255#211#156'Q'#255#200#142'K'#255#187'|D'#255#172'k<' - +#255'wF('#255'c7 '#255'^1'#30#254'K@:'#214'DDD'#199'CCC'#184'CCC'#148'AAA^GG' - +'G+NNN'#13'UUU'#3#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2'MMM'#10'@@@$DDDSDDD'#139'L?8'#197'^1'#29#254 - +'c7 '#255'wG)'#255#173'l<'#255#188'E'#255#201#144'N'#255#212#158'T'#255#220 - +#169'X'#255#225#175'['#255#228#179'\'#255#229#180'\'#255#230#181']'#255#230 - +#181']'#255#230#182']'#255#230#182']'#255#230#182']'#255#230#182']'#255#230 - +#182']'#255#230#182']'#255#230#182']'#255#230#182']'#255#230#182']'#255#230 - +#182']'#255#230#182']'#255#230#182']'#255#230#182']'#255#230#182']'#255#230 - ,#182']'#255#230#182']'#255#230#182']'#255#230#182']'#255#230#182']'#255#221 - +#175'Y'#255#10#8#4#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#1#1#1#255'4)'#21#255#184#146'J'#255#230#182']'#255 - +#230#182']'#255#230#182']'#255#230#182']'#255#230#182']'#255#230#182']'#255 - +#230#182']'#255#230#181']'#255#229#182']'#255#228#180']'#255#227#178'\'#255 - +#224#174'['#255#218#166'W'#255#209#155'R'#255#198#139'K'#255#184'zC'#255#162 - +'d8'#255'l@$'#255'a5'#30#255'Z3 '#249'FBA'#206'DDD'#196'CCC'#174'CCC'#129'BB' - +'BIFFF'#29'@@@'#8#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#1'333'#5'FFF'#22'BBB>CCCvFA?'#174'Z3'#31#248'`5'#30 - +#255'l@$'#255#163'd9'#255#184'{D'#255#199#142'N'#255#210#156'T'#255#218#167 - +'Y'#255#223#174'\'#255#226#177'^'#255#228#179'^'#255#228#179'^'#255#228#180 - +'_'#255#228#180'_'#255#228#180'_'#255#228#180'_'#255#228#180'_'#255#228#180 - +'_'#255#228#180'_'#255#228#180'_'#255#228#180'_'#255#228#180'_'#255#228#180 - +'_'#255#228#180'_'#255#228#180'_'#255#228#180'_'#255#228#180'_'#255#228#180 - +'_'#255#228#180'_'#255#228#180'_'#255#228#180'_'#255#228#180'_'#255#228#180 - +'_'#255'bM)'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#27#21#11#255#140 - +'o;'#255#228#180'_'#255#228#180'_'#255#228#180'_'#255#228#180'_'#255#228#180 - +'_'#255#228#180'_'#255#228#180'_'#255#228#179'^'#255#227#179'^'#255#226#176 - +']'#255#222#172'['#255#216#164'X'#255#207#152'R'#255#195#137'K'#255#181'tA' - +#255#149'Z3'#255'g<"'#255'_3'#29#255'V6'''#239'CCC'#201'CCC'#190'CCC'#159'CC' - +'CkCCC5GGG'#18'@@@'#4#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#2'@@@'#12'CCC*DDD^DDD'#150'U6('#230'^2'#28#255'f:!'#255 - +#149'Z3'#255#181'uB'#255#195#137'K'#255#208#154'T'#255#216#165'Y'#255#221#172 - +']'#255#225#176'^'#255#226#178'_'#255#227#178'_'#255#227#178'_'#255#227#179 - +'_'#255#227#179'_'#255#227#179'_'#255#227#179'_'#255#227#179'_'#255#227#179 - +'_'#255#227#179'_'#255#227#179'_'#255#227#179'_'#255#227#179'_'#255#227#179 - +'_'#255#227#179'_'#255#227#179'_'#255#227#179'_'#255#227#179'_'#255#227#179 - +'_'#255#227#179'_'#255#227#179'_'#255#227#179'_'#255#227#179'_'#255#227#179 - +'_'#255#13#10#6#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255'&'#30#16#255#208#164'W'#255#227#179'_'#255#227#179'_'#255#227#179'_' - +#255#227#179'_'#255#227#178'_'#255#227#178'_'#255#226#179'`'#255#226#177'_' - +#255#224#176'^'#255#220#170'\'#255#214#162'X'#255#205#149'R'#255#191#131'I' - +#255#177'p?'#255#131'O.'#255'c7 '#255']1'#29#255'N;3'#222'DDD'#199'DDD'#181 - +'CCC'#140'DDDSDDD"999'#9#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#1'+++'#6'@@@'#24'EEECCCC~K=7'#193']0'#28#255'b6'#31#255#129'M' - +','#255#177'o@'#255#191#131'J'#255#204#149'S'#255#214#163'Z'#255#220#171']' - +#255#223#174'_'#255#225#176'`'#255#225#177'`'#255#226#177'`'#255#226#177'`' - +#255#226#177'`'#255#226#177'`'#255#226#177'`'#255#226#177'`'#255#226#177'`' - +#255#226#177'`'#255#226#177'`'#255#226#177'`'#255#226#177'`'#255#226#177'`' - +#255#226#177'`'#255#226#177'`'#255#226#177'`'#255#226#177'`'#255#226#177'`' - +#255#226#177'`'#255#226#177'`'#255#226#177'`'#255#226#177'`'#255#226#177'`' - +#255#161'~D'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - ,#255#0#0#0#255#0#0#0#255#4#3#2#255#134'i9'#255#226#177'`'#255#226#177'`'#255 - +#226#177'`'#255#226#177'`'#255#226#177'`'#255#225#178'`'#255#225#177'`'#255 - +#224#176'_'#255#222#174'^'#255#219#169']'#255#212#159'X'#255#200#144'Q'#255 - +#187'~G'#255#170'j<'#255'qB%'#255'`5'#30#255'Z2'#31#250'FBA'#205'CCC'#193'DD' - +'D'#166'CCCsCCC9CCC'#19'@@@'#4#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#2'@@@'#12'AAA+BBBaDBB'#155'Y3!'#244'_3'#29#255'l?$'#255#167'f;' - +#255#186'}H'#255#200#144'R'#255#211#158'Y'#255#218#168']'#255#221#172'`'#255 - +#223#174'a'#255#223#176'a'#255#224#176'b'#255#224#176'b'#255#224#176'b'#255 - +#224#176'b'#255#224#176'b'#255#224#176'b'#255#224#176'b'#255#224#176'b'#255 - +#224#176'b'#255#224#176'b'#255#224#176'b'#255#224#176'b'#255#224#176'b'#255 - +#224#176'b'#255#224#176'b'#255#224#176'b'#255#224#176'b'#255#224#176'b'#255 - +#224#176'b'#255#224#176'b'#255#224#176'b'#255#224#176'b'#255#224#176'b'#255 - +'hR.'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'qY2'#255#224#176'b'#255#224#176'b'#255 - +#224#176'b'#255#224#176'b'#255#224#176'b'#255#224#176'b'#255#223#176'a'#255 - +#223#174'`'#255#221#172'_'#255#217#166']'#255#208#155'W'#255#196#139'O'#255 - +#182'wE'#255#152'\5'#255'e9!'#255'^2'#29#255'S7*'#233'DDD'#200'CCC'#183'DDD' - +#143'EEEUDDD"UUU'#9#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'333'#5 - +'@@@'#24'DDDDCCC~P8-'#211']0'#29#255'c8!'#255#144'V2'#255#180'uD'#255#196#138 - +'P'#255#207#155'X'#255#215#165']'#255#220#171'`'#255#221#173'b'#255#222#174 - +'a'#255#222#175'b'#255#222#175'b'#255#222#175'b'#255#222#175'b'#255#222#175 - +'b'#255#222#175'b'#255#222#175'b'#255#222#175'b'#255#222#175'b'#255#222#175 - +'b'#255#222#175'b'#255#222#175'b'#255#222#175'b'#255#222#175'b'#255#222#175 - +'b'#255#222#175'b'#255#222#175'b'#255#222#175'b'#255#222#175'b'#255#222#175 - +'b'#255#222#175'b'#255#222#175'b'#255#222#175'b'#255#222#175'b'#255'VD&'#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#1#1#0#255#166#132'J'#255#222#175'b'#255#222#175 - +'b'#255#222#175'b'#255#222#175'b'#255#222#175'b'#255#222#175'b'#255#222#174 - +'a'#255#221#173'a'#255#219#170'`'#255#213#163'\'#255#205#150'V'#255#192#132 - +'M'#255#176'oA'#255'}K,'#255'a5'#31#255'\0'#28#254'I?;'#212'CCC'#194'DDD'#166 - +'CCCrDDD8GGG'#18'@@@'#4#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2'FFF'#11'D' - +'DD)BBB`G@<'#164'[1'#29#252'_3'#29#255'uD)'#255#174'l?'#255#189#130'L'#255 - +#204#150'V'#255#213#162'^'#255#217#168'`'#255#220#172'b'#255#221#173'b'#255 - +#221#173'c'#255#221#173'c'#255#221#173'c'#255#221#173'c'#255#221#173'c'#255 - +#221#173'c'#255#221#173'c'#255#221#173'c'#255#221#173'c'#255#221#173'c'#255 - +#221#173'c'#255#221#173'c'#255#221#173'c'#255#221#173'c'#255#221#173'c'#255 - +#221#173'c'#255#221#173'c'#255#221#173'c'#255#221#173'c'#255#221#173'c'#255 - +#221#173'c'#255#221#173'c'#255#221#173'c'#255#221#173'c'#255#221#173'c'#255 - +'gQ.'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#8#6#4#255#207#163']'#255#221 - +#173'c'#255#221#173'c'#255#221#173'c'#255#221#173'c'#255#221#173'c'#255#221 - +#173'c'#255#221#172'b'#255#219#171'b'#255#217#167'`'#255#211#159'['#255#200 - +#145'T'#255#185'{H'#255#164'd;'#255'h<#'#255'^1'#29#255'X3#'#244'DDD'#200'CC' - +'C'#183'DDD'#142'DDDSFFF!@@@'#8#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'@@@'#4'II' - +'I'#21'EEE?DDD|S5'''#222'\1'#28#255'c8!'#255#158'_8'#255#183'yH'#255#198#142 - ,'S'#255#209#158'\'#255#215#166'`'#255#218#170'b'#255#219#171'c'#255#219#171 - +'d'#255#219#172'd'#255#219#172'd'#255#219#172'd'#255#219#172'd'#255#219#172 - +'d'#255#219#172'd'#255#219#172'd'#255#219#172'd'#255#219#172'd'#255#219#172 - +'d'#255#219#172'd'#255#219#172'd'#255#219#172'd'#255#219#172'd'#255#219#172 - +'d'#255#219#172'd'#255#219#172'd'#255#219#172'd'#255#219#172'd'#255#219#172 - +'d'#255#219#172'd'#255#219#172'd'#255#219#172'd'#255#219#172'd'#255#219#172 - +'d'#255#213#168'b'#255#3#3#1#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'M<#'#255 - +#219#172'd'#255#219#172'd'#255#219#172'd'#255#219#172'd'#255#219#172'd'#255 - +#219#172'd'#255#219#171'd'#255#219#170'c'#255#217#168'b'#255#214#164'`'#255 - +#207#153'Z'#255#194#136'P'#255#178'rD'#255#139'R1'#255'a4'#31#255'\0'#28#255 - +'L<5'#219'CCC'#193'DDD'#164'CCCoCCC5@@@'#16'UUU'#3#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#1'@@@'#8'BBB#CCCWHA='#158'\0'#28#253'_3'#29#255'~I+'#255#175'nB'#255#191#133 - +'O'#255#205#151'Z'#255#213#163'a'#255#216#167'c'#255#217#169'c'#255#218#170 - +'d'#255#218#170'd'#255#218#170'd'#255#218#170'd'#255#218#170'd'#255#218#170 - +'d'#255#218#170'd'#255#218#170'd'#255#218#170'd'#255#218#170'd'#255#218#170 - +'d'#255#218#170'd'#255#218#170'd'#255#218#170'd'#255#218#170'd'#255#218#170 - +'d'#255#218#170'd'#255#218#170'd'#255#218#170'd'#255#218#170'd'#255#218#170 - +'d'#255#218#170'd'#255#218#170'd'#255#218#170'd'#255#218#170'd'#255#218#170 - +'d'#255#218#170'd'#255#218#170'd'#255'YF)'#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#15#12#7#255#218#170'd'#255#218#170'd'#255#218#170'd'#255#218#170'd' - +#255#218#170'd'#255#218#170'd'#255#218#170'd'#255#218#169'c'#255#217#168'c' - +#255#215#166'b'#255#211#160'_'#255#201#147'W'#255#187'L'#255#169'h>'#255'l=' - +'$'#255'^1'#29#255'W3"'#244'DDD'#199'DDD'#180'DDD'#136'DDDKBBB'#27'UUU'#6#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0'UUU'#3'DDD'#15'EEE4BBBpS6)'#215'\1'#28#255'b7!'#255#158'_9' - +#255#184'{J'#255#199#144'W'#255#209#158'_'#255#214#165'c'#255#216#168'd'#255 - +#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd'#255 - +#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd'#255 - +#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd'#255 - +#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd'#255 - +#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd'#255 - +#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd'#255 - +'YD)'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#189#147'W'#255#217#168'd' - +#255#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd'#255#217#168'd' - +#255#217#168'd'#255#216#168'd'#255#215#167'c'#255#213#164'a'#255#207#154']' - +#255#195#138'S'#255#179'tF'#255#139'R1'#255'`5'#30#255'\0'#28#255'K=7'#216'D' - +'DD'#191'DDD'#157'CCCcAAA+FFF'#11#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'+++'#6'EEE'#26'BBBIE?=' - +#144'Z0'#28#252'^2'#29#255'zF*'#255#174'mB'#255#192#135'R'#255#205#152'\'#255 - +#211#161'b'#255#214#165'd'#255#215#166'd'#255#215#166'd'#255#215#166'd'#255 - +#215#166'd'#255#215#166'd'#255#215#166'd'#255#215#166'd'#255#215#166'd'#255 - +#215#166'd'#255#215#166'd'#255#215#166'd'#255#215#166'd'#255#215#166'd'#255 - +#215#166'd'#255#215#166'd'#255#215#166'd'#255#215#166'd'#255#215#166'd'#255 - +#215#166'd'#255#215#166'd'#255#215#166'd'#255#215#166'd'#255#215#166'd'#255 - +#215#166'd'#255#215#166'd'#255#215#166'd'#255#215#166'd'#255#215#166'd'#255 - ,#215#166'd'#255#215#166'd'#255#215#166'd'#255#215#166'd'#255'jR1'#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255'fN/'#255#215#166'd'#255#215#166'd'#255#215#166'd' - +#255#215#166'd'#255#215#166'd'#255#215#166'd'#255#215#166'd'#255#215#166'd' - +#255#215#167'e'#255#214#164'c'#255#210#159'a'#255#202#148'Z'#255#187#128'N' - +#255#168'f='#255'i;#'#255'\1'#28#255'V3#'#242'CCC'#197'DDD'#173'BBB{FFF>@@@' - +#20'@@@'#4#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#1'MMM'#10'FFF(CCC_R6*'#206'\0'#28#255'`5'#31#255#154'\8' - +#255#182'yJ'#255#199#144'Y'#255#208#156'`'#255#212#163'd'#255#213#165'd'#255 - +#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255 - +#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255 - +#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255 - +#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255 - +#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255 - +#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255 - +#214#165'e'#255#214#165'e'#255#214#165'e'#255#135'h@'#255#2#1#1#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'b' - +'L/'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255#214#165 - +'e'#255#214#165'e'#255#214#165'e'#255#214#165'e'#255#213#165'e'#255#213#164 - +'d'#255#211#161'c'#255#206#154'^'#255#194#139'U'#255#177'sF'#255#135'P/'#255 - +'^3'#29#255'[/'#27#255'J>9'#213'DDD'#185'CCC'#144'DDDSHHH III'#7#0#0#0#1#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'UUU'#3 - +'<<<'#17'DDD8DBAyZ0'#29#248'\1'#28#255'uB'''#255#172'lA'#255#190#132'Q'#255 - +#203#151'^'#255#209#160'c'#255#211#162'e'#255#212#163'e'#255#212#163'e'#255 - +#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255 - +#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255 - +#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255 - +#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255 - +#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255 - +#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255 - +#212#163'e'#255#212#163'e'#255#212#163'e'#255#156'xK'#255#1#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'jQ2'#255#212 - +#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255#212 - +#163'e'#255#212#163'e'#255#212#163'e'#255#212#163'e'#255#212#162'e'#255#211 - +#162'd'#255#208#158'b'#255#200#147'['#255#186'~M'#255#165'd<'#255'd8!'#255'\' - +'0'#28#255'T5'''#237'CCC'#193'DDD'#162'CCCjCCC.@@@'#12#0#0#0#2#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'333'#5'GGG'#25'BB' - +'BIN:1'#174'[/'#27#255'^2'#29#255#145'T3'#255#180'vI'#255#196#142'Y'#255#206 - +#155'a'#255#209#161'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211 - +#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211 - +#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211 - +#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211 - +#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211 - +#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211 - +#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211 - +#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255'O=&'#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - ,#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'sX7'#255#211 - +#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211 - +#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#210 - +#161'f'#255#209#159'd'#255#204#153'`'#255#192#135'U'#255#175'pE'#255'~H+'#255 - +']0'#29#255'Z0'#28#253'FBA'#201'DDD'#176'CCC~FFF>CCC'#19'@@@'#4#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'999'#9'EEE%CCC[V3' - +'$'#222'\0'#27#255'f7!'#255#167'd>'#255#187#128'Q'#255#200#148'^'#255#207#156 - +'d'#255#209#159'e'#255#209#160'e'#255#209#160'e'#255#209#160'e'#255#209#160 - +'e'#255#209#160'e'#255#209#160'e'#255#209#160'e'#255#209#160'e'#255#209#160 - +'e'#255#209#160'e'#255#209#160'e'#255#209#160'e'#255#209#160'e'#255#209#160 - +'e'#255#209#160'e'#255#209#160'e'#255#151'tI'#255'2&'#25#255#27#21#13#255#15 - +#12#7#255#7#5#3#255#7#5#3#255' '#25#16#255'P='''#255#151'tI'#255#209#160'e' - +#255#209#160'e'#255#209#160'e'#255#209#160'e'#255#209#160'e'#255#209#160'e' - +#255#209#160'e'#255#209#160'e'#255#209#160'e'#255#209#160'e'#255#209#160'e' - +#255#209#160'e'#255#209#160'e'#255#6#5#3#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#146'pF'#255#209#160'e'#255#209#160'e'#255 - +#209#160'e'#255#209#160'e'#255#209#160'e'#255#209#160'e'#255#209#160'e'#255 - +#209#160'e'#255#209#160'e'#255#209#160'e'#255#209#160'e'#255#209#159'e'#255 - +#206#155'c'#255#198#143'['#255#182'zM'#255#151'Y6'#255'^3'#29#255'[/'#27#255 - +'N:1'#222'DDD'#185'DDD'#142'DDDOIII'#28'+++'#6#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2';;;'#13'DDD1F@=x[/'#28#253'\1'#28 - +#255'~G+'#255#174'oE'#255#193#137'X'#255#203#151'b'#255#207#156'e'#255#207 - +#158'f'#255#208#158'f'#255#208#158'f'#255#208#158'f'#255#208#158'f'#255#208 - +#158'f'#255#208#158'f'#255#208#158'f'#255#208#158'f'#255#208#158'f'#255#208 - +#158'f'#255#208#158'f'#255#208#158'f'#255#208#158'f'#255#208#158'f'#255'x[;' - +#255#15#12#8#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#4#3#2#255'bJ0'#255#208#158'f'#255#208#158'f' - +#255#208#158'f'#255#208#158'f'#255#208#158'f'#255#208#158'f'#255#208#158'f' - +#255#208#158'f'#255#208#158'f'#255#208#158'f'#255#208#158'f'#255'3'''#25#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#2#2#1#255#208#158'f' - +#255#208#158'f'#255#208#158'f'#255#208#158'f'#255#208#158'f'#255#208#158'f' - +#255#208#158'f'#255#208#158'f'#255#208#158'f'#255#208#158'f'#255#208#158'f' - +#255#208#158'f'#255#207#158'e'#255#206#156'd'#255#201#149'_'#255#189#131'T' - +#255#169'h@'#255'j:#'#255'\0'#28#255'V3#'#242'DDD'#191'CCC'#156'BBB`AAA''333' - +#10#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'@@@'#4'C' - +'CC'#19'EEE?Q7+'#179'[/'#27#255'^1'#29#255#152'X6'#255#181'yM'#255#197#144']' - +#255#204#153'c'#255#206#155'f'#255#206#156'e'#255#206#156'e'#255#206#156'e' - +#255#206#156'e'#255#206#156'e'#255#206#156'e'#255#206#156'e'#255#206#156'e' - +#255#206#156'e'#255#206#156'e'#255#206#156'e'#255#206#156'e'#255#206#156'e' - +#255#202#154'c'#255'6)'#27#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#14#10#7#255#178#135'W'#255#206#156'e'#255#206#156'e'#255#206#156'e' - +#255#206#156'e'#255#206#156'e'#255#206#156'e'#255#206#156'e'#255#206#156'e' - +#255#206#156'e'#255'WB+'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#29#22#14#255#206#156'e'#255#206#156'e'#255#206#156'e'#255#206#156'e' - +#255#206#156'e'#255#206#156'e'#255#206#156'e'#255#206#156'e'#255#206#156'e' - +#255#206#156'e'#255#206#156'e'#255#206#156'e'#255#206#156'e'#255#205#156'e' - ,#255#203#151'c'#255#194#139'Z'#255#176'qH'#255#131'K-'#255'\1'#28#255'[/'#27 - +#255'HA='#204'DDD'#169'DDDqEEE4III'#14#128#128#128#2#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'+++'#6'BBB'#27'EEENW2!'#228'[/'#27#255'i9!' - +#255#168'e?'#255#187#130'T'#255#200#147'`'#255#203#153'e'#255#205#154'e'#255 - +#205#154'e'#255#205#154'e'#255#205#154'e'#255#205#154'e'#255#205#154'e'#255 - +#205#154'e'#255#205#154'e'#255#205#154'e'#255#205#154'e'#255#205#154'e'#255 - +#205#154'e'#255#205#154'e'#255#199#150'a'#255#19#14#9#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#7#6#4#255#178#133'W' - +#255#205#154'e'#255#205#154'e'#255#205#154'e'#255#205#154'e'#255#205#154'e' - +#255#205#154'e'#255#205#154'e'#255#205#154'e'#255'S>)'#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#128'a?'#255#205#154'e'#255#205#154'e' - +#255#205#154'e'#255#205#154'e'#255#205#154'e'#255#205#154'e'#255#205#154'e' - +#255#205#154'e'#255#205#154'e'#255#205#154'e'#255#205#154'e'#255#205#154'e' - +#255#205#154'e'#255#204#154'e'#255#203#152'd'#255#197#145'^'#255#182'zO'#255 - +#157'Z8'#255']1'#29#255'[/'#27#255'P9.'#226'CCC'#179'CCC'#129'CCCA@@@'#20'@@' - +'@'#4#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'999'#9'EEE%EA?' - +'b[/'#27#253'\0'#27#255'F*'#255#173'nG'#255#191#136'Z'#255#200#149'c'#255 - +#202#152'e'#255#202#152'e'#255#202#153'e'#255#202#153'e'#255#202#153'e'#255 - +#202#153'e'#255#202#153'e'#255#202#153'e'#255#202#153'e'#255#202#153'e'#255 - +#202#153'e'#255#202#153'e'#255#202#153'e'#255#202#153'e'#255#31#24#16#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#11#8#6#255#200#151'e'#255#202#153'e'#255#202#153'e'#255#202 - +#153'e'#255#202#153'e'#255#202#153'e'#255#202#153'e'#255#202#153'e'#255'O;''' - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255''''#29#19#255#202#153'e'#255 - +#202#153'e'#255#202#153'e'#255#202#153'e'#255#202#153'e'#255#202#153'e'#255 - +#202#153'e'#255#202#153'e'#255#202#153'e'#255#202#153'e'#255#202#153'e'#255 - +#202#153'e'#255#202#153'e'#255#202#153'e'#255#202#152'e'#255#202#151'd'#255 - +#199#147'b'#255#188#130'U'#255#168'e@'#255'k:"'#255'[/'#27#255'V3$'#241'CCC' - +#186'BBB'#142'EEENBBB'#27'+++'#6#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#2'@@@'#12'DDD-O:0'#146'[/'#27#255'\0'#28#255#145'R2'#255#179'wM' - +#255#194#141'^'#255#200#148'c'#255#201#150'e'#255#201#150'e'#255#201#150'e' - +#255#201#150'e'#255#201#150'e'#255#201#150'e'#255#201#150'e'#255#201#150'e' - +#255#201#150'e'#255#201#150'e'#255#201#150'e'#255#201#150'e'#255#201#150'e' - +#255';,'#30#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'S>*'#255#201#150'e' - +#255#201#150'e'#255#201#150'e'#255#201#150'e'#255#201#150'e'#255#201#150'e' - +#255#201#150'e'#255'K8&'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#29#22#14#255 - +#184#138']'#255#201#150'e'#255#201#150'e'#255#201#150'e'#255#201#150'e'#255 - +#201#150'e'#255#201#150'e'#255#201#150'e'#255#201#150'e'#255#201#150'e'#255 - +#201#150'e'#255#201#150'e'#255#201#150'e'#255#201#150'e'#255#201#150'e'#255 - +#201#150'e'#255#200#149'd'#255#199#148'c'#255#191#136'['#255#173'mG'#255'}D(' - +#255'\0'#27#255'Z0'#28#253'DBB'#192'CCC'#152'DDDZBBB#@@@'#8#0#0#0#1#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#128#128#128#2'III'#14'CCC5T5'''#190'[/'#27#255 - +'_2'#29#255#162'^:'#255#184'}S'#255#195#142'`'#255#198#147'd'#255#199#148'd' - +#255#199#148'd'#255#199#148'd'#255#199#148'd'#255#199#148'd'#255#199#148'd' - +#255#199#148'd'#255#199#148'd'#255#199#148'd'#255#199#148'd'#255#199#148'd' - ,#255#199#148'd'#255#147'mJ'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#3#2#1#255#195#144'b'#255#199#148'd'#255#199#148'd'#255#199#148'd'#255 - +#199#148'd'#255#199#148'd'#255#199#148'd'#255'H6$'#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#3#2#1#255#23#17#12 - +#255'qT8'#255#199#148'd'#255#199#148'd'#255#199#148'd'#255#199#148'd'#255#199 - +#148'd'#255#199#148'd'#255#199#148'd'#255#199#148'd'#255#199#148'd'#255#199 - +#148'd'#255#199#148'd'#255#199#148'd'#255#199#148'd'#255#199#148'd'#255#199 - +#148'd'#255#199#148'd'#255#199#148'd'#255#199#148'd'#255#198#146'c'#255#193 - +#140'^'#255#178'uN'#255#143'O1'#255'\0'#28#255'[/'#27#255'J=7'#208'CCC'#161 - +'DDDfAAA+FFF'#11#0#0#0#2#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'UUU'#3'<<<' - +#17'CCC=X1 '#227'[/'#27#255'm:"'#255#167'eA'#255#187#130'X'#255#195#143'a' - +#255#196#144'c'#255#197#145'c'#255#197#145'c'#255#197#145'c'#255#197#145'c' - +#255#197#145'c'#255#197#145'c'#255#197#145'c'#255#197#145'c'#255#197#145'c' - +#255#197#145'c'#255#197#145'c'#255#197#145'c'#255#19#14#10#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#135'cD'#255#197#145'c'#255#197 - +#145'c'#255#197#145'c'#255#197#145'c'#255#197#145'c'#255#197#145'c'#255'I5$' - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#19#14#10#255'4&'#26#255'cI2'#255 - +#161'vP'#255#197#145'c'#255#197#145'c'#255#197#145'c'#255#197#145'c'#255#197 - +#145'c'#255#197#145'c'#255#197#145'c'#255#197#145'c'#255#197#145'c'#255#197 - +#145'c'#255#197#145'c'#255#197#145'c'#255#197#145'c'#255#197#145'c'#255#197 - +#145'c'#255#197#145'c'#255#197#145'c'#255#197#145'c'#255#197#145'c'#255#197 - +#145'c'#255#197#145'b'#255#196#144'c'#255#194#140'`'#255#182'|S'#255#159'\:' - +#255']2'#28#255'[/'#27#255'Q8-'#226'DDD'#168'DDDqAAA3NNN'#13#128#128#128#2#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'@@@'#4':::'#22'G@<N[/'#27#253'[/'#27 - +#255'~D('#255#172'lF'#255#189#133'['#255#194#141'a'#255#194#142'b'#255#195 - +#142'b'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255#195 - +#142'b'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255#195 - +#142'b'#255'oQ8'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255'N9'''#255#195#142'b'#255#195#142'b'#255#195#142'b'#255#195#142 - +'b'#255#195#142'b'#255#195#142'b'#255#145'jI'#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#0#0#255'7('#28 - +#255#166'yS'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255 - +#195#142'b'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255 - +#195#142'b'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255 - +#195#142'b'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255 - +#195#142'b'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255#195#142'b'#255 - +#195#142'b'#255#194#142'a'#255#193#140'`'#255#185#128'X'#255#166'c?'#255'k8!' - +#255'[/'#27#255'V3#'#241'DDD'#175'DDD{BBB:@@@'#16#128#128#128#2#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#1'333'#5'EEE'#26'Q:/z[/'#27#255'\0'#27#255#142'M/' - +#255#176'rM'#255#189#135']'#255#192#139'a'#255#192#139'`'#255#192#139'`'#255 - +#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255 - +#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255 - +#26#19#13#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255'D1"'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255#192 - +#139'`'#255#192#139'`'#255#192#139'`'#255#11#8#6#255#0#0#0#255#0#0#0#255#0#0 - ,#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#8#6#4#255#143'hH'#255#192#139'`'#255 - +#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255 - +#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255 - +#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255 - +#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255 - +#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255#192#139'`'#255 - +#192#139'`'#255#192#139'`'#255#192#138'a'#255#187#131'Z'#255#170'iE'#255'{B' - +''''#255'[/'#27#255'Z0'#28#253'DDD'#182'DDD'#132'CCCACCC'#19'UUU'#3#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'III'#7'BBB'#31'S5('#154'[/'#27#255'\0'#28#255 - +#153'W5'#255#179'wQ'#255#189#133'^'#255#190#136'_'#255#190#136'_'#255#190#136 - +'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190#136 - +'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#184#132 - +']'#255#1#1#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255'dH2'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255 - +#190#136'_'#255#190#136'_'#255#190#136'_'#255'G3#'#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#2#1#1#255#165'wS'#255#190#136'_'#255#190 - +#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190 - +#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190 - +#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190 - +#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190 - +#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#190 - +#136'_'#255#190#136'_'#255#190#136'_'#255#190#136'_'#255#187#131'\'#255#173 - +'nJ'#255#134'H,'#255'[/'#27#255'[/'#27#255'HA='#194'DDD'#139'CCCHCCC'#23'@@@' - +#4#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'@@@'#8'DDD"V4$'#182'[/'#27#255'`2' - +#29#255#160'\9'#255#181'zT'#255#187#132']'#255#188#133'^'#255#188#134'^'#255 - +#188#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255 - +#188#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255 - +'hK4'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#2#1#1#255#163'uR'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188 - +#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#180#128'Z'#255#13#9 - +#6#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'cF1'#255#188#134'^'#255#188 - +#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188 - +#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188 - +#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188 - +#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188 - +#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188 - +#134'^'#255#188#134'^'#255#188#134'^'#255#188#134'^'#255#188#133'^'#255#186 - +#131'\'#255#176'sO'#255#144'O/'#255'[/'#27#255'[/'#27#255'L=6'#206'DDD'#143 - +'BBBMEEE'#26'333'#5#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'999'#9'EEE%X2 ' - +#209'[/'#27#255'j8 '#255#163'_='#255#181'{V'#255#186#130'\'#255#186#130'\' - +#255#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\' - +#255#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\' - +#255#186#130'\'#255'#'#25#17#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255'_B/'#255#186#130'\'#255#186#130'\'#255#186#130'\' - +#255#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\' - +#255#186#130'\'#255#163'rQ'#255#5#3#2#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'$'#26#18#255 - ,#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255 - +#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255 - +#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255 - +#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255 - +#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255 - +#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255#186#130'\'#255 - +#186#130'\'#255#186#130'\'#255#185#129'\'#255#177'vR'#255#155'V5'#255'\0'#28 - +#255'[/'#27#255'P9/'#217'DDD'#146'BBBQ@@@'#28'UUU'#6#0#0#0#1#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#1'MMM'#10'@@@(Z0'#29#234'[/'#27#255't<$'#255#165'b@'#255#181 - +'{V'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z' - +#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255 - +#184'Z'#255#4#3#2#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1 - +#255'Q8('#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184 - +'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#142'bE' - +#255#3#2#2#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#6#4#3#255#174'yV'#255#184'Z'#255#184'Z'#255#184'Z'#255 - +#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184 - +'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z' - +#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255 - +#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#184 - +'Z'#255#184'Z'#255#184'Z'#255#184'Z'#255#183'~Z'#255#178'wT'#255#159'Y8' - +#255'b3'#30#255'[/'#27#255'S6)'#228'CCC'#149'CCCTBBB'#31'III'#7#0#0#0#1#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#1'FFF'#11'F@>/[/'#27#254'[/'#27#255'}A&'#255#167'gD' - +#255#180'zW'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255 - +#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181 - +'|Y'#255#136']C'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#19#13#9 - +#255#151'gJ'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255 - +#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181 - +'|Y'#255#181'|Y'#255#171'vU'#255'1"'#24#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#1#0#0#255'}V>'#255#181'|Y'#255#181'|Y'#255#181'|Y' - +#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255 - +#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181 - +'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y' - +#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255 - +#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#181'|Y'#255#178 - +'wT'#255#161'];'#255'k8 '#255'[/'#27#255'V3#'#238'DDD'#151'CCCWFFF!@@@'#8#0#0 - +#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2'FFF'#11'K:3?[/'#27#255'[/'#27#255#131'F' - +'*'#255#169'iH'#255#179'xV'#255#180'zW'#255#180'{W'#255#180'{W'#255#180'{W' - +#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255 - +#180'{W'#255#180'{W'#255'<)'#29#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1#255'O' - +'6&'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W' - +#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255 - +#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#142'aE'#255#27 - +#19#13#255#2#1#1#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#8#5#4#255#135']A'#255#180'{W'#255#180 - +'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W' - +#255#180'{W'#255'pL6'#255'+'#29#20#255'('#28#19#255'*'#28#20#255'+'#29#21#255 - +'-'#30#21#255'fF2'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{' - +'W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W' - +#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255#180'{W'#255 - +#180'{W'#255#180'zX'#255#178'vU'#255#163'_>'#255'r<"'#255'[/'#27#255'X2!'#243 - +'CCC'#152'BBBYDDD"@@@'#8#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2'FFF'#11'N8.' - +'J[/'#27#255'[/'#27#255#135'H,'#255#170'kI'#255#178'wV'#255#179'yX'#255#179 - ,'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX' - +#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#16#11#8#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#19#13#10#255#150'fJ'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX' - +#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255 - +#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179 - +'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255'~V>'#255'=)'#30#255 - +#19#13#9#255#1#1#1#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'$'#24#17 - +#255#172'uT'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255 - +#179'yX'#255#179'yX'#255#158'kN'#255'=)'#30#255#6#4#3#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#6#4#3#255'?+'#31#255#164 - +'oP'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX' - +#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255 - +#179'yX'#255#179'yX'#255#179'yX'#255#179'yX'#255#177'uU'#255#164'b@'#255'v?%' - +#255'[/'#27#255'Y1'#31#246'CCC'#152'BBBYDDD"@@@'#8#0#0#0#1#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#1'FFF'#11'Q7+U[/'#27#255'\0'#28#255#139'J-'#255#171'lL'#255#177 - +'wW'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV' - +#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#172'sT'#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#1#1#1#255'O5&'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255 - +#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178 - +'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV' - +#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255 - +#178'wV'#255#178'wV'#255#178'wV'#255#176'wV'#255'vO9'#255'7%'#27#255#16#10#8 - +#255#1#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#11#8#6#255 - +'9&'#28#255#137'\C'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178 - +'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255'M3%'#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#1#0#0#255'4"'#25#255#178'wV'#255#178'wV'#255#178'wV'#255 - +#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178 - +'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#178'wV'#255#176'tT' - +#255#165'cC'#255'yA'''#255'[/'#27#255'Z0'#29#250'DDD'#150'CCCWFFF!III'#7#0#0 - +#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'MMM'#10'R4''^[/'#27#255'\0'#28#255#142 - +'M.'#255#171'nN'#255#177'uV'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX' - +#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255 - +#177'wX'#255'gF3'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#23#16#12#255#152'fK'#255#177'wX'#255#177'wX'#255#177'wX' - +#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255 - +#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177 - +'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX' - +#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255 - +#177'wX'#255#177'wX'#255#177'wX'#255#173'uV'#255#136'\D'#255#133'YB'#255#131 - +'XA'#255#128'V@'#255#153'gL'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX' - +#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255 - +'S8)'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255'"'#23#17#255#175'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX' - +#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255#177'wX'#255 - +#177'wX'#255#177'wX'#255#177'wX'#255#175'tU'#255#166'eE'#255'}B)'#255'\0'#28 - +#255'[/'#28#253'CCC'#148'CCCTBBB'#31'III'#7#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#1'999'#9'V4%h[/'#27#255'\0'#28#255#145'O0'#255#171'mN'#255#175'sU'#255 - +#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175 - +'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255'*'#29#21#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#2#1#1#255'X;,'#255#175'uW' - +#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255 - +#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175 - +'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW' - +#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255 - +#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175 - ,'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW' - +#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255 - +#175'uW'#255#175'uW'#255#175'uW'#255'^?/'#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'$'#24#18#255 - +#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175 - +'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW'#255#175'uW' - +#255#175'sT'#255#166'eE'#255#128'D*'#255'\0'#28#255'[/'#27#255'ECB'#149'CCCP' - +'@@@'#28'UUU'#6#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'@@@'#8'W3#q[/'#27#255 - +'\1'#29#255#150'Q1'#255#171'mN'#255#174'sU'#255#175'uV'#255#175'uV'#255#175 - +'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV' - +#255#175'uV'#255#175'uV'#255#15#10#8#255#0#0#0#255#0#0#0#255#0#0#0#255#4#3#2 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#29#19#14#255#156'iM'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV' - +#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255 - +#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175 - +'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV' - +#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255 - +#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175 - +'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV' - +#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255 - +#175'uV'#255#11#7#6#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'sM9'#255#175'uV'#255#175 - +'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV'#255#175'uV' - +#255#175'uV'#255#175'uV'#255#175'uV'#255#175'tV'#255#174'rS'#255#167'fG'#255 - +#131'G,'#255'\0'#28#255'[/'#27#255'GA?'#150'CCCLGGG'#25'333'#5#0#0#0#1#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#1'III'#7'W3#k[/'#27#255'\1'#29#255#149'P2'#255#169 - +'lM'#255#174'qT'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV' - +#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255 - +#9#6#4#255#0#0#0#255#0#0#0#255#0#0#0#255#136'ZC'#255'T8*'#255#1#1#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#7#5#3#255'bA0'#255#175'tV'#255#175'tV'#255 - +#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175 - +'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV' - +#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255 - +#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175 - +'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV' - +#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255 - +#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175 - +'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#165'nR'#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#10#7#5#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255 - +#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175'tV'#255#175 - +'tV'#255#174'sU'#255#173'qR'#255#166'eG'#255#131'F,'#255'\0'#28#255'[/'#27 - +#255'EA?'#145'DDDGFFF'#22'@@@'#4#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'333' - +#5'W3#\[/'#27#255'\1'#29#255#146'P3'#255#168'jM'#255#173'pT'#255#173'rU'#255 - +#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173 - +'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#4#3#2#255#0#0#0#255#0#0#0#255#21 - +#14#10#255#173'rU'#255#173'rU'#255#152'dK'#255'dB1'#255'A+ '#255'D-!'#255#141 - +']E'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU' - +#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255 - +#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173 - +'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU' - +#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255 - +#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173 - +'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU' - +#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255 - +#173'rU'#255#173'rU'#255#169'pS'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#139'\D'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU' - +#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'rU'#255#173'qT'#255 - +#172'oR'#255#165'cF'#255#128'E+'#255'\0'#28#255'[/'#27#255'CBA'#132'DDD@CCC' - ,#19'UUU'#3#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'@@@'#4'U2"L[/'#27#255'\1' - +#29#255#143'N3'#255#168'hK'#255#172'oT'#255#173'qU'#255#173'rV'#255#173'rV' - +#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255 - +#173'rV'#255#173'rV'#255#1#1#1#255#0#0#0#255#0#0#0#255'nI7'#255#173'rV'#255 - +#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173 - +'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV' - +#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255 - +#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173 - +'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV' - +#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255 - +#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173 - +'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV' - +#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255 - +#173'rV'#255#173'rV'#255#2#1#1#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'H' - +'0$'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV' - +#255#173'rV'#255#173'rV'#255#173'rV'#255#173'rV'#255#173'qU'#255#171'nQ'#255 - +#165'dF'#255'~D+'#255'\1'#29#255'Z0'#28#252'CCCzCCC9@@@'#16#128#128#128#2#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'UUU'#3'T2#=[/'#27#255'\1'#29#255#140'N1' - +#255#166'gI'#255#171'oR'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255 - +#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173 - +'qU'#255#1#1#1#255#27#18#13#255'a@0'#255#173'qU'#255#173'qU'#255#173'qU'#255 - +#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173 - +'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU' - +#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255 - +#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173 - +'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU' - +#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255 - +#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173 - +'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU' - +#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255 - +#173'qU'#255'$'#24#18#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#31#20#15#255 - +#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#173 - +'qU'#255#173'qU'#255#173'qU'#255#173'qU'#255#172'pT'#255#169'lP'#255#164'aD' - +#255'{C*'#255'\1'#29#255'Y0'#30#247'CCCoBBB2;;;'#13#0#0#0#2#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#128#128#128#2'V6''-[/'#27#255'\1'#29#255#137'L0'#255 - +#165'fH'#255#170'mQ'#255#172'pT'#255#172'pU'#255#172'pU'#255#172'pU'#255#172 - +'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU' - +#255#166'lS'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255 - +#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172 - +'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU' - +#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255 - +#168'nS'#255#134'WB'#255'b@0'#255'tK9'#255#166'lS'#255#172'pU'#255#172'pU' - +#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255 - +#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172 - +'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU' - +#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255 - +#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172 - +'pU'#255#129'T@'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#22#14#11#255#172 - +'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU'#255#172'pU' - +#255#172'pU'#255#172'pU'#255#172'pU'#255#172'oT'#255#170'mP'#255#163'`C'#255 - +'xB*'#255'\1'#29#255'Y1'#31#241'BBBdCCC*MMM'#10#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#2'T7*'#29'[/'#27#255'\1'#29#255#134'J0'#255#165'dG' - +#255#169'lQ'#255#171'pU'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255 - +#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172 - +'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW' - +#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255 - +#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172 - +'qW'#255#172'qW'#255#172'qW'#255#135'YD'#255'$'#24#18#255#3#2#2#255#0#0#0#255 - ,#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#8#5#4#255#30#20#16#255'xO='#255#172 - +'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW' - +#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255 - +#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172 - +'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW' - +#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255 - +#14#9#7#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#20#13#10#255#172'qW'#255#172'qW'#255 - +#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172'qW'#255#172 - +'qW'#255#172'qW'#255#171'oT'#255#169'jM'#255#162'_A'#255's>'''#255'\1'#29#255 - +'X2 '#234'CCCXDDD"@@@'#8#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#1'C><'#9'[/'#27#247'\1'#29#255'}F-'#255#164'bE'#255#169'lO'#255#171'pT' - +#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255 - +#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172 - +'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV' - +#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255 - +#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#154'eM'#255#22 - +#15#11#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#25#17#13#255#136'ZE'#255#172'qV' - +#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255 - +#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172 - +'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV' - +#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255 - +#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255'U7*'#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#17#11#9#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV' - +#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#172'qV'#255#171'oT'#255 - +#168'jM'#255#161'^@'#255'k;%'#255'\0'#28#255'V3#'#220'CCCLEEE'#26'333'#5#0#0 - +#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'+++'#6'[/'#28#214'\1'#29 - +#255't@)'#255#162'`B'#255#168'jO'#255#172'pV'#255#173'rX'#255#173'rX'#255#173 - +'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX' - +#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255 - +#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173 - +'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX' - +#255#173'rX'#255#144'_I'#255#6#4#3#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255'Z;-'#255#173'rX'#255#173'rX'#255#173'rX'#255#173 - +'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX' - +#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255 - +#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173 - +'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX' - +#255#171'pV'#255#3#2#1#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'!'#22#17#255#173'rX'#255#173'rX' - +#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255#173'rX'#255 - +#173'rX'#255#172'qX'#255#171'oU'#255#167'hM'#255#160'[>'#255'b5!'#255'\0'#28 - +#255'S5'''#196'EEE?CCC'#19'@@@'#4#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0'UUU'#3'Z0'#29#176'\1'#29#255'k:%'#255#162'^A'#255#169'jN'#255 - +#173'qV'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174 - +'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY' - +#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255 - +#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174 - +'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#151'dM'#255#4#2#2#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1#255 - +#136'YF'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174 - +'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY' - +#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255 - +#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174 - +'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255'5#'#27#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - ,#0#0#255'G.$'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255 - +#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#172'pV'#255#167 - +'gK'#255#153'X:'#255'^3'#31#255'\0'#28#255'Q7+'#166'BBB2777'#14#0#0#0#2#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2'Z0'#29#137'\0'#28 - +#255'a5!'#255#160']>'#255#168'jN'#255#173'sY'#255#175'v\'#255#175'v\'#255#175 - +'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\' - +#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255 - +#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175 - +'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\' - +#255#29#20#15#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#8#5#4#255#169'rX'#255#175'v\'#255#175'v\' - +#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255 - +#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175 - +'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\' - +#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255 - +#175'v\'#255#156'jR'#255#1#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'yQ?'#255#175'v\'#255#175'v\'#255 - +#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175'v\'#255#175 - +'v\'#255#174'u['#255#172'qV'#255#166'gJ'#255#143'S6'#255'^2'#31#255'[/'#27 - +#255'O;2'#130'CCC&999'#9#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#1'Z0'#29'a\0'#28#255'^3'#31#255#153'W;'#255#168'jN'#255 - +#174'tZ'#255#176'x_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176 - +'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_' - +#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255 - +#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176 - +'y_'#255#176'y_'#255#176'y_'#255'oL='#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255']@2'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176 - +'y_'#255'Z=1'#255')'#28#22#255#26#18#14#255#14#10#8#255#10#7#5#255#23#16#12 - +#255'+'#30#23#255'E0&'#255#138'_J'#255#176'y_'#255#176'y_'#255#176'y_'#255 - +#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176 - +'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255'#'#24#19#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#172'w]'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255 - +#176'y_'#255#176'y_'#255#176'y_'#255#176'y_'#255#175'w^'#255#172'rW'#255#165 - +'fI'#255#134'K2'#255']1'#30#255'[/'#27#255'H?:ZBBB'#27'+++'#6#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'X/'#29'7[/'#27#255 - +'^2'#31#255#141'P5'#255#167'hK'#255#174'sY'#255#177'x_'#255#177'za'#255#177 - +'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za' - +#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255 - +#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177 - +'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#9#6#5 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'%'#25#20#255#177'za'#255#177'za' - +#255#177'za'#255#177'za'#255'bD6'#255#8#6#4#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'>+"'#255#175'za' - +#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255 - +#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#136 - +'^K'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#16#11#9#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za' - +#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#177'za'#255#176'x^'#255 - +#172'qV'#255#164'cF'#255'{E,'#255']1'#30#255'Z0'#29#245'DDD<GGG'#18'UUU'#3#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'X?4'#7 - +'[/'#27#243']1'#30#255'}G/'#255#165'eH'#255#174'sY'#255#178'zb'#255#179'}d' - +#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255 - +#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179 - +'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d' - +#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#158'oX'#255 - ,#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'&'#26#21#255#179'}d'#255#179 - +'}d'#255#179'}d'#255'kK;'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#12 - +#8#6#255#137'_L'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d' - +#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255 - +#179'}d'#255#20#14#11#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255'vRB'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179 - +'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#179'}d'#255#177'y`' - +#255#172'pU'#255#163'aC'#255'i;&'#255'\1'#29#255'W2!'#206'FFF,@@@'#12#0#0#0#2 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#1'[/'#27#184'\1'#29#255'l<'''#255#164'bE'#255#173'rX'#255#179'|c'#255#180 - +'g'#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255#181'g' - +#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255 - +#181'g'#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255#181 - +'g'#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255'_C6'#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'Q9.'#255#181'g'#255#181'g' - +#255#152'kW'#255#2#1#1#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#3#2#2#255#151'jV'#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255 - +#181'g'#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255#181 - +'g'#255'sPA'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#23#16 - +#13#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255#181'g' - +#255#181'g'#255#181'g'#255#181'g'#255#181'g'#255#180'g'#255#177'za'#255 - +#171'nS'#255#156'[>'#255'`5!'#255'\0'#28#255'T5'''#155'DDD'#30'III'#7#0#0#0#1 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0'Z/'#28'w\0'#28#255'`5!'#255#157'\?'#255#172'pU'#255#179'}d'#255#181#129 - +'i'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182#129 - +'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182#129 - +'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182#129 - +'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182#129 - +'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255'.!'#27 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#2#1#1#255#166'va'#255#182#129'j' - +#255#182#129'j'#255'A.&'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#9#6#5#255#170'yd'#255#182#129'j'#255#182#129'j' - +#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182#129'j' - +#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#8#6#5#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#135'`O'#255#182#129'j'#255#182 - +#129'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#182 - +#129'j'#255#182#129'j'#255#182#129'j'#255#182#129'j'#255#181#128'h'#255#177 - +'za'#255#169'kP'#255#139'Q7'#255'^3'#31#255'\0'#28#255'N9/\GGG'#18'UUU'#3#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0'X.'#26'4\0'#28#255'^3'#31#255#141'S8'#255#170'mQ'#255#179'|c'#255 - +#182#130'k'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255 - +#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255 - +#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255 - +#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255 - +#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255 - +#20#15#12#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'N8.'#255#184#132'm'#255 - +#184#132'm'#255#184#132'm'#255#22#16#13#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#21#15#13#255#182#130'm' - +#255#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm' - ,#255#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm' - +#255'N8.'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#23#17#13#255#184#132'm'#255 - +#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255 - +#184#132'm'#255#184#132'm'#255#184#132'm'#255#184#132'm'#255#183#131'l'#255 - +#182#129'j'#255#177'x_'#255#167'hK'#255'zG/'#255']1'#30#255'Z0'#28#241'@@@(M' - +'MM'#10#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0'='#31#18#3'[/'#27#238']1'#30#255'{G/'#255#167'hL' - +#255#177'za'#255#183#131'l'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255 - +#185#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255 - +#185#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255 - +#185#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255 - +#185#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255 - +#185#134'p'#255#26#19#16#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#12#9#7#255#185#134 - +'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255#3#2#2#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255'U>4'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255#185 - +#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255#185 - +#134'p'#255#185#134'p'#255#25#18#15#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'xWI'#255#185 - +#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255#185 - +#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255#185#134'p'#255#184 - +#134'o'#255#182#129'j'#255#175'v\'#255#165'dG'#255'h;&'#255'\1'#29#255'X2 ' - +#193'==='#25'+++'#6#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#170'\1'#29#255'f9%'#255 - +#164'bG'#255#175'v]'#255#183#131'l'#255#186#135'r'#255#186#137's'#255#186#137 - +'s'#255#186#137's'#255#186#137's'#255#186#137's'#255#186#137's'#255#186#137 - +'s'#255#186#137's'#255#186#137's'#255#186#137's'#255#186#137's'#255#186#137 - +'s'#255#186#137's'#255#186#137's'#255#186#137's'#255#186#137's'#255#186#137 - +'s'#255#186#137's'#255#186#137's'#255#186#137's'#255#186#137's'#255#186#137 - +'s'#255#186#137's'#255'$'#27#23#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#129'_P'#255 - +#186#137's'#255#186#137's'#255#186#137's'#255#186#137's'#255#1#1#1#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255' '#23#19#255#186#137's'#255#186#137's'#255#186#137's'#255#186 - +#137's'#255#186#137's'#255#186#137's'#255#186#137's'#255#186#137's'#255#186 - +#137's'#255#186#137's'#255#186#137's'#255#172'j'#255#7#5#4#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#12#9#7#255 - +#186#137's'#255#186#137's'#255#186#137's'#255#186#137's'#255#186#137's'#255 - +#186#137's'#255#186#137's'#255#186#137's'#255#186#137's'#255#186#137's'#255 - +#186#137's'#255#185#135'q'#255#181#129'i'#255#173'qW'#255#149'X='#255'`5!' - +#255'\0'#28#255'V4%333'#15#0#0#0#3#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'Z/'#27'O\0'#28 - +#255'_4 '#255#144'U:'#255#172'qV'#255#182#130'k'#255#187#137't'#255#188#139 - +'u'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140 - +'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140 - +'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140 - +'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140 - +'v'#255#188#140'v'#255#188#140'v'#255'1$'#31#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255':+$'#255 - +#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#1 - +#1#1#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#4#3#3#255#188#140'v'#255#188#140'v'#255#188#140 - +'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140 - +'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140 - +'v'#255'}]N'#255'6("'#255#14#11#9#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255'S>5'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140'v' - ,#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140'v'#255#188#140'v' - +#255#188#140'v'#255#188#139'u'#255#186#136'r'#255#180'~f'#255#169'kP'#255'|H' - +'1'#255'^2'#31#255'[/'#27#249'L=6+III'#7#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +'[/'#27#5'[/'#27#235']1'#30#255'wD/'#255#169'jO'#255#180'g'#255#187#137'u' - +#255#189#141'x'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y' - +#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y' - +#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y' - +#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y' - +#255#189#142'y'#255#189#142'y'#255#189#142'y'#255'J7/'#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#16#12#11 - +#255#187#140'y'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y' - +#255#189#142'y'#255#1#1#1#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#181#136'u'#255#189 - +#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189 - +#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189 - +#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#11#8 - +#7#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1#255#181#136's'#255#189#142'y'#255 - +#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255 - +#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#142'y'#255#189#141'x'#255 - +#186#136'q'#255#177'za'#255#164'dG'#255'f:&'#255'\1'#29#255'Y1'#31#186'@@@' - +#16'UUU'#3#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'Z/'#27#147'\1'#29#255 - +'b8$'#255#160'bF'#255#177'y`'#255#186#137's'#255#190#143'z'#255#191#145'|' - +#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|' - +#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|' - +#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|' - +#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|' - +#255#191#145'|'#255#162'{i'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#1#1#1#255#162'{i'#255#191#145'|'#255#191#145'|' - +#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#2#1#1#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#1#1#1#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191 - +#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191 - +#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191 - +#145'|'#255#191#145'|'#255#191#145'|'#255#5#4#3#255#0#0#0#255#0#0#0#255#0#0#0 - +#255'&'#29#25#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|'#255 - +#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|'#255#191#145'|'#255 - +#191#145'|'#255#191#145'|'#255#189#142'y'#255#184#134'o'#255#174'sY'#255#143 - +'U;'#255'`5!'#255'\0'#28#255'U3$`@@@'#8#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0'Z.'#27'5\0'#28#255'_4 '#255#137'Q8'#255#173'rX'#255#184#134'o' - +#255#190#144'{'#255#192#147''#255#192#147''#255#192#147''#255#192#147'' - +#255#192#147''#255#192#147''#255#192#147''#255#192#147''#255#192#147'' - +#255#192#147''#255#192#147''#255#192#147''#255#192#147''#255#192#147'' - +#255#192#147''#255#192#147''#255#192#147''#255#192#147''#255#192#147'' - +#255#192#147''#255#192#147''#255#192#147''#255#192#147''#255#14#11#9#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255' '#24#21#255 - +#192#147''#255#192#147''#255#192#147''#255#192#147''#255#192#147''#255 - +#192#147''#255#192#147''#255#4#3#3#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#8#6#5#255#192#147 - +''#255#192#147''#255#192#147''#255#192#147''#255#192#147''#255#192#147 - +''#255#192#147''#255#192#147''#255#192#147''#255#192#147''#255#192#147 - +''#255#192#147''#255#192#147''#255#192#147''#255#192#147''#255#192#147 - +''#255#1#1#0#255#0#0#0#255#0#0#0#255#0#0#0#255#137'iZ'#255#192#147''#255 - +#192#147''#255#192#147''#255#192#147''#255#192#147''#255#192#147''#255 - +#192#147''#255#192#147''#255#192#147''#255#192#147''#255#192#146'~'#255 - +#189#142'y'#255#182#129'j'#255#169'lP'#255'uD.'#255'^2'#31#255'[/'#28#237'NF' - ,'B'#21'UUU'#3#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/' - +#27#211']1'#30#255'l>)'#255#167'iM'#255#181'g'#255#190#143'z'#255#193#148 - +#128#255#194#149#130#255#194#149#130#255#194#149#130#255#194#149#130#255#194 - +#149#130#255#194#149#130#255#194#149#130#255#194#149#130#255#194#149#130#255 - +#194#149#130#255#194#149#130#255#194#149#130#255#194#149#130#255#194#149#130 - +#255#194#149#130#255#194#149#130#255#194#149#130#255#194#149#130#255#194#149 - +#130#255#194#149#130#255#194#149#130#255#194#149#130#255'G60'#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'ZE<'#255#194#149#130 - +#255#194#149#130#255#194#149#130#255#194#149#130#255#194#149#130#255#194#149 - +#130#255#194#149#130#255#7#6#5#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#23#17#15#255#194#149 - +#130#255#194#149#130#255#194#149#130#255#194#149#130#255#194#149#130#255#194 - +#149#130#255#194#149#130#255#194#149#130#255#194#149#130#255#194#149#130#255 - +#194#149#130#255#194#149#130#255#194#149#130#255#194#149#130#255#194#149#130 - +#255#179#137'x'#255#0#0#0#255#0#0#0#255#0#0#0#255'-#'#30#255#194#149#130#255 - +#194#149#130#255#194#149#130#255#194#149#130#255#194#149#130#255#194#149#130 - +#255#194#149#130#255#194#149#130#255#194#149#130#255#194#149#130#255#194#149 - +#130#255#192#147''#255#188#140'v'#255#177'za'#255#156'_D'#255'a6#'#255'\1' - +#29#255'Y1'#31#153'@@@'#8#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0'Z/'#27']\0'#28#255'`5!'#255#143'V='#255#175'v]'#255#187#138 - +'u'#255#193#149#129#255#195#152#132#255#195#152#133#255#195#152#133#255#195 - +#152#133#255#195#152#133#255#195#152#133#255#195#152#133#255#195#152#133#255 - +#195#152#133#255#195#152#133#255#195#152#133#255#195#152#133#255#195#152#133 - +#255#195#152#133#255#195#152#133#255#195#152#133#255#195#152#133#255#195#152 - +#133#255#195#152#133#255#195#152#133#255#195#152#133#255#195#152#133#255#170 - +#133't'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#172#133'u'#255#195#152#133#255#195#152#133#255#195#152#133#255#195#152#133 - +#255#195#152#133#255#195#152#133#255#146'rd'#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255'_JA'#255#195#152#133#255#195#152#133#255#195#152#133#255#195#152#133#255 - +#195#152#133#255#195#152#133#255#195#152#133#255#195#152#133#255#195#152#133 - +#255#195#152#133#255#195#152#133#255#195#152#133#255#195#152#133#255#195#152 - +#133#255#195#152#133#255#140'm`'#255#0#0#0#255#0#0#0#255#6#5#4#255#186#145'' - +#255#195#152#133#255#195#152#133#255#195#152#133#255#195#152#133#255#195#152 - +#133#255#195#152#133#255#195#152#133#255#195#152#133#255#195#152#133#255#195 - +#152#133#255#195#152#132#255#192#147''#255#185#134'p'#255#172'pU'#255'|I2' - +#255'^3'#31#255'\0'#28#250'R5()UUU'#3#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#3'[/'#27#221']1'#30#255'oA,'#255#169'lP' - +#255#183#131'l'#255#192#147''#255#196#154#135#255#197#155#136#255#197#155 - +#136#255#197#155#136#255#197#155#136#255#197#155#136#255#197#155#136#255#197 - +#155#136#255#197#155#136#255#197#155#136#255#197#155#136#255#197#155#136#255 - +#197#155#136#255#197#155#136#255#197#155#136#255#197#155#136#255#197#155#136 - +#255#197#155#136#255#197#155#136#255#197#155#136#255#197#155#136#255#197#155 - +#136#255#197#155#136#255#26#20#18#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#8#6#5#255#197#155#136#255#197#155#136#255#197#155#136#255#197#155#136 - +#255#197#155#136#255#197#155#136#255#197#155#136#255#28#22#19#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#4#3#3#255#193#153#134#255#197#155#136#255#197#155#136#255#197#155 - +#136#255#197#155#136#255#197#155#136#255#197#155#136#255#197#155#136#255#197 - +#155#136#255#197#155#136#255#197#155#136#255#197#155#136#255#197#155#136#255 - +#197#155#136#255#197#155#136#255#197#155#136#255#182#143'~'#255'=0*'#255#7#5 - +#5#255#130'fY'#255#197#155#136#255#197#155#136#255#197#155#136#255#197#155 - +#136#255#197#155#136#255#197#155#136#255#197#155#136#255#197#155#136#255#197 - +#155#136#255#197#155#136#255#196#154#136#255#195#152#133#255#190#144'{'#255 - +#180'~f'#255#159'bG'#255'c8%'#255'\1'#29#255'Z0'#30#163'III'#7#0#0#0#1#0#0#0 - ,#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27 - +'g\0'#28#255'a6#'#255#147'Y@'#255#177'za'#255#190#143'z'#255#196#154#135#255 - +#198#157#139#255#198#157#139#255#198#157#139#255#198#157#139#255#198#157#139 - +#255#198#157#139#255#198#157#139#255#198#157#139#255#198#157#139#255#198#157 - +#139#255#198#157#139#255#198#157#139#255#198#157#139#255#198#157#139#255#198 - +#157#139#255#198#157#139#255#198#157#139#255#198#157#139#255#198#157#139#255 - +#198#157#139#255#198#157#139#255#198#157#139#255'<0+'#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255'*!'#29#255#198#157#139#255#198#157#139#255#198#157 - +#139#255#198#157#139#255#198#157#139#255#198#157#139#255#164#130's'#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255'?2,'#255#198#157#139#255#198#157#139#255#198#157#139 - +#255#198#157#139#255#198#157#139#255#198#157#139#255#198#157#139#255#198#157 - +#139#255#198#157#139#255#198#157#139#255#198#157#139#255#198#157#139#255#198 - +#157#139#255#198#157#139#255#198#157#139#255#198#157#139#255#198#157#139#255 - +#198#157#139#255#198#157#139#255#198#157#139#255#198#157#139#255#198#157#139 - +#255#198#157#139#255#198#157#139#255#198#157#139#255#198#157#139#255#198#157 - +#139#255#198#157#139#255#198#157#139#255#198#157#139#255#198#156#138#255#195 - +#152#132#255#187#138't'#255#173'sY'#255'L5'#255'^3'#31#255'\0'#28#252'U3$,' - +#128#128#128#2#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0'[/'#27#7'[/'#27#229'^2'#31#255'qB.'#255#171'nS'#255 - +#185#134'p'#255#194#151#131#255#198#158#140#255#200#160#142#255#200#160#142 - +#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160 - +#142#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160#142#255#200 - +#160#142#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160#142#255 - +#200#160#142#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160#142 - +#255'9.)'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'hSJ'#255#200#160#142 - +#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160 - +#142#255'6+'''#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#6#5#5#255#184#148#131#255#200#160#142 - +#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160 - +#142#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160#142#255#200 - +#160#142#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160#142#255 - +#200#160#142#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160#142 - +#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160 - +#142#255#200#160#142#255#200#160#142#255#200#160#142#255#200#160#142#255#199 - +#159#141#255#198#157#139#255#192#147''#255#181'g'#255#161'dI'#255'd:&'#255 - +'\1'#29#255'Z0'#29#171'@@@'#4#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27'q\1'#29#255'a6#' - +#255#143'W?'#255#177'za'#255#191#144'|'#255#198#157#139#255#200#161#144#255 - +#201#162#145#255#201#163#145#255#201#163#145#255#201#163#145#255#201#163#145 - +#255#201#163#145#255#201#163#145#255#201#163#145#255#201#163#145#255#201#163 - +#145#255#201#163#145#255#201#163#145#255#201#163#145#255#201#163#145#255#201 - +#163#145#255#201#163#145#255#201#163#145#255#201#163#145#255#201#163#145#255 - +#201#163#145#255'=2,'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'!'#27#24#255#197 - +#161#143#255#201#163#145#255#201#163#145#255#201#163#145#255#201#163#145#255 - +#201#163#145#255#197#161#143#255#3#2#2#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1#255#149'zl'#255#201 - +#163#145#255#201#163#145#255#201#163#145#255#201#163#145#255#201#163#145#255 - +#201#163#145#255#201#163#145#255#201#163#145#255#201#163#145#255#201#163#145 - +#255#201#163#145#255#201#163#145#255#201#163#145#255#201#163#145#255#201#163 - +#145#255#201#163#145#255#201#163#145#255#201#163#145#255#201#163#145#255#201 - +#163#145#255#201#163#145#255#201#163#145#255#201#163#145#255#201#163#145#255 - +#201#163#145#255#201#163#145#255#201#163#145#255#201#163#145#255#201#163#145 - +#255#201#162#145#255#200#161#143#255#197#155#136#255#187#138'u'#255#173'rX' - +#255'|I3'#255'_4 '#255'\0'#28#253'W1 2'#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - ,#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27 - +#6'\0'#28#216'^2'#31#255'i=)'#255#165'jP'#255#184#133'n'#255#196#152#134#255 - +#201#162#145#255#202#164#148#255#202#165#148#255#202#165#148#255#202#165#148 - +#255#202#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255#202#165 - +#148#255#202#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255#202 - +#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255 - +#202#165#148#255#202#165#148#255'E82'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#2#2#2#255#1#1#1#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'.&"'#255#202 - +#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255 - +#202#165#148#255#202#165#148#255#146'wk'#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'eRI'#255#202 - +#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255 - +#202#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255#202#165#148 - +#255#202#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255#202#165 - +#148#255#202#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255#202 - +#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255 - +#202#165#148#255#202#165#148#255#202#165#148#255#202#165#148#255#202#165#148 - +#255#202#165#148#255#202#164#147#255#200#160#143#255#193#148#128#255#180'~f' - +#255#151'^D'#255'a7$'#255'\1'#29#255'Z0'#28#156'UUU'#3#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0'[/'#27'D\0'#28#255'`5!'#255#128'N8'#255#175'v]'#255#190 - +#143'{'#255#200#159#142#255#203#165#150#255#204#167#151#255#204#167#151#255 - +#204#167#151#255#204#167#151#255#204#167#151#255#204#167#151#255#204#167#151 - +#255#204#167#151#255#204#167#151#255#204#167#151#255#204#167#151#255#204#167 - +#151#255#204#167#151#255#204#167#151#255#204#167#151#255#204#167#151#255#204 - +#167#151#255#204#167#151#255#204#167#151#255'N?9'#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255'vaW'#255#204#167#151#255#160#131'w'#255'cRI'#255'WG@'#255 - +#168#137'|'#255#204#167#151#255#204#167#151#255#204#167#151#255#204#167#151 - +#255#204#167#151#255#204#167#151#255#204#167#151#255#204#167#151#255#145'wk' - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255'G:4'#255#204#167#151#255#204#167#151#255#204#167#151#255#204 - +#167#151#255#204#167#151#255#204#167#151#255#204#167#151#255#204#167#151#255 - +#204#167#151#255#204#167#151#255#204#167#151#255#204#167#151#255#204#167#151 - +#255#204#167#151#255#204#167#151#255#204#167#151#255#204#167#151#255#204#167 - +#151#255#204#167#151#255#204#167#151#255#204#167#151#255#204#167#151#255#204 - +#167#151#255#204#167#151#255#204#167#151#255#204#167#151#255#204#167#151#255 - +#204#167#151#255#204#167#151#255#204#167#151#255#204#167#150#255#202#165#148 - +#255#198#155#138#255#186#137's'#255#171'nU'#255'oB.'#255'^2'#31#255'\0'#28 - +#235'V2"'#21#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27 - +#169'\1'#29#255'c9&'#255#155'aH'#255#182#128'j'#255#195#152#133#255#202#164 - +#148#255#205#169#154#255#206#169#155#255#206#169#155#255#206#169#155#255#206 - +#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255 - +#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155 - +#255#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255#206#169 - +#155#255'VGA'#255#0#0#0#255#0#0#0#255#0#0#0#255#5#4#4#255#206#169#155#255#206 - +#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255 - +#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155 - +#255#206#169#155#255#206#169#155#255#206#169#155#255#152'}r'#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#2#1#1#255'vaY'#255#206 - +#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255 - +#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155 - +#255#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255#206#169 - +#155#255#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255#206 - +#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255 - +#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155#255#206#169#155 - +#255#206#169#155#255#206#169#155#255#205#167#152#255#201#162#145#255#192#147 - +''#255#177'za'#255#138'T='#255'`6"'#255'\1'#29#255'[0'#29'e'#0#0#0#1#0#0#0#0 - ,#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#27'\0'#28#243'^3'#31 - +#255'pC/'#255#171'oU'#255#188#138'u'#255#199#158#141#255#205#169#154#255#207 - +#171#157#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255 - +#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158 - +#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172 - +#158#255#207#172#158#255#207#172#158#255#207#172#158#255'~i`'#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#22#18#16#255#207#172#158#255#207#172#158#255#207#172#158 - +#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172 - +#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207 - +#172#158#255#207#172#158#255#207#172#158#255'/''$'#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#9#7#7#255#164#136'~'#255#207#172#158#255#207#172#158 - +#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172 - +#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207 - +#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255 - +#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158 - +#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172 - +#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207 - +#172#158#255#207#171#157#255#204#167#151#255#196#153#136#255#183#131'l'#255 - +#160'fK'#255'f;('#255']1'#30#255'[/'#27#199'te^'#3#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27'q\0'#28#255'`6"'#255 - +#130'P9'#255#177'x`'#255#192#146''#255#203#164#149#255#208#173#159#255#209 - +#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255 - +#209#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175#161 - +#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175 - +#161#255#209#175#161#255#209#175#161#255#199#167#153#255#0#0#0#255#0#0#0#255 - +#0#0#0#255'.&#'#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175 - +#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209 - +#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255 - +#209#175#161#255#209#175#161#255#209#175#161#255'*$!'#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255'"'#28#26#255#197#165#153#255#209#175#161#255#209#175#161#255#209#175#161 - +#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175 - +#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209 - +#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255 - +#209#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175#161 - +#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175 - +#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209#175#161#255#209 - +#174#160#255#207#171#157#255#200#160#144#255#188#139'w'#255#171'pV'#255'rD0' - +#255'^3'#31#255'\0'#28#250'\2'#30'1'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#1'[/'#27#184']1'#30#255'c9&' - +#255#148'^E'#255#182#128'j'#255#197#154#136#255#206#170#155#255#210#176#163 - +#255#211#178#164#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178 - +#165#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255#211 - +#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255 - +#211#178#165#255#211#178#165#255#211#178#165#255#12#10#9#255#0#0#0#255#0#0#0 - +#255'MA<'#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255 - +#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165 - +#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178 - +#165#255#211#178#165#255#211#178#165#255#211#178#165#255#178#150#139#255'D95' - +#255#9#7#7#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'V' - +'IC'#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255#211 - +#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255 - +#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165 - +#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178 - +#165#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255#211 - +#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255 - +#211#178#165#255#211#178#165#255#211#178#165#255#211#178#165#255#211#178#164 - ,#255#209#176#162#255#204#167#151#255#193#148#129#255#177'x`'#255#129'P9'#255 - +'`6"'#255'\0'#28#255'[0'#28't'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#21'\0'#28#229'^2' - +#31#255'i?+'#255#163'jP'#255#186#136's'#255#200#160#144#255#209#174#160#255 - +#212#180#167#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169 - +#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181 - +#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213 - +#181#169#255#213#181#169#255#213#181#169#255'.''%'#255#0#0#0#255',%#'#255#200 - +#170#159#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255 - +#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169 - +#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181 - +#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213 - +#181#169#255#139'vo'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'.' - +'''$'#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213 - +#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255 - +#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169 - +#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181 - +#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213 - +#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255 - +#213#181#169#255#213#181#169#255#213#181#169#255#213#181#169#255#212#180#168 - +#255#211#178#165#255#207#171#157#255#197#155#137#255#182#128'j'#255#148']E' - +#255'c9&'#255']1'#30#255'[/'#27#179#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27 - +'?\0'#28#252'_4 '#255'tF1'#255#172'sY'#255#190#142'z'#255#203#165#150#255#211 - +#178#164#255#213#183#170#255#214#184#172#255#214#184#172#255#214#184#172#255 - +#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172 - +#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184 - +#172#255#214#184#172#255#214#184#172#255'ng'#255'{ib'#255#214#184#172#255 - +#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172 - +#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184 - +#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214 - +#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255 - +#214#184#172#255' '#28#26#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#14#12 - +#11#255#209#180#168#255#214#184#172#255#214#184#172#255#214#184#172#255#214 - +#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255 - +#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172 - +#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184 - +#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214 - +#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255 - +#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172#255#214#184#172 - +#255#213#182#169#255#209#176#161#255#200#161#143#255#186#135'q'#255#161'gO' - +#255'h=*'#255'^2'#31#255'\0'#28#226'W-'#26#19#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0'[/'#27'}\1'#29#255'`6"'#255'|M8'#255#175'x^'#255#192#147''#255 - +#205#170#154#255#213#181#169#255#215#185#173#255#216#187#175#255#216#187#175 - +#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187 - +#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216 - +#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255 - +#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175 - +#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187 - +#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216 - +#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255 - +#216#187#175#255#187#162#151#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#2#2 - +#2#255#180#156#146#255#216#187#175#255#216#187#175#255#216#187#175#255#216 - +#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255 - +#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175 - +#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187 - +#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216 - ,#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255 - +#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175#255#216#187#175 - +#255#215#185#173#255#211#179#166#255#202#165#148#255#188#139'w'#255#167'mU' - +#255'nC/'#255'^3'#31#255'\0'#28#251'Z.'#27';'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0'[/'#27#2'[/'#27#170'\1'#29#255'a7$'#255#130'Q<'#255#178 - +'{c'#255#195#151#132#255#208#173#159#255#215#185#173#255#217#189#178#255#217 - +#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255 - +#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178 - +#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189 - +#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217 - +#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255 - +#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178 - +#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189 - +#178#255#217#189#178#255'.(&'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#128 - +'oi'#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217 - +#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255 - +#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178 - +#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189 - +#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217 - +#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255 - +#217#189#178#255#217#189#178#255#217#189#178#255#217#189#178#255#217#188#177 - +#255#213#183#170#255#205#168#153#255#191#144'|'#255#170'rZ'#255'sF2'#255'`5!' - +#255'\0'#28#255'Z/'#27'g'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0'[/'#27#6'[/'#27#188']1'#30#255'c9&'#255#136'V?'#255#180'~g' - +#255#197#154#136#255#209#176#162#255#216#187#176#255#219#192#181#255#219#192 - +#182#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219 - +#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255 - +#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183 - +#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193 - +#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219 - +#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255 - +#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255#158#138#131 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1#255#131'sm'#255#219#193#183#255#219 - +#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255 - +#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183 - +#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193 - +#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219 - +#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255 - +#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183#255#219#193#183 - +#255#219#193#183#255#219#192#182#255#218#191#180#255#215#185#173#255#207#172 - +#156#255#193#147#128#255#174'v^'#255'wJ6'#255'`6"'#255'\1'#29#255'[/'#27'~'#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0'[/'#27#12'[/'#27#203']1'#30#255'c:'''#255#141'YC'#255#181#128'h'#255 - +#198#155#138#255#210#177#164#255#217#190#178#255#220#194#184#255#221#195#185 - +#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196 - +#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221 - +#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255 - +#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186 - +#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196 - +#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221 - +#196#186#255#221#196#186#255#221#196#186#255're_'#255#0#0#0#255#0#0#0#255#4#4 - +#4#255#160#142#134#255#221#196#186#255#221#196#186#255#221#196#186#255#221 - +#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255 - +#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186 - +#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196 - ,#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221 - +#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255 - +#221#196#186#255#221#196#186#255#221#196#186#255#221#196#186#255#221#195#185 - +#255#219#193#183#255#216#187#175#255#207#172#158#255#194#148#130#255#176'x_' - +#255'{M8'#255'a6#'#255'\1'#29#255'[/'#27#148#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27 - +#21'\0'#28#217']1'#30#255'c:'''#255#138'XB'#255#181'h'#255#197#156#137#255 - +#211#178#164#255#218#191#180#255#221#196#187#255#222#198#188#255#223#198#189 - +#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198 - +#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223 - +#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255 - +#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189 - +#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198 - +#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223 - +#198#189#255#221#196#187#255#6#6#5#255#10#9#8#255#188#167#159#255#223#198#189 - +#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198 - +#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223 - +#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255 - +#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189 - +#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198 - +#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223#198#189#255#223 - +#198#189#255#223#198#189#255#222#198#188#255#221#196#186#255#217#188#177#255 - +#208#173#159#255#193#149#129#255#174'w^'#255'zM8'#255'a7$'#255'\1'#29#255'[/' - +#27#170'[/'#27#2#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27' \0'#28#223']1'#30 - +#255'c:'''#255#132'T?'#255#180'~g'#255#197#155#136#255#211#178#164#255#219 - +#193#181#255#223#198#189#255#224#201#192#255#224#201#192#255#224#201#192#255 - +#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192 - +#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201 - +#192#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255#224 - +#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255 - +#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192 - +#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255#134'xr' - +#255#205#184#176#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201 - +#192#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255#224 - +#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255 - +#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192 - +#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201 - +#192#255#224#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255#224 - +#201#192#255#224#201#192#255#224#201#192#255#224#201#192#255#223#200#191#255 - +#222#197#187#255#217#189#178#255#207#172#158#255#192#147''#255#171'u\'#255 - +'vI6'#255'a6#'#255'\1'#29#255'[/'#27#179'[/'#27#6#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0'[/'#27#25'\0'#28#209']1'#30#255'c9&'#255#128'Q='#255#177 - +'{c'#255#195#151#133#255#209#176#162#255#219#192#181#255#223#200#191#255#225 - +#203#194#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255 - +#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196 - +#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204 - +#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226 - +#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255 - +#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196 - +#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204 - +#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226 - +#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255 - +#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196 - ,#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204 - +#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226 - +#204#196#255#226#204#196#255#226#204#196#255#226#204#196#255#226#203#195#255 - +#225#202#193#255#222#198#188#255#217#188#177#255#206#169#155#255#191#144'|' - +#255#168'qY'#255'rG3'#255'`6"'#255'\1'#29#255'[/'#27#159'[/'#27#4#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#15'\0'#28#193']1' - +#30#255'b8%'#255'yM8'#255#170'u\'#255#192#146'~'#255#207#171#156#255#217#189 - +#178#255#223#200#191#255#226#205#197#255#227#206#198#255#228#207#199#255#228 - +#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255 - +#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199 - +#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207 - +#199#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255#228 - +#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255 - +#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199 - +#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207 - +#199#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255#228 - +#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255 - +#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199 - +#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207#199#255#228#207 - +#199#255#228#207#199#255#228#207#199#255#228#207#199#255#227#206#198#255#226 - +#204#196#255#222#198#188#255#215#185#173#255#202#165#148#255#187#137'u'#255 - +#159'jR'#255'nD0'#255'`6"'#255'\1'#29#255'[/'#27#137#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#8'[/' - +#27#175'\1'#29#255'`6"'#255'nD0'#255#158'iR'#255#187#137'u'#255#202#165#148 - +#255#215#186#174#255#223#199#190#255#227#205#198#255#229#208#201#255#229#209 - +#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229 - +#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255 - +#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202 - +#255#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229#210 - +#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229 - +#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255 - +#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202 - +#255#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229#210 - +#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229 - +#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255 - +#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202#255#229#210#202 - +#255#229#210#202#255#229#209#202#255#228#207#201#255#226#204#197#255#221#196 - +#187#255#212#180#168#255#198#157#140#255#183#130'l'#255#143'^G'#255'g>+'#255 - +'_4 '#255'\1'#29#254'[/'#27'q'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#3'[/'#27 - +#132'\1'#29#254'_4 '#255'g>+'#255#140'[F'#255#181#129'j'#255#198#155#138#255 - +#211#178#165#255#221#195#185#255#227#204#197#255#230#209#203#255#230#212#205 - +#255#231#212#206#255#231#212#206#255#231#212#206#255#231#212#206#255#231#212 - +#206#255#231#212#206#255#231#212#206#255#231#212#206#255#231#212#206#255#231 - +#212#206#255#231#212#206#255#231#212#206#255#231#212#206#255#231#212#206#255 - +#231#212#206#255#231#212#206#255#231#212#206#255#231#212#206#255#231#212#206 - +#255#231#212#206#255#231#212#206#255#231#212#206#255#231#212#206#255#231#212 - +#206#255#231#212#206#255#231#212#206#255#231#212#206#255#231#212#206#255#231 - +#212#206#255#231#212#206#255#231#212#206#255#231#212#206#255#231#212#206#255 - +#231#212#206#255#231#212#206#255#231#212#206#255#231#212#206#255#231#212#206 - +#255#231#212#206#255#231#212#206#255#231#212#206#255#231#212#206#255#231#212 - +#206#255#231#212#206#255#231#212#206#255#231#212#206#255#231#212#206#255#231 - ,#212#206#255#231#212#206#255#231#212#206#255#231#211#206#255#230#211#205#255 - +#229#209#201#255#225#203#194#255#219#191#181#255#208#173#159#255#193#149#129 - +#255#173'w`'#255'~Q='#255'c:'''#255'^2'#31#255'\0'#28#238'[/'#27'O'#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27'F\0'#28#234'^2'#31#255'c:' - +''''#255'zN:'#255#168's['#255#190#143'{'#255#205#167#152#255#217#187#177#255 - +#225#201#193#255#229#209#202#255#232#213#207#255#232#214#209#255#233#215#210 - +#255#233#215#210#255#233#215#210#255#233#215#210#255#233#215#210#255#233#215 - +#210#255#233#215#210#255#233#215#210#255#233#215#210#255#233#215#210#255#233 - +#215#210#255#233#215#210#255#233#215#210#255#233#215#210#255#233#215#210#255 - +#233#215#210#255#233#215#210#255#233#215#210#255#233#215#210#255#233#215#210 - +#255#233#215#210#255#233#215#210#255#233#215#210#255#233#215#210#255#233#215 - +#210#255#233#215#210#255#233#215#210#255#233#215#210#255#233#215#210#255#233 - +#215#210#255#233#215#210#255#233#215#210#255#233#215#210#255#233#215#210#255 - +#233#215#210#255#233#215#210#255#233#215#210#255#233#215#210#255#233#215#210 - +#255#233#215#210#255#233#215#210#255#233#215#210#255#233#215#210#255#233#215 - +#210#255#233#215#210#255#233#215#210#255#232#215#209#255#232#214#208#255#231 - +#212#206#255#228#207#201#255#223#198#189#255#213#182#170#255#200#160#144#255 - +#186#136'r'#255#158'iR'#255'pE2'#255'a7$'#255']1'#30#255'\0'#28#201'[/'#27#31 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27 - +#26'\0'#28#193']1'#30#255'`6"'#255'iA.'#255#142']H'#255#179#128'h'#255#196 - +#153#135#255#210#176#163#255#220#194#184#255#227#205#198#255#231#212#206#255 - +#233#216#211#255#234#217#212#255#234#218#213#255#234#218#213#255#234#218#213 - +#255#234#218#213#255#234#218#213#255#234#218#213#255#234#218#213#255#234#218 - +#213#255#234#218#213#255#234#218#213#255#234#218#213#255#234#218#213#255#234 - +#218#213#255#234#218#213#255#234#218#213#255#234#218#213#255#234#218#213#255 - +#234#218#213#255#234#218#213#255#234#218#213#255#234#218#213#255#234#218#213 - +#255#234#218#213#255#234#218#213#255#234#218#213#255#234#218#213#255#234#218 - +#213#255#234#218#213#255#234#218#213#255#234#218#213#255#234#218#213#255#234 - +#218#213#255#234#218#213#255#234#218#213#255#234#218#213#255#234#218#213#255 - +#234#218#213#255#234#218#213#255#234#218#213#255#234#218#213#255#234#218#213 - +#255#234#218#213#255#234#217#212#255#234#216#211#255#233#215#210#255#230#211 - +#205#255#226#203#195#255#218#190#179#255#206#170#155#255#192#146''#255#172 - +'v`'#255#129'S?'#255'e<)'#255'_4 '#255'\1'#29#255'[/'#27#143'[/'#27#5#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +'[/'#27#3'[/'#27'\0'#28#248'^3'#31#255'c:'''#255'sI6'#255#158'kT'#255#186 - +#136's'#255#200#160#143#255#213#180#169#255#222#197#187#255#229#208#201#255 - +#232#214#209#255#234#218#213#255#235#219#215#255#236#220#215#255#236#220#216 - +#255#236#220#216#255#236#220#216#255#236#220#216#255#236#220#216#255#236#220 - +#216#255#236#220#216#255#236#220#216#255#236#220#216#255#236#220#216#255#236 - +#220#216#255#236#220#216#255#236#220#216#255#236#220#216#255#236#220#216#255 - +#236#220#216#255#236#220#216#255#236#220#216#255#236#220#216#255#236#220#216 - +#255#236#220#216#255#236#220#216#255#236#220#216#255#236#220#216#255#236#220 - +#216#255#236#220#216#255#236#220#216#255#236#220#216#255#236#220#216#255#236 - +#220#216#255#236#220#216#255#236#220#216#255#236#220#216#255#236#220#216#255 - +#236#220#216#255#236#220#216#255#236#220#216#255#236#220#215#255#235#219#215 - +#255#234#217#212#255#232#213#207#255#227#205#198#255#219#193#183#255#209#176 - +#162#255#196#153#136#255#181#129'l'#255#145'`K'#255'lB0'#255'a7$'#255']1'#30 - +#255'\0'#28#231'[/'#27'O'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - ,#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27'$\0' - +#28#185'\1'#29#255'`5!'#255'f=*'#255#128'S>'#255#166'r['#255#188#139'v'#255 - +#201#161#145#255#213#181#169#255#223#197#189#255#229#209#202#255#233#216#210 - +#255#235#219#215#255#236#221#217#255#237#222#219#255#238#223#220#255#238#223 - +#220#255#238#223#220#255#238#223#220#255#238#223#220#255#238#223#220#255#238 - +#223#220#255#238#223#220#255#238#223#220#255#238#223#220#255#238#223#220#255 - +#238#223#220#255#238#223#220#255#238#223#220#255#238#223#220#255#238#223#220 - +#255#238#223#220#255#238#223#220#255#238#223#220#255#238#223#220#255#238#223 - +#220#255#238#223#220#255#238#223#220#255#238#223#220#255#238#223#220#255#238 - +#223#220#255#238#223#220#255#238#223#220#255#238#223#220#255#238#223#220#255 - +#238#223#220#255#238#223#220#255#237#222#219#255#237#222#218#255#236#221#217 - +#255#235#218#214#255#232#214#208#255#228#206#199#255#220#193#184#255#210#176 - +#163#255#197#155#137#255#184#134'o'#255#155'iR'#255'uI7'#255'c:'''#255'^3'#31 - +#255'\1'#29#252'[/'#27#144'[/'#27#14#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27'U\0'#28#228']1'#30#255'`6"'#255'g>,'#255'R?' - +#255#164'qZ'#255#188#139'v'#255#200#159#144#255#211#179#166#255#220#194#184 - +#255#228#206#199#255#232#214#209#255#235#219#215#255#237#222#219#255#238#225 - +#221#255#239#225#222#255#239#225#222#255#239#226#223#255#239#226#223#255#239 - +#226#223#255#239#226#223#255#239#226#223#255#239#226#223#255#239#226#223#255 - +#239#226#223#255#239#226#223#255#239#226#223#255#239#226#223#255#239#226#223 - +#255#239#226#223#255#239#226#223#255#239#226#223#255#239#226#223#255#239#226 - +#223#255#239#226#223#255#239#226#223#255#239#226#223#255#239#226#223#255#239 - +#226#223#255#239#226#223#255#239#226#223#255#239#226#223#255#239#225#222#255 - +#238#225#221#255#238#224#220#255#237#222#218#255#235#218#214#255#231#212#206 - +#255#226#203#195#255#218#191#180#255#209#174#160#255#197#154#136#255#184#132 - +'o'#255#154'hR'#255'uK8'#255'e;('#255'_4 '#255'\1'#29#255'\0'#28#197'[/'#27 - +'/'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0'[/'#27#12'[/'#27#134'\0'#28#242'^2'#31#255'a5#'#255'g>+'#255 - +'|Q='#255#160'lV'#255#184#133'p'#255#196#152#134#255#207#170#156#255#216#187 - +#175#255#224#200#192#255#230#210#203#255#234#216#211#255#236#220#216#255#238 - +#224#220#255#239#226#223#255#240#227#225#255#240#228#225#255#240#228#225#255 - +#241#229#226#255#241#229#226#255#241#229#226#255#241#229#226#255#241#229#227 - +#255#241#229#227#255#241#229#227#255#241#229#227#255#241#229#227#255#241#229 - +#227#255#241#229#227#255#241#229#227#255#241#229#227#255#241#229#226#255#241 - +#229#226#255#241#229#226#255#241#229#226#255#240#228#225#255#240#228#225#255 - +#240#227#224#255#239#225#222#255#238#223#220#255#236#220#215#255#233#215#210 - +#255#228#207#201#255#222#196#187#255#213#183#170#255#204#166#150#255#192#147 - +''#255#179'i'#255#150'fO'#255'tI7'#255'e;('#255'`5!'#255']1'#30#255'\0'#28 - +#223'[/'#27'`[/'#27#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#17'[/'#27'\' - +'0'#28#238']1'#30#255'`6"'#255'e<)'#255'rI6'#255#142'_J'#255#170'wa'#255#188 - +#140'w'#255#199#158#141#255#208#173#159#255#216#186#175#255#222#196#187#255 - ,#227#206#198#255#232#213#208#255#235#219#214#255#237#222#218#255#238#224#220 - +#255#239#226#223#255#240#227#225#255#241#229#226#255#242#230#228#255#242#230 - +#228#255#242#231#229#255#242#231#229#255#242#231#229#255#242#231#229#255#242 - +#231#229#255#242#231#229#255#242#231#229#255#242#230#228#255#241#229#227#255 - +#241#229#226#255#240#227#225#255#239#225#222#255#238#224#220#255#236#221#217 - +#255#234#218#213#255#231#211#206#255#226#204#196#255#220#194#184#255#214#183 - +#171#255#206#169#155#255#196#153#135#255#185#135'r'#255#162'q['#255#134'XD' - +#255'lC1'#255'c:'''#255'_4 '#255']0'#30#255'\0'#28#217'[/'#27'`[/'#27#4#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#13'[/' - +#27'w\0'#28#233']1'#30#255'_4 '#255'b8%'#255'h?-'#255'zN;'#255#149'dP'#255 - +#170'xa'#255#187#137't'#255#194#150#131#255#202#164#148#255#210#176#163#255 - +#216#187#175#255#220#194#184#255#224#200#192#255#228#207#199#255#231#212#206 - +#255#234#217#211#255#236#220#215#255#236#222#217#255#237#222#219#255#238#223 - +#220#255#238#223#220#255#238#224#220#255#238#223#220#255#237#223#219#255#237 - +#223#218#255#236#221#217#255#235#219#215#255#233#216#210#255#230#211#204#255 - +#227#204#197#255#223#199#190#255#219#192#183#255#215#185#173#255#208#173#159 - +#255#200#160#144#255#192#146''#255#183#133'o'#255#164's\'#255#142']J'#255'r' - +'I7'#255'f=*'#255'a6#'#255'^3'#31#255'\1'#29#255'\0'#28#210'[/'#27'X[/'#27#2 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#5'[/'#27'P\0'#28#173'\1'#29#249']1'#30#255'`' - +'5!'#255'c9&'#255'g>,'#255'qH6'#255#133'YE'#255#153'iT'#255#172'yd'#255#186 - +#136'q'#255#191#144'}'#255#196#153#136#255#202#162#146#255#207#171#156#255 - +#211#178#165#255#214#183#171#255#216#186#175#255#217#188#177#255#218#190#179 - +#255#219#191#182#255#219#192#182#255#219#192#181#255#218#189#179#255#217#187 - +#177#255#215#185#173#255#213#182#169#255#210#176#163#255#205#169#154#255#200 - +#160#144#255#195#151#133#255#190#142'z'#255#183#132'o'#255#167'u_'#255#148'e' - +'P'#255'S@'#255'mE2'#255'f=*'#255'b7%'#255'_4 '#255']1'#30#255'\0'#28#235'[' - +'/'#27#147'[/'#27'5'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0'[/'#27#18'[/'#27'i\0'#28#199'\1'#29#254']1'#30#255'_4 '#255 - +'a6#'#255'd9('#255'g>,'#255'oF3'#255'}Q>'#255#138'[G'#255#149'fQ'#255#160'oZ' - +#255#170'xa'#255#176'}h'#255#180#128'l'#255#183#133'p'#255#186#136's'#255#188 - +#138'u'#255#188#139'v'#255#187#138'u'#255#186#135'r'#255#183#132'n'#255#178 - +#128'j'#255#174'|e'#255#168'v`'#255#158'mW'#255#146'bN'#255#133'YE'#255'yM;' - +#255'mC1'#255'g>+'#255'c9&'#255'`6"'#255'^3'#31#255']1'#30#255'\0'#28#248'\0' - +#28#173'[/'#27'O[/'#27#5#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - ,#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#20'[/'#27 - +'V\0'#28#153'\0'#28#220'\1'#29#255']1'#30#255'^3'#31#255'`5!'#255'a7$'#255'c' - +'9&'#255'e;('#255'f=*'#255'g>,'#255'i@.'#255'jB0'#255'mE2'#255'qH5'#255'tJ7' - +#255'qG5'#255'lC1'#255'jA0'#255'h?-'#255'g>+'#255'e<)'#255'd:('#255'c9&'#255 - +'a6#'#255'`5!'#255'^2'#31#255']1'#30#255'\1'#29#252'\0'#28#201'[/'#27#134'[/' - +#27'C[/'#27#8#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#31'[/'#27'_[/'#27#138'\0'#28#176'\0'#28 - +#213'\0'#28#248'\1'#29#255'\1'#29#255'\1'#29#255']1'#30#255'^2'#31#255'^2'#31 - +#255'^3'#31#255'^3'#31#255'^3'#31#255'^2'#31#255']1'#30#255']1'#30#255'\1'#29 - +#255'\1'#29#255'\1'#29#255'\0'#28#241'\0'#28#203'\0'#28#165'[/'#27'[/'#27'N' - +'[/'#27#15#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0'[/'#27#2'[/'#27'![/'#27'E[/'#27'U[/'#27'a[/'#27'm[/' - +#27'y[/'#27#134'\0'#29#141'[/'#27#130'[/'#27'v[/'#27'j[/'#27'^[/'#27'Q[/'#27 - +'=[/'#27#23#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - ,#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#255#255#255#255#255#255#255#128#0 - +#255#255#255#255#255#255#255#255#255#255#255#255#255#224#0#0#3#255#255#255 - +#255#255#255#255#255#255#255#255#254#0#0#0#0'?'#255#255#255#255#255#255#255 - +#255#255#255#240#0#0#0#0#7#255#255#255#255#255#255#255#255#255#255#128#0#0#0 - +#0#0#255#255#255#255#255#255#255#255#255#254#0#0#0#0#0#0'?'#255#255#255#255 - +#255#255#255#255#248#0#0#0#0#0#0#15#255#255#255#255#255#255#255#255#224#0#0#0 - +#0#0#0#3#255#255#255#255#255#255#255#255#128#0#0#0#0#0#0#0#255#255#255#255 - +#255#255#255#254#0#0#0#0#0#0#0#0'?'#255#255#255#255#255#255#252#0#0#0#0#0#0#0 - +#0#31#255#255#255#255#255#255#240#0#0#0#0#0#0#0#0#7#255#255#255#255#255#255 - +#224#0#0#0#0#0#0#0#0#3#255#255#255#255#255#255#128#0#0#0#0#0#0#0#0#1#255#255 - +#255#255#255#255#0#0#0#0#0#0#0#0#0#0''#255#255#255#255#254#0#0#0#0#0#0#0#0#0 - +#0'?'#255#255#255#255#252#0#0#0#0#0#0#0#0#0#0#31#255#255#255#255#248#0#0#0#0 - +#0#0#0#0#0#0#15#255#255#255#255#240#0#0#0#0#0#0#0#0#0#0#7#255#255#255#255#224 - +#0#0#0#0#0#0#0#0#0#0#3#255#255#255#255#192#0#0#0#0#0#0#0#0#0#0#1#255#255#255 - +#255#128#0#0#0#0#0#0#0#0#0#0#0#255#255#255#255#0#0#0#0#0#0#0#0#0#0#0#0''#255 - +#255#254#0#0#0#0#0#0#0#0#0#0#0#0''#255#255#254#0#0#0#0#0#0#0#0#0#0#0#0'?' - +#255#255#252#0#0#0#0#0#0#0#0#0#0#0#0#31#255#255#248#0#0#0#0#0#0#0#0#0#0#0#0 - +#15#255#255#240#0#0#0#0#0#0#0#0#0#0#0#0#7#255#255#240#0#0#0#0#0#0#0#0#0#0#0#0 - +#7#255#255#224#0#0#0#0#0#0#0#0#0#0#0#0#3#255#255#192#0#0#0#0#0#0#0#0#0#0#0#0 - +#3#255#255#192#0#0#0#0#0#0#0#0#0#0#0#0#1#255#255#128#0#0#0#0#0#0#0#0#0#0#0#0 - +#1#255#255#128#0#0#0#0#0#0#0#0#0#0#0#0#0#255#255#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#255#255#0#0#0#0#0#0#0#0#0#0#0#0#0#0''#254#0#0#0#0#0#0#0#0#0#0#0#0#0#0'' - +#254#0#0#0#0#0#0#0#0#0#0#0#0#0#0'?'#254#0#0#0#0#0#0#0#0#0#0#0#0#0#0'?'#252#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0'?'#252#0#0#0#0#0#0#0#0#0#0#0#0#0#0#31#252#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#31#248#0#0#0#0#0#0#0#0#0#0#0#0#0#0#15#248#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#15#248#0#0#0#0#0#0#0#0#0#0#0#0#0#0#15#240#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#7#240#0#0#0#0#0#0#0#0#0#0#0#0#0#0#7#240#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#7#224#0#0#0#0#0#0#0#0#0#0#0#0#0#0#7#224#0#0#0#0#0#0#0#0#0#0#0#0#0#0#3#224#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#3#224#0#0#0#0#0#0#0#0#0#0#0#0#0#0#3#224#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#3#224#0#0#0#0#0#0#0#0#0#0#0#0#0#0#3#192#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#3#192#0#0#0#0#0#0#0#0#0#0#0#0#0#0#3#192#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#1#192#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1#192#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1#192 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1#192#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1#192#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#1#192#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1#192#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#1#192#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1#192#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#1#192#0#0#0#0#0#0#0#0#0#0#0#0#0#0#3#192#0#0#0#0#0#0#0#0#0#0#0#0#0#0#3 - +#224#0#0#0#0#0#0#0#0#0#0#0#0#0#0#3#224#0#0#0#0#0#0#0#0#0#0#0#0#0#0#3#224#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#3#224#0#0#0#0#0#0#0#0#0#0#0#0#0#0#3#224#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#3#240#0#0#0#0#0#0#0#0#0#0#0#0#0#0#7#240#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#7#240#0#0#0#0#0#0#0#0#0#0#0#0#0#0#7#240#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#15#248#0#0#0#0#0#0#0#0#0#0#0#0#0#0#15#248#0#0#0#0#0#0#0#0#0#0#0#0#0#0#15#248 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#15#252#0#0#0#0#0#0#0#0#0#0#0#0#0#0#31#252#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#31#252#0#0#0#0#0#0#0#0#0#0#0#0#0#0'?'#254#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0'?'#254#0#0#0#0#0#0#0#0#0#0#0#0#0#0'?'#254#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0''#255#0#0#0#0#0#0#0#0#0#0#0#0#0#0''#255#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#255#255#128#0#0#0#0#0#0#0#0#0#0#0#0#0#255#255#128#0#0#0#0#0#0#0#0#0#0#0 - +#0#1#255#255#128#0#0#0#0#0#0#0#0#0#0#0#0#1#255#255#192#0#0#0#0#0#0#0#0#0#0#0 - +#0#3#255#255#192#0#0#0#0#0#0#0#0#0#0#0#0#3#255#255#224#0#0#0#0#0#0#0#0#0#0#0 - +#0#7#255#255#224#0#0#0#0#0#0#0#0#0#0#0#0#15#255#255#240#0#0#0#0#0#0#0#0#0#0#0 - ,#0#15#255#255#248#0#0#0#0#0#0#0#0#0#0#0#0#31#255#255#248#0#0#0#0#0#0#0#0#0#0 - +#0#0'?'#255#255#252#0#0#0#0#0#0#0#0#0#0#0#0''#255#255#252#0#0#0#0#0#0#0#0#0 - +#0#0#0#255#255#255#254#0#0#0#0#0#0#0#0#0#0#0#0#255#255#255#255#0#0#0#0#0#0#0 - +#0#0#0#0#1#255#255#255#255#128#0#0#0#0#0#0#0#0#0#0#3#255#255#255#255#128#0#0 - +#0#0#0#0#0#0#0#0#7#255#255#255#255#192#0#0#0#0#0#0#0#0#0#0#15#255#255#255#255 - +#224#0#0#0#0#0#0#0#0#0#0#31#255#255#255#255#240#0#0#0#0#0#0#0#0#0#0#31#255 - +#255#255#255#248#0#0#0#0#0#0#0#0#0#0'?'#255#255#255#255#252#0#0#0#0#0#0#0#0#0 - +#0''#255#255#255#255#254#0#0#0#0#0#0#0#0#0#1#255#255#255#255#255#255#0#0#0#0 - +#0#0#0#0#0#3#255#255#255#255#255#255#128#0#0#0#0#0#0#0#0#7#255#255#255#255 - +#255#255#224#0#0#0#0#0#0#0#0#15#255#255#255#255#255#255#240#0#0#0#0#0#0#0#0 - +#31#255#255#255#255#255#255#248#0#0#0#0#0#0#0#0''#255#255#255#255#255#255 - +#254#0#0#0#0#0#0#0#0#255#255#255#255#255#255#255#255#128#0#0#0#0#0#0#3#255 - +#255#255#255#255#255#255#255#192#0#0#0#0#0#0#7#255#255#255#255#255#255#255 - +#255#240#0#0#0#0#0#0#31#255#255#255#255#255#255#255#255#252#0#0#0#0#0#0'' - +#255#255#255#255#255#255#255#255#255#0#0#0#0#0#3#255#255#255#255#255#255#255 - +#255#255#255#224#0#0#0#0#15#255#255#255#255#255#255#255#255#255#255#252#0#0#0 - +#0''#255#255#255#255#255#255#255#255#255#255#255#192#0#0#7#255#255#255#255 - +#255#255#255#255#255#255#255#255#254#0#1#255#255#255#255#255#255#255#255#255 - +#255#255#255#255#255#255#255#255#255#255#255#255#255#255#255#255#255#255#255 - +#255#255#255#255#255#255#255#255#255#255#255#255#255#255#255#255#255#255#255 - +#255#255#255#255#255#255#255#255'('#0#0#0'@'#0#0#0#128#0#0#0#1#0' '#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'@@@'#4'III'#7'III'#7'33' - +'3'#5#128#128#128#2#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'UUU'#6'DDD'#15 - +'FFF'#22'EEE'#26'DDD'#30'III#GGG/DDD<DDD@CCC5FFF(BBB'#31'BBB'#27'@@@'#24'GGG' - +#18'999'#9#128#128#128#2#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#128#128#128 - +#2'999'#9'GGG'#18'DDD'#30'CCC9FFF_DDDEDD'#146'DDC'#159'EED'#170'DDC'#175'DD' - +'C'#178'DDC'#179'EED'#177'EED'#173'DCC'#164'EDD'#151'BBB'#136'CCCoCCCHCCC&CC' - +'C'#23';;;'#13'@@@'#4#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#128#128#128#2'777'#14'DDD"EEEFCCCoEDD'#150'DDC'#175'FFC' - +#187'GGC'#195'IFC'#200'LD<'#208'OB:'#215'RA6'#221'WA0'#226'TA3'#223'PB9'#218 - +'MD<'#212'JD@'#206'IGD'#200'HGD'#197'EED'#191'DDC'#180'CCC'#163'DDD'#128'DDD' - +'VGGG/III'#21'+++'#6#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#128#128#128 - +#2'FFF'#11'BBB#CCCXEED'#149'EEB'#179'GFC'#191'MC<'#208'W?.'#228'd=%'#246'i?!' - +#252'j?'#30#255'l?'#31#255'nA'#31#255'sF!'#253'zL%'#253'wG#'#253'oB!'#254'm@' - +#31#255'k>'#30#255'j>'#31#253'g>#'#249'[>+'#235'PB8'#218'HEC'#203'FEC'#195'E' - +'ED'#184'DDD'#166'DDDqEEE4@@@'#20'@@@'#4#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'999'#9'BBB'#31 - +'CCCPEDD'#143'FFE'#182'NB:'#211'Z?-'#231'g>#'#249'l?'#31#255'zK$'#253#150']*' - +#255#165'h.'#255#172'm0'#255#180's2'#255#186'x4'#255#189'{4'#255#192'5'#255 - +#190'|4'#255#187'x4'#255#182'u3'#255#175'p1'#255#167'j/'#255#156'b+'#255#131 - +'R&'#254'oB '#255'i>!'#253'_>)'#238'S@6'#222'FEB'#203'DDC'#190'DDD'#164'CCCk' - +'CCC.PPP'#16'UUU'#3#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - ,#0#0#0#128#128#128#2'CCC'#19'BBBBDDD'#136'EED'#179'LA9'#209'`=&'#243'k?'#31 - +#255'N&'#254#154'`,'#255#176'q1'#255#191'~5'#255#200#138'7'#255#207#145'8' - +#255#210#149'9'#255#213#153'9'#255#217#157':'#255#219#160';'#255#221#162';' - +#255#220#162':'#255#217#158':'#255#215#155':'#255#211#151'9'#255#208#146'8' - +#255#203#141'8'#255#194#129'6'#255#183'v4'#255#163'f.'#255#137'U('#254'qC"' - +#254'f<!'#250'T?4'#224'GDB'#202'CCB'#188'DDD'#158'BBB`>>>!+++'#6#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'@@@'#4'BBB'#27'CCC[CCB'#165'LD='#203']<' - +'('#238'j> '#255#135'R('#254#176'p3'#255#193#128'7'#255#202#139'9'#255#211 - +#151';'#255#220#162'<'#255#227#170'>'#255#232#176'?'#255#234#180'?'#255#237 - +#182'?'#255#239#185'@'#255#241#187'@'#255#242#188'A'#255#241#187'@'#255#240 - +#185'A'#255#238#183'@'#255#235#180'@'#255#233#178'?'#255#229#173'>'#255#222 - +#165'='#255#215#156'<'#255#205#144':'#255#196#133'8'#255#185'x5'#255#152'^-' - +#255'oC"'#254'd<#'#247'QA7'#220'FFE'#200'CCC'#180'CCC}AAA/UUU'#9#0#0#0#1#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#1'III'#7'CCC&DDDpECC'#180'X<,'#232'i=!'#254#128'O('#254 - +#170'l3'#255#194#130':'#255#210#150'>'#255#220#163'?'#255#227#171'A'#255#234 - +#180'B'#255#240#187'D'#255#242#190'D'#255#244#193'E'#255#246#194'D'#255#247 - +#195'E'#255#248#197'E'#255#249#197'E'#255#249#198'E'#255#249#197'E'#255#248 - +#196'E'#255#247#195'E'#255#246#195'E'#255#245#194'D'#255#243#191'D'#255#240 - +#188'C'#255#236#183'C'#255#230#174'B'#255#223#165'@'#255#215#156'?'#255#200 - +#137';'#255#182'u6'#255#146'[,'#255'm@!'#254'`:%'#244'KA;'#211'CCC'#189'CCC' - +#144'BBB>PPP'#16#128#128#128#2#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'999'#9'@@@4CCC'#133'HD@'#192'_9$'#244 - +'rE%'#254#164'h3'#255#191';'#255#206#146'?'#255#219#163'C'#255#232#178'F' - +#255#238#185'G'#255#241#189'H'#255#244#193'H'#255#246#195'I'#255#247#196'I' - +#255#247#197'J'#255#247#197'I'#255#247#198'I'#255#248#198'I'#255#248#198'I' - +#255#248#197'I'#255#248#198'I'#255#248#198'I'#255#248#198'I'#255#247#197'I' - +#255#247#197'I'#255#247#196'I'#255#246#196'I'#255#245#194'H'#255#242#190'H' - +#255#239#187'H'#255#235#182'G'#255#224#168'D'#255#211#153'A'#255#196#134'=' - +#255#177'q7'#255#133'Q)'#254'e: '#253'N?7'#218'DDC'#195'CCC'#160'FFFP@@@'#20 - +#0#0#0#2#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +'III'#7'FFF3CCC'#145'LB;'#204'd:"'#250'|L('#254#180's9'#255#202#143'A'#255 - +#216#159'F'#255#228#174'I'#255#236#184'K'#255#241#191'L'#255#243#193'M'#255 - +#244#194'M'#255#244#195'M'#255#245#196'M'#255#245#195'M'#255#245#195'M'#255 - +#245#195'M'#255#245#195'M'#255#245#195'M'#255#245#195'M'#255#245#195'M'#255 - +#245#195'M'#255#245#195'M'#255#245#195'M'#255#245#195'M'#255#245#195'M'#255 - +#245#195'M'#255#245#196'M'#255#245#195'M'#255#244#194'M'#255#243#194'M'#255 - +#242#192'M'#255#238#186'L'#255#232#179'J'#255#220#165'F'#255#208#150'C'#255 - +#190'<'#255#147'[.'#255'h< '#255'S=1'#228'EED'#199'DDD'#168'BBBUGGG'#18#0#0 - +#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'@@@'#4'FFF(EEE'#134 - +'P?5'#212'e:!'#254#137'U,'#254#186'z='#255#208#150'E'#255#224#171'L'#255#233 - +#181'O'#255#238#187'P'#255#240#191'P'#255#242#192'Q'#255#242#193'Q'#255#242 - +#193'Q'#255#242#193'P'#255#242#193'Q'#255#242#193'Q'#255#242#193'Q'#255#242 - +#193'Q'#255#242#193'Q'#255#242#193'Q'#255#242#193'Q'#255#242#193'Q'#255#242 - +#193'Q'#255#242#193'Q'#255#242#193'Q'#255#242#193'Q'#255#242#193'Q'#255#242 - +#193'Q'#255#242#193'Q'#255#242#193'Q'#255#242#193'Q'#255#242#193'Q'#255#242 - +#193'Q'#255#241#192'Q'#255#239#189'P'#255#234#184'O'#255#228#175'M'#255#215 - +#159'I'#255#194#132'@'#255#160'd3'#255'k?"'#254'X:*'#236'FDC'#201'CCC'#164'A' - +'AAG;;;'#13#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'UUU'#3'BBB'#31'CCCvO=' - +'3'#211'f:'#31#255#150']2'#255#190#128'A'#255#211#155'I'#255#226#175'Q'#255 - +#234#185'S'#255#237#188'T'#255#238#190'T'#255#239#191'U'#255#239#191'U'#255 - +#239#191'U'#255#239#191'U'#255#239#191'U'#255#239#191'U'#255#239#191'U'#255 - +#239#191'U'#255#235#189'S'#255'|d,'#255'N>'#28#255'4*'#19#255'=1'#22#255'WE' - +#31#255'w_*'#255#173#139'='#255#239#191'U'#255#239#191'U'#255#239#191'U'#255 - +#239#191'U'#255#239#191'U'#255#239#191'U'#255#239#191'U'#255#239#191'U'#255 - +#239#191'U'#255#239#190'T'#255#238#189'U'#255#236#187'S'#255#230#179'R'#255 - +#217#162'L'#255#198#138'D'#255#170'l8'#255'pC$'#254'Z8'''#240'DBB'#197'CCC' - ,#153'FFF:@@@'#8#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2'FFF'#22'BBBeJ?9'#195'b8 ' - +#254#150']2'#255#194#133'E'#255#213#158'N'#255#226#176'T'#255#233#185'W'#255 - +#236#187'W'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255 - +#236#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255 - +'cO%'#255#4#3#1#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#1#1#0#255#18#14#7#255'A4'#24#255#134'k2'#255#218#174'R'#255 - +#236#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255#236#188'X'#255 - +#236#188'X'#255#236#187'W'#255#234#186'W'#255#229#180'U'#255#218#165'P'#255 - +#201#143'H'#255#173'o<'#255'k>#'#254'S;-'#231'CCC'#193'CCC'#141'DDD-fff'#5#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0'@@@'#12'DDDKFA='#179'`7 '#251#137'S-'#255#192#131'E'#255 - +#214#161'Q'#255#226#176'W'#255#231#183'Y'#255#233#185'['#255#233#186'['#255 - +#234#185'Z'#255#234#185'Z'#255#234#185'Z'#255#234#185'Z'#255#234#185'Z'#255 - +#234#185'Z'#255#234#185'Z'#255#234#185'Z'#255'L<'#29#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#8#7#3#255'P?'#31#255#203#161'N' - +#255#234#185'Z'#255#234#185'Z'#255#234#185'Z'#255#234#185'Z'#255#233#186'[' - +#255#232#183'Z'#255#228#178'Y'#255#219#167'T'#255#201#143'K'#255#163'g8'#255 - +'f: '#255'O=4'#222'DDD'#188'CCCzBBB'#27#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'UUU'#3'GGG$D@@'#147'\6#' - +#245'{I*'#254#185'{C'#255#211#157'R'#255#225#175'Z'#255#229#181'\'#255#230 - +#183']'#255#230#183']'#255#230#183']'#255#230#183']'#255#230#183']'#255#230 - +#183']'#255#230#183']'#255#230#183']'#255#230#183']'#255#230#183']'#255#138 - +'m8'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#24#20#10#255'x_0'#255#226#181'['#255 - +#230#183']'#255#230#183']'#255#230#183']'#255#230#181'\'#255#227#177'['#255 - +#217#165'V'#255#196#136'J'#255#151']4'#255'b8 '#254'K@9'#214'CCC'#175'AAAJ..' - +'.'#11#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0'@@@'#16'CCC\V6'''#228'nA&'#254#178'tA'#255#205#151'R'#255#221#172 - +'\'#255#226#179'_'#255#228#180'_'#255#228#180'_'#255#228#180'_'#255#228#180 - +'_'#255#228#180'_'#255#228#180'_'#255#228#180'_'#255#228#180'_'#255#228#180 - +'_'#255#228#180'_'#255#228#180'_'#255#17#13#7#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#2#1#1#255'H9'#30#255#224#178']'#255#228#180'_'#255 - +#228#180'_'#255#227#179'^'#255#224#175']'#255#213#160'W'#255#190#129'H'#255 - +#137'T/'#255'^4 '#252'FB?'#201'CCC'#140'DDD"'#0#0#0#3#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'@@@'#4'FFF,J=7'#175'`4'#30#255#165 - +'g;'#255#200#144'Q'#255#217#166'\'#255#223#176'`'#255#225#177'`'#255#225#177 - +'a'#255#225#177'a'#255#225#177'a'#255#225#177'a'#255#225#177'a'#255#225#177 - +'a'#255#225#177'a'#255#225#177'a'#255#225#177'a'#255#225#177'a'#255#218#171 - +'^'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#11#9#5#255#196#154'U'#255#225#177'a'#255#225#177'`'#255#224#177'a' - +#255#220#171'^'#255#208#154'W'#255#184'{F'#255'nA%'#254'T8*'#233'DDD'#180'CC' - +'CX333'#15#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'C' - +'CC'#19'CCCjY5#'#239'~J,'#254#191#132'M'#255#213#162']'#255#220#171'a'#255 - +#221#173'c'#255#222#174'b'#255#222#174'b'#255#222#174'b'#255#222#174'b'#255 - +#222#174'b'#255#222#174'b'#255#222#174'b'#255#222#174'b'#255#222#174'b'#255 - +#222#174'b'#255#222#174'b'#255#154'yC'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#15#11#6#255#218#172'`' - +#255#222#174'b'#255#222#174'b'#255#221#173'b'#255#217#167'_'#255#202#146'T' - +#255#156'a9'#255'^4'#30#254'G@='#206'DDD'#151'>>>)@@@'#4#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0'UUU'#3'AAA+L;4'#188'b6 '#254#170'l@'#255#206#152 - +'Z'#255#217#168'b'#255#219#171'd'#255#219#171'd'#255#219#171'd'#255#219#171 - +'d'#255#219#171'd'#255#219#171'd'#255#219#171'd'#255#219#171'd'#255#219#171 - +'d'#255#219#171'd'#255#219#171'd'#255#219#171'd'#255#219#171'd'#255#219#171 - +'d'#255#26#20#12#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - ,#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255'oW3'#255#219#171'd'#255#219#171'd'#255#219 - +#171'd'#255#218#169'c'#255#212#161'_'#255#187'L'#255'uD)'#254'W6('#238'CCC' - +#182'CCC[III'#14#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'@@@'#12'EBB' - +'S[3'#31#244#134'O/'#255#192#134'Q'#255#212#162'b'#255#215#167'd'#255#216#168 - +'d'#255#216#168'd'#255#216#168'd'#255#216#168'd'#255#216#168'd'#255#216#168 - +'d'#255#216#168'd'#255#216#168'd'#255#216#168'd'#255#216#168'd'#255#216#168 - +'d'#255#216#168'd'#255#216#168'd'#255#216#168'd'#255#198#154'\'#255#5#4#2#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255'!'#25#15#255#216#168'd'#255#216#168'd'#255#216#168'd'#255#216#168'd'#255 - +#214#166'd'#255#202#147'Y'#255#161'e='#255'^2'#29#255'J?:'#208'CCC'#137'EEE' - +#26#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'CCC'#23'O9/'#171'`3'#31 - +#254#174'pD'#255#203#150']'#255#212#163'e'#255#213#164'e'#255#213#164'e'#255 - +#213#164'e'#255#213#164'e'#255#213#164'e'#255#213#164'e'#255#213#164'e'#255 - +#213#164'e'#255#213#164'e'#255#213#164'e'#255#213#164'e'#255#213#164'e'#255 - +#213#164'e'#255#213#164'e'#255#213#164'e'#255#213#164'e'#255#173#133'R'#255#3 - +#2#1#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#22#17#11#255#213#164'e'#255#213#164'e'#255#213#164'e'#255#213#164'e'#255 - +#213#164'f'#255#209#158'a'#255#187#128'O'#255'yD)'#254'W4$'#241'BBB'#169'DDD' - +'1@@@'#4#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2'FFF(W4$'#229'{E)'#255#191 - +#134'T'#255#207#157'c'#255#210#160'f'#255#210#160'f'#255#210#160'f'#255#210 - +#160'f'#255#210#160'f'#255#210#160'f'#255#210#160'f'#255#210#160'f'#255#152 - +'tJ'#255'[E,'#255'K9$'#255'{]<'#255#196#149'_'#255#210#160'f'#255#210#160'f' - +#255#210#160'f'#255#210#160'f'#255#210#160'f'#255#159'yM'#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'"'#26#17#255#210#160 - +'f'#255#210#160'f'#255#210#160'f'#255#210#160'f'#255#210#160'f'#255#209#160 - +'e'#255#199#146']'#255#156'_;'#255'\1'#28#254'FBA'#188'FFFXNNN'#13#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0'FFF'#11'F@=T\/'#28#253#151'\9'#255#200#147'_'#255#207 - +#156'e'#255#207#156'e'#255#207#156'e'#255#207#156'e'#255#207#156'e'#255#207 - +#156'e'#255#207#156'e'#255#178#135'W'#255'$'#27#17#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255'N:&'#255#207#156'e'#255#207#156'e'#255#207#156 - +'e'#255#207#156'e'#255#207#156'e'#255#3#2#1#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255'uX9'#255#207#156'e'#255#207#156'e'#255#207#156 - +'e'#255#207#156'e'#255#207#156'e'#255#207#157'e'#255#204#153'b'#255#180'wK' - +#255'a4'#30#254'M;3'#216'DDD'#132'==='#25#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'CC' - +'C'#19'N9.'#159'^2'#30#254#176'rI'#255#202#150'c'#255#203#153'e'#255#203#153 - +'f'#255#203#153'f'#255#203#153'f'#255#203#153'f'#255#203#153'f'#255#163'zQ' - +#255#1#1#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#29#22#15#255#203#153'f'#255#203#153'f'#255#203#153'f'#255#203#153'f' - +#255#14#10#7#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#4#3#2#255 - +#203#153'f'#255#203#153'f'#255#203#153'f'#255#203#153'f'#255#203#153'f'#255 - +#203#153'f'#255#203#153'f'#255#203#152'e'#255#190#134'X'#255'yC)'#254'V5&' - +#239'DDD'#165'FFF('#128#128#128#2#0#0#0#0#0#0#0#0#0#0#0#0'GGG'#25'V4%'#212's' - +'@&'#254#186#128'U'#255#199#148'd'#255#200#148'd'#255#200#148'd'#255#200#148 - +'d'#255#200#148'd'#255#200#148'd'#255#198#146'd'#255#12#9#6#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#138'fE'#255#200#148'd'#255#200#148'd'#255#200#148'd'#255#15#11#7#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#1#0#0#255'&'#28#19#255#145'kH'#255#200#148'd'#255 - +#200#148'd'#255#200#148'd'#255#200#148'd'#255#200#148'd'#255#200#148'd'#255 - +#200#148'd'#255#200#148'd'#255#194#141'^'#255#146'W6'#255'[0'#28#254'EBB'#178 - +'CCC9UUU'#6#0#0#0#0#0#0#0#0#0#0#0#0'JAA'#31'\1'#29#249#140'P2'#255#190#135'\' - +#255#196#143'c'#255#196#143'c'#255#196#143'c'#255#196#143'c'#255#196#143'c' - ,#255#196#143'c'#255'>-'#31#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'&'#28#19#255#196#143 - +'c'#255#196#143'c'#255#196#143'c'#255#25#18#12#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#2#2#1#255'*'#31#21#255'kM6' - +#255#187#137'_'#255#196#143'c'#255#196#143'c'#255#196#143'c'#255#196#143'c' - +#255#196#143'c'#255#196#143'c'#255#196#143'c'#255#196#143'c'#255#196#143'c' - +#255#196#143'c'#255#194#141'b'#255#170'kF'#255'\0'#28#254'L<5'#203'CCCH333' - +#10#0#0#0#0#0#0#0#0#0#0#0#1'K3+@[/'#27#255#160'`>'#255#190#136'_'#255#191#137 - +'`'#255#191#137'`'#255#191#137'`'#255#191#137'`'#255#191#137'`'#255#189#135 - +'`'#255#1#1#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#30#21#15#255#191#137'`'#255#191#137 - +'`'#255#191#137'`'#255'aF1'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#5#3#2#255#165'vS'#255#191#137'`'#255#191#137'`'#255#191 - +#137'`'#255#191#137'`'#255#191#137'`'#255#191#137'`'#255#191#137'`'#255#191 - +#137'`'#255#191#137'`'#255#191#137'`'#255#191#137'`'#255#191#137'`'#255#191 - +#137'`'#255#191#136'`'#255#178'vQ'#255'h7 '#253'Q8,'#220'DDDV;;;'#13#0#0#0#0 - +#0#0#0#0'UUU'#3'R4''^[/'#27#255#169'hF'#255#187#131']'#255#187#132'^'#255#187 - +#132'^'#255#187#132'^'#255#187#132'^'#255#187#132'^'#255'uS;'#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255'{W>'#255#187#132'^'#255#187#132'^'#255#187#132'^' - +#255#185#130'^'#255#19#14#10#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#1#1#1#255#158'oO'#255#187#132'^'#255#187#132'^'#255#187#132'^'#255#187#132 - +'^'#255#187#132'^'#255#187#132'^'#255#187#132'^'#255#187#132'^'#255#187#132 - +'^'#255#187#132'^'#255#187#132'^'#255#187#132'^'#255#187#132'^'#255#187#132 - +'^'#255#187#132'^'#255#181'{V'#255'r<"'#255'T6'''#228'BBBd@@@'#16#0#0#0#0#0#0 - +#0#0'UUU'#6'T5''}_2'#30#253#171'kI'#255#183'}Z'#255#183'~Z'#255#183'~Z'#255 - +#183'~Z'#255#183'~Z'#255#183'~Z'#255'. '#23#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#0#255'S9)' - +#255#183'~Z'#255#183'~Z'#255#183'~Z'#255#183'~Z'#255#183'~Z'#255#173'xV'#255 - +#31#21#15#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'bC0'#255#183'~Z'#255#183'~Z' - +#255#183'~Z'#255#183'~Z'#255#183'~Z'#255#183'~Z'#255#183'~Z'#255#183'~Z'#255 - +#183'~Z'#255#183'~Z'#255#183'~Z'#255#183'~Z'#255#183'~Z'#255#183'~Z'#255#183 - +'~Z'#255#183'~Z'#255#181'zV'#255'zA%'#255'X4$'#237'DDDqCCC'#19#0#0#0#0#0#0#0 - +#0'@@@'#8'U4%'#149'f5'#31#252#172'mK'#255#180'yX'#255#180'yX'#255#180'yX'#255 - +#180'yX'#255#180'yX'#255#180'yX'#255#6#4#3#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#19#13#9#255#153'gK'#255 - +#180'yX'#255#180'yX'#255#180'yX'#255#180'yX'#255#180'yX'#255#180'yX'#255#180 - +'yX'#255#180'yX'#255'{S<'#255'+'#29#21#255#7#4#3#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1#255'gF3'#255#180'yX'#255#180'yX' - +#255#180'yX'#255#139']D'#255'+'#29#21#255#8#6#4#255#8#6#4#255#16#11#8#255'W:' - +'*'#255#178'wV'#255#180'yX'#255#180'yX'#255#180'yX'#255#180'yX'#255#180'yX' - +#255#180'yX'#255#180'yX'#255#179'yV'#255#131'F+'#255'[3!'#245'CCCyIII'#21#0#0 - +#0#0#0#0#0#0'UUU'#6'X3"'#165'n:"'#253#172'pO'#255#177'wW'#255#177'wW'#255#177 - +'wW'#255#177'wW'#255#177'wW'#255#173'uU'#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#0#0#255'P6'''#255#177'wW'#255#177'w' - +'W'#255#177'wW'#255#177'wW'#255#177'wW'#255#177'wW'#255#177'wW'#255#177'wW' - +#255#177'wW'#255#177'wW'#255#177'wW'#255#177'wW'#255#177'wW'#255#158'jM'#255 - +'W;+'#255''''#26#19#255'"'#23#17#255'%'#25#18#255'W:+'#255#167'qS'#255#177'w' - +'W'#255#177'wW'#255#177'wW'#255'@+'#31#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#3#2#1#255#149'dI'#255#177'wW'#255#177'wW'#255#177 - +'wW'#255#177'wW'#255#177'wW'#255#177'wW'#255#177'vV'#255#139'M0'#255'[1'#30 - +#250'DDDpCCC'#19#0#0#0#0#0#0#0#0'UUU'#3'Y1'#31#182'v>%'#255#172'oQ'#255#175 - +'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255'}S?'#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#26#17#13#255#150'cK'#255#175 - +'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW' - +#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255 - +#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175 - +'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255'dC2'#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#9#6#4#255#169'pU' - +#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#174'sU'#255 - ,#145'S6'#255']0'#28#254'DBBd@@@'#16#0#0#0#0#0#0#0#0#0#0#0#0'Z0'#30#175'v=%' - +#255#171'nP'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255 - +'L2&'#255#0#0#0#255'/'#31#23#255'}R>'#255' '#21#16#255'!'#22#17#255#139'[E' - +#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255 - +#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174 - +'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV' - +#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255 - +'?*'#31#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255'6#'#27#255#174'rV'#255#174'rV'#255#174'rV'#255#174 - +'rV'#255#174'rV'#255#173'qT'#255#145'S7'#255'\1'#29#254'AAAV;;;'#13#0#0#0#0#0 - +#0#0#0#0#0#0#0'Z1'#30#150'n;$'#252#169'jM'#255#172'qU'#255#172'qU'#255#172'q' - +'U'#255#172'qU'#255#172'qU'#255#149'bI'#255'@* '#255#170'qU'#255#172'qU'#255 - +#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172 - +'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU' - +#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255 - +#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172 - +'qU'#255#172'qU'#255#172'qU'#255'G/#'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#24#16#12#255#172'qU' - +#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#171'oS'#255#138'N3'#255 - +'\2'#31#249'DDDG999'#9#0#0#0#0#0#0#0#0#0#0#0#0'X1!|g7#'#250#167'gI'#255#172 - +'pT'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU' - +#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255 - +#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255'P5('#255')'#27#20#255#27#18 - +#13#255'5#'#26#255'pJ8'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255 - +#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172 - +'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#162'jO'#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#9#6#4#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU'#255#172'qU' - +#255#170'mR'#255#131'I.'#255'[4"'#241'FFF7UUU'#6#0#0#0#0#0#0#0#0#0#0#0#0'Y1!' - +'`b5 '#251#164'cG'#255#172'pU'#255#173'rW'#255#173'rW'#255#173'rW'#255#173'r' - +'W'#255#173'rW'#255#173'rW'#255#173'rW'#255#173'rW'#255#173'rW'#255#173'rW' - +#255#173'rW'#255#173'rW'#255#173'rW'#255#173'rW'#255'}S?'#255#9#6#5#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#16#11#8#255#152'dM'#255#173'rW' - +#255#173'rW'#255#173'rW'#255#173'rW'#255#173'rW'#255#173'rW'#255#173'rW'#255 - +#173'rW'#255#173'rW'#255#173'rW'#255#173'rW'#255#173'rW'#255#173'rW'#255#173 - +'rW'#255'$'#24#18#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#4#3#2#255#173'rW'#255#173'rW'#255#173'rW'#255#173'r' - +'W'#255#173'rW'#255#170'mQ'#255'|C*'#255'W4$'#229'CCC&'#0#0#0#2#0#0#0#0#0#0#0 - +#0#0#0#0#0'X5!A^2'#31#254#162'bE'#255#173'rV'#255#175'tY'#255#175'tY'#255#175 - +'tY'#255#175'tY'#255#175'tY'#255#175'tY'#255#175'tY'#255#175'tY'#255#175'tY' - +#255#175'tY'#255#175'tY'#255#175'tY'#255#175'tY'#255'{Q?'#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#14#9#7#255 - +#173'tY'#255#175'tY'#255#175'tY'#255#175'tY'#255#175'tY'#255#175'tY'#255#175 - +'tY'#255#175'tY'#255#175'tY'#255#175'tY'#255#175'tY'#255#175'tY'#255#175'tY' - +#255#175'tY'#255#132'XC'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#21#14#11#255#175'tY'#255#175'tY'#255#175 - +'tY'#255#175'tY'#255#174'tY'#255#170'mP'#255't?('#254'U5&'#203'@@@'#24#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0'X0'#24' ^2'#31#255#156'\A'#255#175'v\'#255#177'y`' - +#255#177'y`'#255#177'y`'#255#177'y`'#255#177'y`'#255#177'y`'#255#177'y`'#255 - +#177'y`'#255#177'y`'#255#177'y`'#255#177'y`'#255#177'y`'#255#177'y`'#255#11#8 - +#6#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#136']J'#255#177'y`'#255#177'y`'#255'7%'#30#255#9#6#5#255 - +#13#9#7#255#16#11#9#255#26#18#14#255#139'_K'#255#177'y`'#255#177'y`'#255#177 - +'y`'#255#177'y`'#255#177'y`'#255#177'y`'#255#18#12#9#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'<)!'#255#177'y`'#255#177 - +'y`'#255#177'y`'#255#177'y`'#255#176'x^'#255#169'kP'#255'j<&'#253'U4%'#168';' - +';;'#13#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'^2'#29#234#136'O4'#255#176'x' - +'^'#255#180'~f'#255#180'~f'#255#180'~f'#255#180'~f'#255#180'~f'#255#180'~f' - +#255#180'~f'#255#180'~f'#255#180'~f'#255#180'~f'#255#180'~f'#255#180'~f'#255 - +#146'gS'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#139'bO'#255#170'x`'#255#20#14#11#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255']A4'#255#180'~f'#255 - +#180'~f'#255#180'~f'#255#180'~f'#255#180'~f'#255'qO@'#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#0#255#165't^'#255#180'~f'#255 - ,#180'~f'#255#180'~f'#255#180'~f'#255#178'zb'#255#162'dH'#255'_3'#31#254'R6)c' - +'@@@'#4#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#29#153'r?)'#252#174'sZ' - +#255#182#129'j'#255#183#131'l'#255#183#131'l'#255#183#131'l'#255#183#131'l' - +#255#183#131'l'#255#183#131'l'#255#183#131'l'#255#183#131'l'#255#183#131'l' - +#255#183#131'l'#255#183#131'l'#255'ZA5'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#5#3#3#255#179'j' - +#255'uTE'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255'{XI'#255#183#131'l'#255#183#131'l'#255#183#131'l'#255#183 - +#131'l'#255#183#131'l'#255#20#15#12#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255':)"'#255#183#131'l'#255#183#131'l'#255#183#131'l'#255#183#131 - +'l'#255#183#131'l'#255#179'|c'#255#140'R9'#255'\2'#31#248'@@6'#28#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#29'E`5 '#252#166'iO'#255#183#131'l' - +#255#186#136'q'#255#186#136'q'#255#186#136'q'#255#186#136'q'#255#186#136'q' - +#255#186#136'q'#255#186#136'q'#255#186#136'q'#255#186#136'q'#255#186#136'q' - +#255#186#136'q'#255'\D8'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'oQC'#255#186#136'q'#255'X@6'#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#12#9#7#255#186#136'q'#255#186#136'q'#255#186#136'q'#255#186#136'q'#255 - +#186#136'q'#255#148'lZ'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#169'|g'#255#186#136'q'#255#186#136'q'#255#186#136'q'#255#186#136'q'#255#185 - +#134'q'#255#177'y`'#255'tB+'#254'W2!'#194'III'#14#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0'@@'#0#4'^3'#30#245#144'W='#255#184#134'o'#255#189#141'x' - +#255#189#141'x'#255#189#141'x'#255#189#141'x'#255#189#141'x'#255#189#141'x' - +#255#189#141'x'#255#189#141'x'#255#189#141'x'#255#189#141'x'#255#189#141'x' - +#255'tWJ'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255'('#30#26#255#189#141'x'#255#189#141'x'#255'?/('#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#187#139'v'#255#189#141'x'#255#189#141'x'#255#189#141'x'#255#189#141 - +'x'#255#189#141'x'#255#172#128'm'#255'_G<'#255#1#0#0#255#0#0#0#255'-"'#28#255 - +#189#141'x'#255#189#141'x'#255#189#141'x'#255#189#141'x'#255#189#141'x'#255 - +#186#137's'#255#171'oU'#255'a6"'#253'T5$jUUU'#3#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0'\/'#27#170'vD,'#252#181#128'h'#255#190#144'{'#255 - +#192#146'~'#255#192#146'~'#255#192#146'~'#255#192#146'~'#255#192#146'~'#255 - +#192#146'~'#255#192#146'~'#255#192#146'~'#255#192#146'~'#255#192#146'~'#255 - +#186#142'z'#255#1#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#2#1#1#255#186#142'z'#255#192#146'~'#255#192#146'~'#255'=.('#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#192#146'~'#255#192#146'~'#255#192#146'~'#255#192#146'~'#255#192 - +#146'~'#255#192#146'~'#255#192#146'~'#255#192#146'~'#255#0#0#0#255#0#0#0#255 - +#167'n'#255#192#146'~'#255#192#146'~'#255#192#146'~'#255#192#146'~'#255#191 - +#145'}'#255#186#137's'#255#149'[A'#255'^1'#31#250'F:.'#22#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'\.'#28'H`5 '#252#164'jP'#255#190 - +#143'z'#255#194#151#131#255#194#151#131#255#194#151#131#255#194#151#131#255 - +#194#151#131#255#194#151#131#255#194#151#131#255#194#151#131#255#194#151#131 - +#255#194#151#131#255#194#151#131#255#27#21#18#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255' '#25#21#255#194#151#131#255#194#151#131 - +#255#194#151#131#255'7*%'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#12#9#8#255#194#151#131#255#194#151#131#255 - +#194#151#131#255#194#151#131#255#194#151#131#255#194#151#131#255#194#151#131 - +#255#190#149#129#255#0#0#0#255'0% '#255#194#151#131#255#194#151#131#255#194 - +#151#131#255#194#151#131#255#194#151#131#255#192#147''#255#180'~f'#255'q@+' - +#252'Z0'#29#180#0#0#0#2#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0']0'#28#199'{H2'#252#184#134'o'#255#196#153#134#255#197#156 - +#137#255#197#156#137#255#197#156#137#255#197#156#137#255#197#156#137#255#197 - +#156#137#255#197#156#137#255#197#156#137#255#197#156#137#255#197#156#137#255 - +'G81'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'WE=' - +#255#197#156#137#255#197#156#137#255#191#152#133#255#1#1#1#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#135'k^' - +#255#197#156#137#255#197#156#137#255#197#156#137#255#197#156#137#255#197#156 - +#137#255#197#156#137#255#197#156#137#255#197#156#137#255#16#12#11#255#174#138 - +'y'#255#197#156#137#255#197#156#137#255#197#156#137#255#197#156#137#255#197 - +#155#136#255#191#145'|'#255#151'_F'#255'^3'#31#253'T2!-'#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[0'#29'5_4 '#254 - +#161'hP'#255#195#152#132#255#200#161#143#255#200#161#144#255#200#161#144#255 - ,#200#161#144#255#200#161#144#255#200#161#144#255#200#161#144#255#200#161#144 - +#255#200#161#144#255#200#161#144#255'xaW'#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#1#1#1#255#171#138'{'#255#200#161#144#255#200#161#144#255 - +'f\'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255')!'#30#255#200#161#144#255#200#161#144#255#200#161#144#255#200 - +#161#144#255#200#161#144#255#200#161#144#255#200#161#144#255#200#161#144#255 - +#200#161#144#255#153'{n'#255#200#161#144#255#200#161#144#255#200#161#144#255 - +#200#161#144#255#200#161#144#255#198#156#138#255#181#128'h'#255'l>*'#252'Z1' - +#30#159#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0'^0'#27#173'uE/'#249#186#136'r'#255#201#162#145#255 - +#203#165#150#255#203#165#150#255#203#165#150#255#203#165#150#255#203#165#150 - +#255#203#165#150#255#203#165#150#255#203#165#150#255#203#165#150#255#163#132 - +'x'#255#0#0#0#255#0#0#0#255#26#21#19#255',$!'#255'-%!'#255#156'~s'#255#203 - +#165#150#255#203#165#150#255#203#165#150#255'@4/'#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#4#3#3#255#186#151#138#255#203 - +#165#150#255#203#165#150#255#203#165#150#255#203#165#150#255#203#165#150#255 - +#203#165#150#255#203#165#150#255#203#165#150#255#203#165#150#255#203#165#150 - +#255#203#165#150#255#203#165#150#255#203#165#150#255#203#165#150#255#203#164 - +#149#255#195#152#132#255#146'\D'#255'_3'#31#247'R3'#30#25#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +'Z-'#30'"^4 '#252#155'cK'#255#196#153#136#255#206#169#155#255#207#171#156#255 - +#207#171#156#255#207#171#156#255#207#171#156#255#207#171#156#255#207#171#156 - +#255#207#171#156#255#207#171#156#255#203#167#152#255#0#0#0#255#0#0#0#255#193 - +#159#145#255#207#171#156#255#207#171#156#255#207#171#156#255#207#171#156#255 - +#207#171#156#255#207#171#156#255'E94'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#15#12#11#255#182#150#137#255#207#171#156#255#207#171 - +#156#255#207#171#156#255#207#171#156#255#207#171#156#255#207#171#156#255#207 - +#171#156#255#207#171#156#255#207#171#156#255#207#171#156#255#207#171#156#255 - +#207#171#156#255#207#171#156#255#207#171#156#255#206#170#155#255#201#162#145 - +#255#178'}f'#255'h<('#250'[0'#28#129#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0']1' - +#29#130'f:&'#250#171'u^'#255#202#164#148#255#210#176#163#255#210#177#164#255 - +#210#177#164#255#210#177#164#255#210#177#164#255#210#177#164#255#210#177#164 - +#255#210#177#164#255#210#177#164#255#2#1#1#255#27#22#21#255#210#177#164#255 - +#210#177#164#255#210#177#164#255#210#177#164#255#210#177#164#255#210#177#164 - +#255#210#177#164#255#210#177#164#255'I=8'#255#6#5#4#255#0#0#0#255#0#0#0#255#0 - +#0#0#255'''!'#31#255#206#175#162#255#210#177#164#255#210#177#164#255#210#177 - +#164#255#210#177#164#255#210#177#164#255#210#177#164#255#210#177#164#255#210 - +#177#164#255#210#177#164#255#210#177#164#255#210#177#164#255#210#177#164#255 - +#210#177#164#255#210#177#164#255#210#176#163#255#206#170#155#255#188#139'w' - +#255'zI4'#252'^2'#29#222']/'#23#11#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#1'^1'#29#181'oA-'#249#182#132'n'#255#208#173#159#255#213#182#169#255#213#183 - +#170#255#213#183#170#255#213#183#170#255#213#183#170#255#213#183#170#255#213 - +#183#170#255#213#183#170#255#12#10#9#255#159#136''#255#213#183#170#255#213 - +#183#170#255#213#183#170#255#213#183#170#255#213#183#170#255#213#183#170#255 - +#213#183#170#255#213#183#170#255#213#183#170#255#139'xo'#255#0#0#0#255#0#0#0 - +#255'% '#30#255#213#183#170#255#213#183#170#255#213#183#170#255#213#183#170 - +#255#213#183#170#255#213#183#170#255#213#183#170#255#213#183#170#255#213#183 - +#170#255#213#183#170#255#213#183#170#255#213#183#170#255#213#183#170#255#213 - +#183#170#255#213#183#170#255#213#183#170#255#211#178#165#255#196#152#134#255 - +#136'T?'#254'`1 '#243'U+'#28'$'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0'U+'#21#12']2'#30#218'zK5'#251#192#146''#255#212#180#168#255#216#187 - +#176#255#217#188#177#255#217#188#177#255#217#188#177#255#217#188#177#255#217 - +#188#177#255#217#188#177#255'o`Z'#255#217#188#177#255#217#188#177#255#217#188 - +#177#255#217#188#177#255#217#188#177#255#217#188#177#255#217#188#177#255#217 - +#188#177#255#217#188#177#255#217#188#177#255#27#24#22#255#0#0#0#255#9#8#7#255 - +#209#180#171#255#217#188#177#255#217#188#177#255#217#188#177#255#217#188#177 - +#255#217#188#177#255#217#188#177#255#217#188#177#255#217#188#177#255#217#188 - +#177#255#217#188#177#255#217#188#177#255#217#188#177#255#217#188#177#255#217 - +#188#177#255#217#188#177#255#215#185#173#255#202#163#147#255#152'cM'#255'`5 ' - +#252'X.'#27'B'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - ,#0#0#0'].'#23'!_3 '#241#135'V@'#254#197#155#137#255#215#185#173#255#219#193 - +#183#255#220#194#184#255#220#194#184#255#220#194#184#255#220#194#184#255#220 - +#194#184#255#220#194#184#255#220#194#184#255#220#194#184#255#220#194#184#255 - +#220#194#184#255#220#194#184#255#220#194#184#255#220#194#184#255#220#194#184 - +#255#220#194#184#255#220#194#184#255#4#4#4#255#13#11#11#255#197#173#164#255 - +#220#194#184#255#220#194#184#255#220#194#184#255#220#194#184#255#220#194#184 - +#255#220#194#184#255#220#194#184#255#220#194#184#255#220#194#184#255#220#194 - +#184#255#220#194#184#255#220#194#184#255#220#194#184#255#220#194#184#255#220 - +#194#183#255#218#190#179#255#205#169#154#255#165'pY'#255'b8$'#252'[/'#27'h'#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0'Y1'#28'?_3!'#249#132'T?'#252#193#148#130#255#215#186#174#255#223#198 - +#189#255#223#200#191#255#223#200#191#255#223#200#191#255#223#200#191#255#223 - +#200#191#255#223#200#191#255#223#200#191#255#223#200#191#255#223#200#191#255 - +#223#200#191#255#223#200#191#255#223#200#191#255#223#200#191#255#223#200#191 - +#255#223#200#191#255#199#179#171#255#215#192#183#255#223#200#191#255#223#200 - +#191#255#223#200#191#255#223#200#191#255#223#200#191#255#223#200#191#255#223 - +#200#191#255#223#200#191#255#223#200#191#255#223#200#191#255#223#200#191#255 - +#223#200#191#255#223#200#191#255#223#200#191#255#223#199#190#255#219#192#183 - +#255#203#165#150#255#158'kS'#255'c9%'#251']0'#30#147#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +'Z-'#25'3_3'#30#234'wI5'#250#185#138'v'#255#215#186#174#255#225#203#194#255 - +#227#205#197#255#227#205#197#255#227#205#197#255#227#205#197#255#227#205#197 - +#255#227#205#197#255#227#205#197#255#227#205#197#255#227#205#197#255#227#205 - +#197#255#227#205#197#255#227#205#197#255#227#205#197#255#227#205#197#255#227 - +#205#197#255#227#205#197#255#227#205#197#255#227#205#197#255#227#205#197#255 - +#227#205#197#255#227#205#197#255#227#205#197#255#227#205#197#255#227#205#197 - +#255#227#205#197#255#227#205#197#255#227#205#197#255#227#205#197#255#227#205 - +#197#255#226#204#196#255#220#194#183#255#200#159#143#255#144'_H'#254'a6"'#253 - +'\1'#30'y'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'\3'#31#25'^2'#29#207'nB.' - +#249#175'~i'#255#213#182#169#255#225#202#193#255#230#209#203#255#230#211#205 - +#255#230#211#205#255#230#211#205#255#230#211#205#255#230#211#205#255#230#211 - +#205#255#230#211#205#255#230#211#205#255#230#211#205#255#230#211#205#255#230 - +#211#205#255#230#211#205#255#230#211#205#255#230#211#205#255#230#211#205#255 - +#230#211#205#255#230#211#205#255#230#211#205#255#230#211#205#255#230#211#205 - +#255#230#211#205#255#230#211#205#255#230#211#205#255#230#211#205#255#230#211 - +#205#255#230#210#204#255#227#205#198#255#219#191#181#255#193#149#131#255#131 - +'R='#251'_4 '#247'Z0'#26'O'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0'` '#8']0'#28#169'e:'''#250#148'aL'#255#196#153#136#255#220#193#183 - +#255#229#209#202#255#233#216#210#255#234#216#211#255#234#216#211#255#234#216 - +#211#255#234#216#211#255#234#216#211#255#234#216#211#255#234#216#211#255#234 - +#216#211#255#234#216#211#255#234#216#211#255#234#216#211#255#234#216#211#255 - +#234#216#211#255#234#216#211#255#234#216#211#255#234#216#211#255#234#216#211 - +#255#234#216#211#255#234#216#211#255#234#216#211#255#234#216#211#255#233#216 - +#211#255#231#212#206#255#224#200#192#255#207#171#156#255#168'va'#255'rE1'#249 - +'^3'#31#230'Y,'#28'.'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0'^1'#29'h`3'#31#237'nA/'#249#158'mW'#255#203#165#150 - +#255#224#201#192#255#232#213#208#255#235#219#215#255#237#222#218#255#237#222 - +#218#255#237#222#218#255#237#222#218#255#237#222#218#255#237#222#218#255#237 - +#222#218#255#237#222#218#255#237#222#218#255#237#222#218#255#237#222#218#255 - +#237#222#218#255#237#222#218#255#237#222#218#255#237#222#218#255#237#222#218 - +#255#237#222#218#255#236#220#216#255#233#216#211#255#228#206#200#255#213#182 - +#170#255#177#131'n'#255'}M:'#250'a6"'#253']1'#29#155'U1'#24#21#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0'].'#23#11']1'#29'~`5!'#246'tF5'#249#166'u`'#255#202#164#148#255#221 - ,#195#186#255#230#210#203#255#234#218#213#255#238#224#220#255#240#227#224#255 - +#240#227#225#255#240#227#225#255#240#227#225#255#240#227#225#255#240#227#225 - +#255#240#227#225#255#240#227#225#255#240#227#225#255#240#227#225#255#240#227 - +#225#255#239#225#222#255#236#220#215#255#232#213#207#255#225#203#194#255#210 - +#176#164#255#183#138'u'#255#133'UA'#252'c9&'#254'^2'#30#178'\.'#26''''#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'U1'#24#21']0'#30#147'`7"'#251 - +'jA-'#249#133'UA'#253#169'zf'#255#201#162#146#255#220#194#184#255#230#211#205 - +#255#233#215#210#255#234#217#212#255#236#220#215#255#237#222#218#255#238#223 - +#220#255#237#222#219#255#236#220#216#255#234#219#213#255#233#216#211#255#231 - +#213#206#255#225#203#194#255#209#174#160#255#181#136'u'#255#145'`L'#255'sG4' - +#249'c9&'#255'_2'#30#198'].'#28'7'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'U+'#28#18'\.'#27'^_1'#30#179'c7$' - +#248'h?,'#251'Q;'#251#148'dN'#255#163't_'#255#175#129'n'#255#186#143'~'#255 - +#198#157#141#255#204#167#151#255#201#161#145#255#191#149#132#255#179#135'u' - +#255#167'ye'#255#153'iT'#255#136'WC'#254'pE2'#249'c9&'#255'_3'#31#212'Z/'#27 - +'|].'#29','#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'U9'#28#9'\.'#26'N' - +'^0'#29#157'`4 '#203'c6#'#230'c7%'#249'd:('#255'f=*'#255'i?-'#252'g>+'#255'e' - +';('#255'd9&'#253'b7$'#237'a5!'#214'_1'#30#180'\/'#26'lX,'#26#29#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'`0 '#16'[1'#24'*].'#27'B]1'#29'4\3'#31#25'U' - +'U'#0#3#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#255#255 - +#255#248#15#255#255#255#255#255#254#0#0''#255#255#255#255#240#0#0#7#255#255 - +#255#255#192#0#0#1#255#255#255#255#0#0#0#0#255#255#255#252#0#0#0#0'?'#255#255 - +#248#0#0#0#0#31#255#255#240#0#0#0#0#7#255#255#192#0#0#0#0#3#255#255#128#0#0#0 - +#0#1#255#255#128#0#0#0#0#0#255#255#0#0#0#0#0#0''#254#0#0#0#0#0#0''#252#0#0 - +#0#0#0#0'?'#252#0#0#0#0#0#0#31#248#0#0#0#0#0#0#31#248#0#0#0#0#0#0#15#240#0#0 - +#0#0#0#0#15#240#0#0#0#0#0#0#7#224#0#0#0#0#0#0#7#224#0#0#0#0#0#0#7#224#0#0#0#0 - +#0#0#3#192#0#0#0#0#0#0#3#192#0#0#0#0#0#0#3#192#0#0#0#0#0#0#1#192#0#0#0#0#0#0 - +#1#192#0#0#0#0#0#0#1#128#0#0#0#0#0#0#1#128#0#0#0#0#0#0#1#128#0#0#0#0#0#0#1 - +#128#0#0#0#0#0#0#1#128#0#0#0#0#0#0#1#128#0#0#0#0#0#0#1#192#0#0#0#0#0#0#1#192 - +#0#0#0#0#0#0#1#192#0#0#0#0#0#0#1#192#0#0#0#0#0#0#1#192#0#0#0#0#0#0#3#192#0#0 - +#0#0#0#0#3#224#0#0#0#0#0#0#3#224#0#0#0#0#0#0#7#224#0#0#0#0#0#0#7#224#0#0#0#0 - +#0#0#7#240#0#0#0#0#0#0#15#240#0#0#0#0#0#0#15#248#0#0#0#0#0#0#31#248#0#0#0#0#0 - +#0#31#252#0#0#0#0#0#0'?'#252#0#0#0#0#0#0''#254#0#0#0#0#0#0''#254#0#0#0#0#0 - +#0#255#255#0#0#0#0#0#1#255#255#128#0#0#0#0#3#255#255#192#0#0#0#0#7#255#255 - +#224#0#0#0#0#15#255#255#240#0#0#0#0#31#255#255#248#0#0#0#0'?'#255#255#254#0#0 - +#0#0''#255#255#255#0#0#0#1#255#255#255#255#192#0#0#7#255#255#255#255#240#0#0 - +#31#255#255#255#255#254#0#0#255#255#255#255#255#255#248#31#255#255#255#255 - +#255#255#255#255#255#255#255'('#0#0#0'0'#0#0#0'`'#0#0#0#1#0' '#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - ,#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'UUU'#3'333'#5'UUU'#6'III'#7'@@@'#8'@@@' - +#8'III'#7'UUU'#6'333'#5'UUU'#3#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#1'@@@'#4'@@@'#8'@@@'#16'DDD1BBBMCCC_FCCrDBB'#129'FDD'#133'CCCvCCCcDDDRD' - +'DD<FFF'#22'333'#10'333'#5#0#0#0#2#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'III'#7'FFF'#22'CCCEDAA|CCB'#173'FEC' - +#194'IGC'#198'KE?'#204'QD:'#211'TC4'#218'SD6'#216'OD<'#209'JD@'#201'HGD'#197 - +'FEC'#195'DDC'#184'DDD'#139'EEEUDDD"999'#9#0#0#0#2#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#1'+++'#6'FFF'#22'BBBaDDC'#175'JEA'#198'TB5'#218'a>('#240 - +'l@!'#252'qE!'#253'yJ$'#252#129'R&'#252#138'X)'#253#134'T('#253'}N%'#252'uG#' - +#252'oC"'#253'h=#'#248'Z>.'#230'OC;'#211'FEC'#198'DDC'#187'BBB|CCC&999'#9#0#0 - +#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#2'<<<'#17'CCCWEED'#170'OB9'#211'd=&'#244'qE"'#253 - +#137'V)'#253#164'h.'#255#186'y3'#255#194#130'5'#255#198#135'6'#255#203#140'7' - +#255#207#145'8'#255#205#143'7'#255#201#137'7'#255#196#132'6'#255#192'5'#255 - +#177'r1'#255#152'_+'#254'{L%'#252'k?"'#251'[>-'#233'IEA'#203'EDD'#184'DDDq<<' - +'<'#30'@@@'#4#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0'@@@'#4'BBB#EDD'#146'KB<'#205'a='''#242'yK&'#252#166'i/'#255#192 - +#128'6'#255#205#143'9'#255#217#159'<'#255#226#169'>'#255#230#173'>'#255#232 - +#176'>'#255#235#180'?'#255#237#182'@'#255#236#181'?'#255#234#178'>'#255#231 - +#175'>'#255#228#171'>'#255#223#165'<'#255#211#150':'#255#199#136'8'#255#182 - +'v5'#255#146'\,'#253'k@"'#252'W?1'#227'GFE'#199'CCC'#171'???=@@@'#8#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'III'#7'DDD8ECB'#170'[>,' - +#234'sF$'#252#160'e0'#255#196#133';'#255#216#158'@'#255#227#170'A'#255#234 - +#180'C'#255#241#189'D'#255#245#193'E'#255#246#195'F'#255#247#195'F'#255#247 - +#196'E'#255#248#197'F'#255#248#197'E'#255#247#195'F'#255#247#194'F'#255#246 - +#194'E'#255#244#192'E'#255#238#184'C'#255#230#176'C'#255#223#166'A'#255#207 - +#147'='#255#182'w7'#255#138'V+'#253'h>#'#250'NA9'#215'CCC'#186'@@@[;;;'#13#0 - +#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'@@@'#8'EEENGC?'#189'b;%'#245#139 - +'W-'#253#190';'#255#212#153'B'#255#227#172'F'#255#239#186'I'#255#242#191'J' - +#255#244#193'K'#255#246#195'K'#255#247#196'K'#255#247#196'K'#255#247#196'L' - +#255#247#196'L'#255#247#196'L'#255#247#196'L'#255#247#196'L'#255#247#196'K' - +#255#247#196'K'#255#246#196'K'#255#245#195'J'#255#243#193'J'#255#242#190'J' - +#255#234#180'H'#255#221#165'E'#255#202#142'@'#255#173'o5'#255'oC$'#252'T?3' - +#225'CCC'#193'AAAy@@@'#16#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'+++'#6'DDD@K@<'#198'g<"' - +#250#154'a2'#254#200#141'B'#255#222#168'J'#255#234#183'N'#255#239#189'P'#255 - +#242#192'P'#255#242#193'P'#255#243#193'P'#255#243#193'P'#255#243#193'P'#255 - +#243#193'P'#255#243#193'P'#255#243#193'P'#255#243#193'P'#255#243#193'P'#255 - +#243#193'P'#255#243#193'P'#255#243#193'P'#255#243#193'P'#255#243#193'P'#255 - +#242#193'P'#255#242#193'P'#255#241#191'P'#255#238#187'N'#255#229#176'L'#255 - +#214#158'G'#255#183'y;'#255'yJ)'#252'[=,'#236'CCB'#195'EEEoFFF'#11#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0'UUU'#3'CCC.J?;'#190'g=#'#252#167'k7'#255#205#147'H'#255#226#173'P'#255#235 - +#186'T'#255#238#189'T'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239 - +#190'U'#255#239#190'U'#255#239#190'U'#255#191#152'D'#255'A4'#23#255'>1'#22 - +#255'D6'#24#255'H9'#26#255'RA'#29#255#146's4'#255#228#182'Q'#255#239#190'U' - +#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#239#190'U'#255#238#190'U' - +#255#237#187'T'#255#232#182'S'#255#217#162'M'#255#191#129'B'#255#132'R-'#253 - +'[9('#240'DDD'#191'CCCW@@@'#8#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'>>>'#29'F@='#169'd9#'#250#165'i8'#255#208 - +#151'L'#255#226#174'U'#255#233#184'Y'#255#235#186'Y'#255#235#187'Y'#255#235 - +#187'Y'#255#235#187'Y'#255#235#187'Y'#255#235#187'Y'#255#235#187'Y'#255'<0' - +#23#255#1#1#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#0#255 - ,#0#0#0#255#6#5#2#255'*!'#16#255'w^-'#255#227#181'W'#255#235#187'Y'#255#235 - +#187'Y'#255#235#186'Y'#255#234#186'Y'#255#231#181'X'#255#218#164'Q'#255#194 - +#133'E'#255'{J*'#252'U=/'#230'CCC'#186'EEE?@@@'#4#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'333'#10'DAAw_9%'#244#150'^4'#254#204#148 - +'N'#255#225#175'Y'#255#230#181'\'#255#231#183'\'#255#231#183'\'#255#231#183 - +'\'#255#231#183'\'#255#231#183'\'#255#231#183'\'#255#231#183'\'#255'hR*'#255 - +#1#1#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#0#255#1#1#0#255'*!'#17#255#164#130'B' - +#255#231#183'\'#255#231#183'\'#255#231#182']'#255#228#179'['#255#217#165'V' - +#255#185'|C'#255'nA'''#252'N>5'#219'CCC'#163'@@@'#20#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#128#128#128#2'@@@,W8*'#227#132'P/'#253#196 - +#139'M'#255#220#170'['#255#226#179'_'#255#227#179'_'#255#227#179'_'#255#227 - +#179'_'#255#227#179'_'#255#227#179'_'#255#227#179'_'#255#227#179'_'#255#227 - +#179'_'#255#4#3#2#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#1#1#0#255#7#6#3#255#178#141'J'#255#227#179'_'#255#227#179'_'#255#225 - +#176'^'#255#211#158'U'#255#174'q?'#255'c9"'#252'GA>'#203'BBB]III'#7#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'FFF'#11'H>:'#150'e:#'#252#186'~H'#255 - +#214#164'\'#255#222#174'a'#255#223#175'b'#255#223#175'b'#255#223#175'b'#255 - +#223#175'b'#255#223#175'b'#255#223#175'b'#255#223#175'b'#255#223#175'b'#255 - +#188#148'S'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1#255#154'yC'#255#223#175'b'#255#223 - +#175'a'#255#220#171'`'#255#204#150'U'#255#145'Y5'#254'Y8('#238'CCC'#175'FFF' - +#29#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'UUU'#3'AAA3Z7'''#236#148'\6'#255 - +#207#154'Z'#255#218#170'b'#255#219#171'd'#255#219#171'd'#255#219#171'd'#255 - +#219#171'd'#255#219#171'd'#255#219#171'd'#255#219#171'd'#255#219#171'd'#255 - +#219#171'd'#255#211#165'`'#255#6#5#3#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#2#2#1#255#219#171 - +'d'#255#219#171'd'#255#219#171'd'#255#215#166'`'#255#189#129'M'#255'h=$'#252 - +'H>;'#207'EEEhIII'#7#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'III'#7'K<5'#137'h;$'#252 - +#188#130'O'#255#213#164'b'#255#215#167'd'#255#215#167'd'#255#215#167'd'#255 - +#215#167'd'#255#215#167'd'#255#215#167'd'#255#215#167'd'#255#215#167'd'#255 - +#215#167'd'#255#215#167'd'#255#215#167'd'#255#168#130'N'#255#1#1#1#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#200#155']'#255#215#167'd'#255#215#167'd'#255#215#166'd'#255 - +#205#153']'#255#153'`:'#255'[6$'#243'CCC'#164';;;'#13#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0'777'#14'Z5%'#229#150'[8'#255#203#150'^'#255#211#162'e'#255#211#162 - +'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211#162 - +'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211#162'e'#255#211#162 - +'e'#255#211#162'e'#255#142'mD'#255#1#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#0#255#145'pF'#255#211#162'e' - +#255#211#162'e'#255#211#162'e'#255#210#159'd'#255#189#131'R'#255'f9#'#252'I@' - +';'#202'===.'#128#128#128#2#0#0#0#0#0#0#0#0#128#128#128#2'D??4]3'#30#252#181 - +'yM'#255#206#155'd'#255#207#157'f'#255#207#157'f'#255#207#157'f'#255#207#157 - +'f'#255#205#155'f'#255'S?('#255#1#1#1#255#1#0#0#255#3#2#1#255#15#11#7#255#169 - +#128'S'#255#207#157'f'#255#207#157'f'#255#207#157'f'#255#16#12#8#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#0#0 - +#255#186#141'\'#255#207#157'f'#255#207#157'f'#255#207#157'f'#255#207#157'e' - +#255#201#148'`'#255#135'O0'#254'R9-'#226'BBBeIII'#7#0#0#0#0#0#0#0#0'UUU'#6'O' - +'8/'#141'o>'''#251#193#139'['#255#203#151'e'#255#203#152'e'#255#203#152'e' - +#255#203#152'e'#255#203#152'e'#255#23#17#11#255#1#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#1#1#1#255#153'rL'#255#203#152'e'#255#203#152'e'#255',!' - +#22#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#0#0 - +#255' '#24#16#255#203#152'e'#255#203#152'e'#255#203#152'e'#255#203#152'e'#255 - +#203#152'e'#255#202#150'd'#255#165'jC'#255'\3!'#247'DDD'#151'MMM'#10#0#0#0#0 - +#0#0#0#0'@@@'#8'Z6%'#206#142'T5'#255#195#142'a'#255#198#145'c'#255#198#145'c' - +#255#198#145'c'#255#198#145'c'#255'W@,'#255#1#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255'%'#27#19#255#198#145'c'#255#198#145'c' - ,#255'U>+'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#0#0#255#1#1#1#255#27#20#13 - +#255'vW;'#255#198#145'c'#255#198#145'c'#255#198#145'c'#255#198#145'c'#255#198 - +#145'c'#255#198#145'c'#255#198#145'c'#255#184'}S'#255'`4 '#252'FA>'#183'@@@' - +#12#0#0#0#0#0#0#0#0'999'#9']2'#30#243#166'gD'#255#192#138'`'#255#192#139'a' - +#255#192#139'a'#255#192#139'a'#255#190#137'a'#255#1#1#1#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#8#6#4#255#192#139'a' - +#255#192#139'a'#255#129']B'#255#1#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#0#0#255'Q;)'#255#192 - +#139'a'#255#192#139'a'#255#192#139'a'#255#192#139'a'#255#192#139'a'#255#192 - +#139'a'#255#192#139'a'#255#192#139'a'#255#192#139'a'#255#192#139'a'#255#188 - +#132'['#255'u@'''#252'M;3'#212'999'#18#0#0#0#0#0#0#0#0'FFF'#11'^1'#29#250#174 - +'oK'#255#186#131'\'#255#186#131'\'#255#186#131'\'#255#186#131'\'#255'vS:'#255 - +#1#1#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#0#0 - +#255'S:)'#255#186#131'\'#255#186#131'\'#255#184#129'\'#255'('#28#20#255#1#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255'Q9('#255#186#131'\'#255#186#131'\'#255#186#131'\'#255#186#131'\'#255 - +#186#131'\'#255#186#131'\'#255#186#131'\'#255#186#131'\'#255#186#131'\'#255 - +#186#131'\'#255#186#131'\'#255#186#130'['#255#131'I-'#255'Q9-'#221'FFF!'#0#0 - +#0#1#0#0#0#0'M33'#20'_2'#30#251#173'pN'#255#180'{X'#255#180'{X'#255#180'{X' - +#255#180'{X'#255'-'#30#22#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#2#1#1#255'dE1'#255#180'{X'#255#180'{X'#255#180'{X'#255#180'{X' - +#255#180'{X'#255'O6&'#255#2#1#1#255#1#1#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255'G0#'#255#180'{X'#255#180'{X'#255#178'{X'#255'hH3'#255'U:)' - +#255'mK5'#255#180'{X'#255#180'{X'#255#180'{X'#255#180'{X'#255#180'{X'#255#180 - +'{X'#255#180'{X'#255#139'P3'#255'U7)'#231'BBB2UUU'#3#0#0#0#0'T1&,c5 '#249#174 - +'rQ'#255#178'wW'#255#178'wW'#255#178'wW'#255#178'wW'#255#8#5#4#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#1#0#0#255#27#18#13#255#164'nQ'#255#178'wW'#255 - +#178'wW'#255#178'wW'#255#178'wW'#255#178'wW'#255#178'wW'#255#178'wW'#255#178 - +'wW'#255#142'_F'#255'H0#'#255#20#13#10#255#25#16#12#255'9'''#28#255'lI5'#255 - +#178'wW'#255#178'wW'#255'kG4'#255#1#1#1#255#1#1#0#255#1#0#0#255#1#0#0#255#3#2 - +#1#255'zQ<'#255#178'wW'#255#178'wW'#255#178'wW'#255#178'wW'#255#178'wW'#255 - +#149'W9'#255'Z5%'#238'DDD-'#128#128#128#2#0#0#0#0'Y3 Bj8#'#248#173'qS'#255 - +#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#1#0#0#255#1#1#1#255#0#0#0 - +#255#0#0#0#255#5#3#3#255'cB2'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW' - +#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255 - +#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175 - +'tW'#255#173'rU'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#2#1#1#255#152'eL'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255 - +#155'[>'#255'\4"'#244'@@@'#28#0#0#0#1#0#0#0#0'X0!3g7"'#246#172'oR'#255#174'r' - +'V'#255#174'rV'#255#174'rV'#255#166'lR'#255#7#5#3#255#131'VA'#255#155'fM'#255 - +'oI7'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV' - +#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255 - +#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174'rV'#255#174 - +'rV'#255#153'dL'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255'<'''#29#255#174'rV'#255#174'rV'#255#174'rV'#255#173'qU'#255 - +#151'Y='#255']6%'#239'333'#15#0#0#0#0#0#0#0#0'O1'''#25'b3'#31#247#168'jN'#255 - +#172'oU'#255#172'oU'#255#172'oU'#255#172'oU'#255#172'oU'#255#172'oU'#255#172 - +'oU'#255#172'oU'#255#172'oU'#255#172'oU'#255#172'oU'#255#172'oU'#255#136'XD' - +#255'U7*'#255'^=/'#255'kE5'#255#170'oU'#255#172'oU'#255#172'oU'#255#172'oU' - +#255#172'oU'#255#172'oU'#255#172'oU'#255#172'oU'#255#172'oU'#255#172'oU'#255 - +#172'oU'#255#172'oU'#255#5#3#2#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255'!'#21#16#255#172'oU'#255#172'oU'#255#172'oU'#255#172'oT' - +#255#143'R7'#255'Y6('#225'@@@'#12#0#0#0#0#0#0#0#0'333'#5'_2'#31#248#167'hL' - +#255#172'qW'#255#172'qX'#255#172'qX'#255#172'qX'#255#172'qX'#255#172'qX'#255 - +#172'qX'#255#172'qX'#255#172'qX'#255#172'qX'#255#172'qX'#255':&'#30#255#1#0#0 - +#255#1#1#0#255#1#1#1#255#1#1#0#255#4#2#2#255#133'WD'#255#172'qX'#255#172'qX' - +#255#172'qX'#255#172'qX'#255#172'qX'#255#172'qX'#255#172'qX'#255#172'qX'#255 - +#172'qX'#255#172'qX'#255'E.$'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#30#20#15#255#172'qX'#255#172'qX'#255#172'qX'#255#172'pV'#255 - +#134'K2'#255'V8)'#199'999'#9#0#0#0#0#0#0#0#0#0#0#0#1'_3'#30#239#164'fJ'#255 - +#175'w]'#255#175'w^'#255#175'w^'#255#175'w^'#255#175'w^'#255#175'w^'#255#175 - +'w^'#255#175'w^'#255#175'w^'#255#175'w^'#255'hF7'#255#1#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#7#5#4#255#175'w^'#255#175'w^'#255'{TB' - ,#255'X;/'#255']?2'#255'VD'#255#175'w^'#255#175'w^'#255#175'w^'#255#175'w^' - +#255#165'qY'#255#1#1#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1#255 - +'fF7'#255#175'w^'#255#175'w^'#255#175'w^'#255#175'v\'#255'{D-'#253'S5('#154 - +'333'#5#0#0#0#0#0#0#0#0#0#0#0#0'a3'#30#200#149'Z?'#255#179'|c'#255#179'}e' - +#255#179'}e'#255#179'}e'#255#179'}e'#255#179'}e'#255#179'}e'#255#179'}e'#255 - +#179'}e'#255#179'}e'#255#19#13#11#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255#2#1#1#255#179'}e'#255'E0'''#255#1#0#0#255#1#1#0#255#1#1 - +#0#255#0#0#0#255'R:/'#255#179'}e'#255#179'}e'#255#179'}e'#255#179'}e'#255'.!' - +#26#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1#255#177'}e'#255#179'}e' - +#255#179'}e'#255#179'}e'#255#175'v\'#255'g8$'#251'W7*O'#0#0#0#1#0#0#0#0#0#0#0 - +#0#0#0#0#0'[0'#29'lyD-'#249#181#128'i'#255#183#131'l'#255#183#131'l'#255#183 - +#131'l'#255#183#131'l'#255#183#131'l'#255#183#131'l'#255#183#131'l'#255#183 - +#131'l'#255#183#131'l'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#0#0#0#255#0#0#0#255'"'#25#20#255#183#131'l'#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255'B/'''#255#183#131'l'#255#183#131'l'#255 - +#183#131'l'#255#169'yd'#255#1#1#1#255#0#0#0#255#0#0#0#255#0#0#0#255'#'#25#21 - +#255#183#131'l'#255#183#131'l'#255#183#131'l'#255#183#131'l'#255#167'lQ'#255 - +'^3'#31#248'@@@'#12#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'Y1'#30#26'b5 '#244#178'z' - +'c'#255#187#138't'#255#187#138'u'#255#187#138'u'#255#187#138'u'#255#187#138 - +'u'#255#187#138'u'#255#187#138'u'#255#187#138'u'#255#187#138'u'#255#2#2#1#255 - +#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#0#255#170'~j'#255#177 - +#130'o'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#4#3#3 - +#255#187#138'u'#255#187#138'u'#255#187#138'u'#255#187#138'u'#255'jNB'#255#11 - +#8#7#255#0#0#0#255#0#0#0#255#152'p`'#255#187#138'u'#255#187#138'u'#255#187 - +#138'u'#255#186#136'r'#255#144'W?'#255']6#'#193'333'#5#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0'`4'#31#217#160'gN'#255#190#143'{'#255#191#145'}'#255#191 - +#145'}'#255#191#145'}'#255#191#145'}'#255#191#145'}'#255#191#145'}'#255#191 - +#145'}'#255#191#145'}'#255#19#14#12#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255'7*$'#255#191#145'}'#255#164'|k'#255#1#1#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#2#2#1#255#191#145'}'#255#191#145'}'#255 - +#191#145'}'#255#191#145'}'#255#191#145'}'#255#191#145'}'#255#0#0#0#255#30#23 - +#20#255#191#145'}'#255#191#145'}'#255#191#145'}'#255#191#145'}'#255#187#138 - +'t'#255'p>('#249'Y2#U'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27 - +'huB+'#244#190#142'z'#255#195#152#132#255#195#152#132#255#195#152#132#255#195 - +#152#132#255#195#152#132#255#195#152#132#255#195#152#132#255#195#152#132#255 - +'A3,'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#0#0#255#183#142'|'#255 - +#195#152#132#255#138'k]'#255#1#1#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#9#7#6#255#195#152#132#255#195#152#132#255#195#152#132#255#195 - +#152#132#255#195#152#132#255#195#152#132#255#1#1#1#255#160'}m'#255#195#152 - +#132#255#195#152#132#255#195#152#132#255#194#150#131#255#167'pW'#255'`4 '#240 - +'M33'#10#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'@@'#0#4'`3'#30#229 - +#164'nU'#255#197#156#137#255#199#159#141#255#199#159#141#255#199#159#141#255 - +#199#159#141#255#199#159#141#255#199#159#141#255#199#159#141#255'x`U'#255#1#1 - +#1#255#0#0#0#255#0#0#0#255#0#0#0#255#18#14#13#255#199#159#141#255#199#159#141 - +#255' '#26#23#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#0#255 - +#139'oc'#255#199#159#141#255#199#159#141#255#199#159#141#255#199#159#141#255 - +#199#159#141#255#199#159#141#255'^KC'#255#199#159#141#255#199#159#141#255#199 - +#159#141#255#199#159#141#255#192#147''#255'rB-'#249'\1 k'#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'\.'#28'Sn>('#243#194#149#131 - +#255#202#165#148#255#203#165#149#255#203#165#149#255#203#165#149#255#203#165 - +#149#255#203#165#149#255#203#165#149#255#169#138'|'#255#0#0#0#255#1#1#1#255 - +#16#13#11#255#23#19#17#255#171#139'~'#255#203#165#149#255#203#165#149#255#3#3 - +#2#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#0#0#255'*"'#31#255#203#165 - +#149#255#203#165#149#255#203#165#149#255#203#165#149#255#203#165#149#255#203 - +#165#149#255#203#165#149#255#203#165#149#255#203#165#149#255#203#165#149#255 - +#203#165#149#255#201#162#145#255#163'mU'#255'_3'#31#227'U'#0#0#3#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'a3'#30#213#153 - +'eM'#254#204#167#151#255#207#172#158#255#207#172#158#255#207#172#158#255#207 - +#172#158#255#207#172#158#255#207#172#158#255#205#170#156#255#0#0#0#255'<1.' - +#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172 - +#158#255#20#16#15#255#1#0#0#255#0#0#0#255#0#0#0#255#1#0#0#255'?40'#255#207 - +#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255 - +#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158#255#207#172#158 - +#255#207#172#158#255#207#171#156#255#192#147''#255'k<'''#244'Z.'#29'L'#0#0#0 - ,#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'^' - +'/'#27'&b5!'#240#174'{f'#255#209#176#162#255#212#180#167#255#212#180#167#255 - +#212#180#167#255#212#180#167#255#212#180#167#255#212#180#167#255#6#5#5#255 - +#179#151#140#255#212#180#167#255#212#180#167#255#212#180#167#255#212#180#167 - +#255#212#180#167#255#212#180#167#255'gXR'#255#0#0#0#255#0#0#0#255'gXR'#255 - +#212#180#167#255#212#180#167#255#212#180#167#255#212#180#167#255#212#180#167 - +#255#212#180#167#255#212#180#167#255#212#180#167#255#212#180#167#255#212#180 - +#167#255#212#180#167#255#211#179#166#255#202#162#146#255'M7'#248'`2'#31#169 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0'[/'#26'Fg:#'#242#190#145''#255#214#184#172#255#216#187 - +#176#255#216#187#176#255#216#187#176#255#216#187#176#255#216#187#176#255'k]X' - +#255#216#187#176#255#216#187#176#255#216#187#176#255#216#187#176#255#216#187 - +#176#255#216#187#176#255#216#187#176#255'E<8'#255#0#0#0#255'>63'#255#216#187 - +#176#255#216#187#176#255#216#187#176#255#216#187#176#255#216#187#176#255#216 - +#187#176#255#216#187#176#255#216#187#176#255#216#187#176#255#216#187#176#255 - +#216#187#176#255#216#187#175#255#210#176#163#255#147'_H'#252'`4'#31#210'U++' - +#6#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'\0'#29'trB-'#242#198#157#140#255#218#191#180 - +#255#221#195#185#255#221#195#185#255#221#195#185#255#221#195#185#255#221#195 - +#185#255#221#195#185#255#221#195#185#255#221#195#185#255#221#195#185#255#221 - +#195#185#255#221#195#185#255#221#195#185#255',''%'#255'920'#255#221#195#185 - +#255#221#195#185#255#221#195#185#255#221#195#185#255#221#195#185#255#221#195 - +#185#255#221#195#185#255#221#195#185#255#221#195#185#255#221#195#185#255#221 - +#195#185#255#220#194#184#255#213#183#170#255#162'oW'#255'b5 '#232'].'#23#22#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'_1'#30#138'l>)'#242#187#143'|'#255 - +#221#196#186#255#225#202#193#255#225#203#194#255#225#203#194#255#225#203#194 - +#255#225#203#194#255#225#203#194#255#225#203#194#255#225#203#194#255#225#203 - +#194#255#225#203#194#255#225#203#194#255#225#203#194#255#225#203#194#255#225 - +#203#194#255#225#203#194#255#225#203#194#255#225#203#194#255#225#203#194#255 - +#225#203#194#255#225#203#194#255#225#203#194#255#225#203#194#255#225#203#194 - +#255#224#201#192#255#212#180#168#255#149'aK'#252'a5!'#230']2'#25')'#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#28'\d9$'#241#172'|h' - +#255#222#197#187#255#228#206#200#255#230#209#203#255#230#209#203#255#230#209 - +#203#255#230#209#203#255#230#209#203#255#230#209#203#255#230#209#203#255#230 - +#209#203#255#230#209#203#255#230#209#203#255#230#209#203#255#230#209#203#255 - +#230#209#203#255#230#209#203#255#230#209#203#255#230#209#203#255#230#209#203 - +#255#230#209#203#255#230#209#203#255#229#209#202#255#226#204#197#255#208#173 - +#159#255#131'R;'#245'a3'#31#207'Y3'#26#20#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'].'#28'7a5!'#236#137'WB'#248#201#163 - +#148#255#229#210#202#255#233#215#210#255#234#217#212#255#234#217#212#255#234 - +#217#212#255#234#217#212#255#234#217#212#255#234#217#212#255#234#217#212#255 - +#234#217#212#255#234#217#212#255#234#217#212#255#234#217#212#255#234#217#212 - +#255#234#217#212#255#234#217#212#255#234#217#212#255#234#216#211#255#231#212 - +#206#255#221#195#185#255#174#128'm'#255'm>*'#242'`1'#29#163'f33'#5#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0'b'''#20#13'`2'#29#140'd8$'#241#151'hR'#251#211#179#167#255#233#215#210 - +#255#236#220#215#255#237#222#219#255#238#225#221#255#238#225#221#255#238#225 - +#221#255#238#225#221#255#238#225#221#255#238#225#221#255#238#225#221#255#238 - +#225#221#255#238#225#221#255#238#223#220#255#236#221#217#255#234#219#213#255 - +#227#205#198#255#188#146#128#255'wH2'#242'b5 '#219'].'#27'B'#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'X1'#29#26'a3'#30#168'f:$'#242#134'T?'#246 - +#179#136'v'#255#214#183#172#255#235#218#214#255#238#225#221#255#239#226#223 - +#255#240#227#224#255#240#227#225#255#240#227#225#255#239#226#223#255#239#225 - +#222#255#238#224#220#255#226#204#196#255#199#161#145#255#158'o['#254'rD-'#239 - +'b4"'#232'^2'#30'\'#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0'`+ '#24']/'#30'fd6#'#197'c8#'#243'vE0'#240#140'ZF'#247 - ,#156'mY'#255#171'm'#255#184#143''#255#178#136'v'#255#164'wd'#255#148'dP' - +#252#130'Q;'#243'l<('#239'b6"'#234'b5!'#151'Z-'#29'>UU'#0#3#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0'].'#23#11'Z/'#29'G^4 fa4!'#130'd6!'#166'd7#'#194'd7"'#182 - +'c6"'#150'_3'#31's\0'#29'YY/'#30'+'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#255#255#224#3#255#255#255#255#255#254 - +#0#0''#255#255#255#255#248#0#0#31#255#255#255#255#224#0#0#7#255#255#255#255 - +#192#0#0#3#255#255#255#255#128#0#0#1#255#255#255#255#0#0#0#0''#255#255#254#0 - +#0#0#0''#255#255#252#0#0#0#0'?'#255#255#248#0#0#0#0#31#255#255#240#0#0#0#0 - +#15#255#255#240#0#0#0#0#15#255#255#224#0#0#0#0#7#255#255#224#0#0#0#0#3#255 - +#255#192#0#0#0#0#3#255#255#192#0#0#0#0#3#255#255#192#0#0#0#0#1#255#255#128#0 - +#0#0#0#1#255#255#128#0#0#0#0#1#255#255#128#0#0#0#0#1#255#255#128#0#0#0#0#1 - +#255#255#128#0#0#0#0#0#255#255#128#0#0#0#0#0#255#255#128#0#0#0#0#0#255#255 - +#128#0#0#0#0#0#255#255#128#0#0#0#0#1#255#255#128#0#0#0#0#1#255#255#128#0#0#0 - +#0#1#255#255#128#0#0#0#0#1#255#255#192#0#0#0#0#1#255#255#192#0#0#0#0#3#255 - +#255#192#0#0#0#0#3#255#255#224#0#0#0#0#7#255#255#224#0#0#0#0#7#255#255#224#0 - +#0#0#0#15#255#255#240#0#0#0#0#15#255#255#248#0#0#0#0#31#255#255#248#0#0#0#0 - +'?'#255#255#252#0#0#0#0'?'#255#255#254#0#0#0#0''#255#255#255#0#0#0#0#255#255 - +#255#255#128#0#0#1#255#255#255#255#192#0#0#3#255#255#255#255#224#0#0#15#255 - +#255#255#255#248#0#0#31#255#255#255#255#254#0#0''#255#255#255#255#255#192#7 - +#255#255#255#255#255#255#255#255#255#255#255#255'('#0#0#0' '#0#0#0'@'#0#0#0#1 - +#0' '#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#2'CCC'#19'FFF'#22'UUU'#3#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0'@@@'#12'EAAGDDB'#133'DDC'#181'HC?'#205'LA;'#213'JB='#212'FC@'#204'E' - +'ED'#185'DDC'#145'DDDSFFF'#22#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0'III'#14'EEBoLA;'#208'eE-'#235'xK('#246#131'R)'#250#139 - +'W*'#251#149'_,'#252#145'\+'#252#136'V*'#250#128'Q('#249'rI)'#244'\D2'#227'F' - +'B?'#207'CCC'#134'DDD'#30#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'EEENMA9' - +#206'oF)'#245#147']-'#252#187'}4'#255#211#150'9'#255#224#167';'#255#230#174 - +'='#255#234#179'>'#255#233#178'>'#255#228#171'='#255#221#163'<'#255#205#143 - +'8'#255#175'r2'#255#131'R*'#250'eC,'#238'FB?'#205'CCCoUUU'#3#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'@@@' - +#4'GEA|dB+'#239#142'[.'#252#198#137'<'#255#227#172'C'#255#236#183'E'#255#242 - +#190'F'#255#246#195'G'#255#248#198'G'#255#249#199'H'#255#249#198'H'#255#247 - +#196'G'#255#245#193'G'#255#240#188'F'#255#234#180'D'#255#220#163'A'#255#183 - +'y8'#255'}O*'#250'WA4'#225'CCC'#152'<<<'#17#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1'G@='#148'lD*'#245#175'u8'#254 - +#221#166'I'#255#234#182'M'#255#242#193'P'#255#243#194'O'#255#244#195'O'#255 - +#244#195'P'#255#244#195'P'#255#244#195'P'#255#244#195'P'#255#244#195'P'#255 - +#244#195'P'#255#243#194'O'#255#243#194'O'#255#240#190'O'#255#230#179'L'#255 - +#212#155'F'#255#148'^1'#252'`A.'#234'CCC'#171'@@@'#12#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'FA>vmB)'#247#189#130'A'#255#224#172 - +'P'#255#236#187'U'#255#238#189'W'#255#238#190'V'#255#238#190'V'#255#238#190 - +'V'#255'fQ%'#255#17#14#6#255#17#13#6#255#16#13#6#255',#'#16#255#155'|8'#255 - +#238#190'V'#255#238#190'V'#255#238#189'V'#255#238#189'W'#255#233#184'U'#255 - +#217#163'M'#255#164'l8'#254'_>+'#238'DDD'#147#128#128#128#2#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'FDD=e?*'#243#183'}B'#255#223#171'W'#255#232 - +#183'['#255#232#183'\'#255#232#183'\'#255#232#183'\'#255#232#183'\'#255'>1' - +#25#255#1#1#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1#255#5#4#2#255 - +#22#17#9#255'pX,'#255#218#171'V'#255#232#183'\'#255#230#182'['#255#217#164'T' - +#255#152'a7'#252'V=1'#228'DDDb'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#128 - ,#128#128#2'Z<-'#215#163'k='#254#217#166'['#255#226#178'`'#255#226#179'a'#255 - +#226#179'a'#255#226#179'a'#255#226#179'a'#255#226#179'a'#255#5#4#2#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#3#3#1#255',#'#19#255#226#179'a'#255#225#177'_'#255#208#154'T'#255 - +#129'O1'#251'H?;'#205'@@@'#20#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'J@<[vF,'#249 - +#206#153'Y'#255#220#172'b'#255#220#172'c'#255#220#172'c'#255#220#172'c'#255 - +#220#172'c'#255#220#172'c'#255#205#160']'#255#4#3#2#255#0#0#0#255#0#0#0#255#0 - +#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#1#0#0#255'.$'#21#255#220#172'c'#255#218#170'a'#255#190#132'N'#255'c=)' - +#242'CCCz'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'\9('#218#177'vG'#255#213#164'c' - +#255#214#166'e'#255#214#166'e'#255#214#166'e'#255#214#166'e'#255#214#166'e' - +#255#214#166'e'#255#214#166'e'#255'v\8'#255#2#2#1#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255 - +#11#9#5#255#214#166'e'#255#214#166'e'#255#209#159'a'#255#139'Y6'#252'K>7'#206 - +'333'#5#0#0#0#0#0#0#0#0'?;9'#19'mA+'#247#201#148'^'#255#209#159'e'#255#209 - +#159'e'#255#209#159'e'#255#146'oF'#255#18#14#9#255#21#16#10#255'O<&'#255#209 - +#159'e'#255#209#159'e'#255'$'#27#17#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#11#8#5#255 - +#209#159'e'#255#209#159'e'#255#208#158'e'#255#185#128'Q'#255'^9)'#240'BBB6'#0 - +#0#0#0#0#0#0#0'P8-q'#144'Z:'#252#201#150'd'#255#202#151'd'#255#202#151'd'#255 - +'uW:'#255#2#1#1#255#0#0#0#255#0#0#0#255#1#0#0#255'-!'#22#255#202#151'd'#255 - +'ZC-'#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#1#1#0#255#5#4#3#255'gM3'#255#202#151'd'#255#202#151'd'#255#202 - +#151'd'#255#197#145'`'#255'l@*'#248'CCBo'#0#0#0#0#0#0#0#0'^7$'#194#171'pK' - +#255#194#140'a'#255#194#140'a'#255#194#140'a'#255#6#4#3#255#0#0#0#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#7#5#4#255#194#140'a'#255#139'eE'#255#1#1#1#255#0#0#0 - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#13#9#6#255'wV<'#255 - +#192#138'a'#255#194#140'a'#255#194#140'a'#255#194#140'a'#255#194#140'a'#255 - +#193#140'a'#255#137'T7'#252'K=6'#158#0#0#0#0#0#0#0#0'a6#'#220#177'uQ'#255#185 - +#129'['#255#185#129'['#255'Y>'#255#1#1#0#255#0#0#0#255#0#0#0#255#0#0#0#255#2 - +#1#1#255'8'''#27#255#185#129'['#255#185#129'['#255'+'#30#21#255#2#1#1#255#0#0 - +#0#255#0#0#0#255#0#0#0#255#0#0#0#255#13#9#6#255#179'}Y'#255#185#129'['#255 - +#185#129'['#255#185#129'['#255#185#129'['#255#185#129'['#255#185#129'['#255 - +#185#129'['#255#152'^>'#255'P:1'#190#0#0#0#0#0#0#0#0'c7#'#227#175'sS'#255#178 - +'xW'#255#178'xW'#255','#30#21#255#0#0#0#255#0#0#0#255#0#0#0#255#5#4#3#255'uO' - +':'#255#178'xW'#255#178'xW'#255#178'xW'#255#178'xW'#255'xP:'#255'('#26#19#255 - +#10#7#5#255#10#7#5#255#28#19#13#255#168'rS'#255#163'nO'#255#25#17#12#255#6#4 - +#3#255#17#11#8#255'bB/'#255#178'xW'#255#178'xW'#255#178'xW'#255#158'aB'#255 - +'V9,'#210#0#0#0#0#0#0#0#0'f7"'#227#173'sT'#255#175'tW'#255#175'tW'#255#15#10 - +#7#255#13#8#6#255#5#4#3#255'+'#29#21#255#169'pU'#255#175'tW'#255#175'tW'#255 - +#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175'tW'#255#175 - +'tW'#255#175'tW'#255#175'tW'#255#17#11#8#255#0#0#0#255#0#0#0#255#0#0#0#255#2 - +#2#1#255#136'ZD'#255#175'tW'#255#175'tW'#255#161'dG'#255'[8('#211#0#0#0#0#0#0 - +#0#0'e6"'#218#171'nR'#255#173'qV'#255#173'qV'#255#136'YC'#255#173'qV'#255#173 - +'qV'#255#173'qV'#255#173'qV'#255#173'qV'#255'sK9'#255'uM:'#255#159'gN'#255 - +#173'qV'#255#173'qV'#255#173'qV'#255#173'qV'#255#173'qV'#255#173'qV'#255#173 - +'qV'#255'5"'#27#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255'H/$'#255#173'qV' - +#255#173'qV'#255#153'^D'#255'Y8*'#188#0#0#0#0#0#0#0#0'c5 '#203#170'kQ'#255 - +#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#164 - +'mU'#255#11#7#6#255#1#1#1#255#1#1#1#255#5#3#2#255'TA'#255#174'sY'#255#174's' - +'Y'#255#174'sY'#255#174'sY'#255#174'sY'#255#174'sY'#255#128'UA'#255#2#1#1#255 - +#0#0#0#255#0#0#0#255#0#0#0#255'J1&'#255#174'sY'#255#174'sY'#255#147'X?'#255 - +'S8,'#140#0#0#0#0#0#0#0#0'd4'#30#153#162'gM'#255#178'{c'#255#178'{c'#255#178 - +'{c'#255#178'{c'#255#178'{c'#255#178'{c'#255'A-$'#255#0#0#0#255#0#0#0#255#0#0 - +#0#255#0#0#0#255#20#14#11#255'xSB'#255#6#4#3#255#5#4#3#255#18#12#10#255#170 - +'u_'#255#178'{c'#255#178'{c'#255#9#6#5#255#0#0#0#255#0#0#0#255#3#2#2#255#166 - +'s]'#255#178'{c'#255#178'{c'#255#131'P8'#252'S3$>'#0#0#0#0#0#0#0#0'[/'#27'3' - +#137'T='#247#184#134'o'#255#184#134'o'#255#184#134'o'#255#184#134'o'#255#184 - +#134'o'#255#184#134'o'#255#14#10#9#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#1 - +#255'\C7'#255#14#10#8#255#0#0#0#255#0#0#0#255#0#0#0#255#14#10#8#255#184#134 - +'o'#255#184#134'o'#255'xWI'#255#2#1#1#255#0#0#0#255#18#14#11#255#184#134'o' - +#255#184#134'o'#255#183#131'l'#255'h:&'#240#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +'n<%'#218#189#140'x'#255#190#143'z'#255#190#143'z'#255#190#143'z'#255#190#143 - +'z'#255#190#143'z'#255',!'#28#255#0#0#0#255#0#0#0#255#0#0#0#255#9#7#6#255#190 - ,#143'z'#255#9#7#6#255#0#0#0#255#0#0#0#255#0#0#0#255#5#4#3#255#190#143'z'#255 - +#190#143'z'#255#190#143'z'#255#159'xe'#255#0#0#0#255#140'jZ'#255#190#143'z' - +#255#190#143'z'#255#175'yc'#255'b6"'#193#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'd4' - +#30#151#167's\'#254#196#154#135#255#196#154#135#255#196#154#135#255#196#154 - +#135#255#196#154#135#255'qYN'#255#0#0#0#255#0#0#0#255#1#1#1#255'v]R'#255#194 - +#152#133#255#5#4#4#255#0#0#0#255#0#0#0#255#0#0#0#255#10#8#7#255#196#154#135 - +#255#196#154#135#255#196#154#135#255#196#154#135#255'"'#27#24#255#196#154#135 - +#255#196#154#135#255#196#154#135#255#131'R<'#248'^1'#28'1'#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0'[/'#27#14'o>('#224#198#157#140#255#202#163#146#255#202#163#146 - +#255#202#163#146#255#202#163#146#255#170#138'{'#255#0#0#0#255#8#6#6#255#26#21 - +#19#255#202#163#146#255#143'th'#255#1#1#1#255#0#0#0#255#0#0#0#255#2#2#2#255 - +#143'sg'#255#202#163#146#255#202#163#146#255#202#163#146#255#202#163#146#255 - +#191#155#138#255#202#163#146#255#202#163#146#255#183#135'r'#255'd5 '#193#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'd3'#30'{'#156'jS'#248#209#174 - +#160#255#209#174#160#255#209#174#160#255#209#174#160#255#209#174#160#255#2#2 - +#2#255#205#172#158#255#209#174#160#255#209#174#160#255#169#140#129#255#11#9#9 - +#255#1#1#0#255#4#3#3#255#132'oe'#255#209#174#160#255#209#174#160#255#209#174 - +#160#255#209#174#160#255#209#174#160#255#209#174#160#255#209#174#160#255#205 - +#167#152#255'yG3'#235'[/'#27'"'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0'd4'#30#181#179#134'r'#254#215#185#173#255#215#185#173#255#215#185 - +#173#255#215#185#173#255'm^X'#255#215#185#173#255#215#185#173#255#215#185#173 - +#255#215#185#173#255#133'sk'#255#0#0#0#255#156#134'}'#255#215#185#173#255#215 - +#185#173#255#215#185#173#255#215#185#173#255#215#185#173#255#215#185#173#255 - +#215#185#173#255#214#183#172#255#143'^I'#243'b3'#29'`'#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#8'f6 '#205#189#148#131#254 - +#222#197#187#255#222#197#187#255#222#197#187#255#222#197#187#255#222#197#187 - +#255#222#197#187#255#222#197#187#255#222#197#187#255'qk'#255#134'wq'#255#222 - +#197#187#255#222#197#187#255#222#197#187#255#222#197#187#255#222#197#187#255 - +#222#197#187#255#222#197#187#255#220#193#183#255#159'o['#247'd3'#30#147#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[' - +'/'#27#15'c3'#30#193#176#132'q'#248#228#206#199#255#228#208#201#255#228#208 - +#201#255#228#208#201#255#228#208#201#255#228#208#201#255#228#208#201#255#228 - +#208#201#255#228#208#201#255#228#208#201#255#228#208#201#255#228#208#201#255 - +#228#208#201#255#228#208#201#255#228#208#201#255#221#196#186#255#141']I'#237 - +'c3'#29#129#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#2'c3'#29#150#135'XD'#231#208#176#164 - +#255#235#219#215#255#235#219#215#255#235#219#215#255#235#219#215#255#235#219 - +#215#255#235#219#215#255#235#219#215#255#235#219#215#255#235#219#215#255#235 - +#219#215#255#235#219#215#255#232#214#209#255#189#149#133#253'qA+'#220'a2'#29 - +'O'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27'"e4'#30#185#144'cO'#235 - +#195#158#144#254#230#209#203#255#242#231#229#255#242#231#229#255#242#231#229 - +#255#242#231#229#255#242#231#229#255#241#230#226#255#220#193#184#255#180#141 - +'|'#251'zK6'#226'd3'#30#142'[/'#27#10#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#28'b3'#29'{g5'#30#198'xH2'#218#137']J' - +#229#156'r`'#236#151'mZ'#234#132'VB'#227'q?)'#213'f4'#30#182'_1'#28'W[/'#27 - +#11#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#5'[/'#27#30'[/'#27#22'[/' - +#27#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#255#252'?'#255#255#192#3#255#255#0#0 - +#255#254#0#0'?'#248#0#0#31#240#0#0#15#240#0#0#7#224#0#0#7#192#0#0#3#192#0#0#3 - +#192#0#0#1#128#0#0#1#128#0#0#1#128#0#0#1#128#0#0#1#128#0#0#1#128#0#0#1#128#0 - +#0#1#128#0#0#1#128#0#0#1#128#0#0#3#192#0#0#3#192#0#0#3#192#0#0#7#224#0#0#7 - +#240#0#0#15#240#0#0#31#248#0#0'?'#252#0#0''#255#0#0#255#255#192#3#255#255 - +#252'?'#255'('#0#0#0#16#0#0#0' '#0#0#0#1#0' '#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0';;;'#13'ICB7M=4zL' - +'>6xFBA;@@@'#16#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0'III'#7'W;,'#171'nC('#245#145'`+'#250#181'2'#253#175'z1'#253#139'\*'#250 - +'h?&'#244'P=3'#159'@@@'#12#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'K921d;''' - +#235#191#137'7'#254#249#198'E'#255#251#199'F'#255#251#200'F'#255#251#200'F' - +#255#251#199'F'#255#246#194'E'#255#173'y3'#253'\9)'#229'E>;2'#0#0#0#0#0#0#0#0 - +#0#0#0#0'>2,'#7'nC'''#238#223#171'N'#255#239#191'U'#255#240#192'U'#255#164 - ,#131':'#255'@3'#23#255'J;'#26#255'~e-'#255#196#157'E'#255#239#191'U'#255#212 - +#159'J'#255'a<('#231'MMM'#10#0#0#0#0#0#0#0#0'[7('#183#195#145'N'#254#228#180 - +'_'#255#228#180'_'#255#228#180'_'#255'</'#25#255#0#0#0#255#0#0#0#255#0#0#0 - +#255#4#3#1#255'9-'#24#255#205#163'U'#255#175'~E'#253'Q:0'#168#0#0#0#0'[/'#27 - +#10#143'd@'#248#219#174'l'#255#219#173'l'#255#159'~N'#255#206#163'f'#255'qY7' - +#255#0#0#0#255#0#0#0#255#0#0#0#255#0#0#0#255#1#1#0#255#152'xK'#255#218#172'k' - +#255'zQ5'#246'UUU'#3'W4$H'#178#134'_'#254#213#169'x'#255'YF2'#255#0#0#0#255 - +'L<+'#255#200#158'p'#255#8#6#4#255#0#0#0#255#0#0#0#255#18#15#10#255'pY?'#255 - +#213#169'x'#255#213#169'x'#255#157'tR'#251'M=74^6$'#154#193#151'x'#255#179 - +#144's'#255#6#5#4#255#27#21#17#255#155'|c'#255#206#165#132#255#130'hS'#255'=' - +'1'''#255':/%'#255#170#137'm'#255#141'q['#255#166#133'j'#255#206#165#132#255 - +#178#135'l'#253'Q8,{c5 '#167#195#154#133#255#163#130'q'#255#153'zj'#255#192 - +#152#132#255#199#159#138#255#200#159#138#255#201#160#139#255#201#160#139#255 - +#201#160#139#255#154'zj'#255#1#1#1#255#29#23#20#255#197#157#136#255#181#139 - +'v'#255'X5%|_1'#28'V'#176#132'r'#254#199#159#141#255#199#159#141#255#184#147 - +#130#255'#'#28#25#255#27#22#19#255#167#133'v'#255'fZ'#255#172#137'z'#255#197 - +#157#139#255' '#26#23#255#15#12#11#255#187#150#133#255#156'tb'#251'W1 0[/'#27 - +#30#160'zk'#246#210#176#163#255#210#176#163#255#154#129'x'#255#0#0#0#255' ' - +#27#25#255#152#128'v'#255#4#3#3#255'>40'#255#210#176#163#255#148'|s'#255'RD?' - +#255#210#176#163#255#140'fW'#243'[/'#27#2#0#0#0#0'tI6'#209#216#188#178#255 - +#221#195#185#255#213#188#178#255'$'#31#30#255#131'sn'#255'sf`'#255#0#0#0#255 - +'eYU'#255#221#195#185#255#221#195#185#255#204#180#170#255#207#178#166#255'k>' - +'+'#182#0#0#0#0#0#0#0#0'[/'#27#31#156'xi'#240#232#213#207#255#232#213#207#255 - +#180#165#161#255#232#213#207#255#182#167#162#255'ICA'#255#226#207#201#255#232 - +#213#207#255#232#213#207#255#231#213#206#255#134'_O'#233'[/'#27#12#0#0#0#0#0 - +#0#0#0#0#0#0#0'd3'#30'e'#171#139'~'#240#239#226#224#255#242#231#229#255#242 - +#231#229#255#240#229#227#255#242#231#229#255#242#231#229#255#242#231#229#255 - +#235#221#216#255#153'uh'#236'a2'#29'?'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0'\0'#27'*'#131'\I'#215#212#191#183#252#239#229#226#255#253#250#252#255#253 - +#249#250#255#236#223#220#255#204#180#172#250'yM;'#196'[/'#27#23#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0'[/'#27#3'[/'#27'>c3'#29#131'uI4' - +#190'oA-'#183'b2'#29'v[/'#27'4'#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#248 - +#31#172'A'#224#7#172'A'#192#3#172'A'#128#1#172'A'#128#1#172'A'#0#0#172'A'#0#0 - +#172'A'#0#0#172'A'#0#0#172'A'#0#0#172'A'#0#0#172'A'#128#1#172'A'#128#1#172'A' - +#192#3#172'A'#224#7#172'A'#240#31#172'A' -]); - diff --git a/examples/cross_calculator/lazarus/nimlaz.rc b/examples/cross_calculator/lazarus/nimlaz.rc deleted file mode 100644 index d66bb817c..000000000 --- a/examples/cross_calculator/lazarus/nimlaz.rc +++ /dev/null @@ -1,6 +0,0 @@ -#define RT_MANIFEST 24 -#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 -#define ISOLATIONAWARE_MANIFEST_RESOURCE_ID 2 -#define ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID 3 - -CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "nimlaz.manifest" diff --git a/examples/cross_calculator/lazarus/readme.txt b/examples/cross_calculator/lazarus/readme.txt deleted file mode 100644 index c704d53fd..000000000 --- a/examples/cross_calculator/lazarus/readme.txt +++ /dev/null @@ -1,8 +0,0 @@ -This example demonstrates how to use Nim with Lazarus. The GUI is generated -with Lazarus, while the "backend" is written in Nim. To compile the example, -use this command: - - nim c --app:gui --no_main --no_linking backend.nim - -Open the ``nimlaz.lpi`` file in Lazarus and run the program. - diff --git a/examples/cross_calculator/lazarus/unit1.lfm b/examples/cross_calculator/lazarus/unit1.lfm deleted file mode 100644 index bf60ff715..000000000 --- a/examples/cross_calculator/lazarus/unit1.lfm +++ /dev/null @@ -1,46 +0,0 @@ -object Form1: TForm1 - Left = 553 - Height = 111 - Top = 464 - Width = 448 - ActiveControl = SpinEdit1 - Caption = 'Sum' - ClientHeight = 111 - ClientWidth = 448 - OnCreate = FormCreate - LCLVersion = '0.9.28.2' - object Label1: TLabel - Left = 8 - Height = 18 - Top = 72 - Width = 34 - Caption = 'Sum:' - ParentColor = False - end - object SpinEdit1: TSpinEdit - Left = 8 - Height = 27 - Top = 8 - Width = 436 - Anchors = [akTop, akLeft, akRight] - OnChange = SpinEdit1Change - TabOrder = 0 - end - object SpinEdit2: TSpinEdit - Left = 8 - Height = 27 - Top = 40 - Width = 436 - Anchors = [akTop, akLeft, akRight] - OnChange = SpinEdit1Change - TabOrder = 1 - end - object Edit1: TEdit - Left = 48 - Height = 27 - Top = 72 - Width = 396 - Anchors = [akTop, akLeft, akRight] - TabOrder = 2 - end -end diff --git a/examples/cross_calculator/lazarus/unit1.pas b/examples/cross_calculator/lazarus/unit1.pas deleted file mode 100644 index 6091a61d3..000000000 --- a/examples/cross_calculator/lazarus/unit1.pas +++ /dev/null @@ -1,58 +0,0 @@ -unit Unit1; - -{$mode objfpc}{$H+} - -interface - -uses - Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, - Spin, StdCtrls; - -type - - { TForm1 } - - TForm1 = class(TForm) - Edit1: TEdit; - Label1: TLabel; - SpinEdit1: TSpinEdit; - SpinEdit2: TSpinEdit; - procedure FormCreate(Sender: TObject); - procedure SpinEdit1Change(Sender: TObject); - private - { private declarations } - public - { public declarations } - end; - -var - Form1: TForm1; - -implementation - -{ TForm1 } - -{$link nimcache/lib/system.o} -{$link nimcache/backend.o} -{$link nimcache/nim__dat.o} -{$linklib c} - -procedure NimMain; cdecl; external; -function myAdd(x, y: longint): longint; cdecl; external; - -procedure TForm1.FormCreate(Sender: TObject); -begin - // we initialize the Nim data structures here: - NimMain(); -end; - -procedure TForm1.SpinEdit1Change(Sender: TObject); -begin - Edit1.text := IntToStr(myAdd(SpinEdit1.Value, SpinEdit2.Value)); -end; - -initialization - {$I unit1.lrs} - -end. - diff --git a/examples/cross_calculator/nim_backend/backend.nim b/examples/cross_calculator/nim_backend/backend.nim deleted file mode 100644 index c8684581c..000000000 --- a/examples/cross_calculator/nim_backend/backend.nim +++ /dev/null @@ -1,5 +0,0 @@ -# Backend for the different user interfaces. - -proc myAdd*(x, y: int): int {.cdecl, exportc.} = - result = x + y - diff --git a/examples/cross_calculator/nim_commandline/nim.cfg b/examples/cross_calculator/nim_commandline/nim.cfg deleted file mode 100644 index 6f0cb4a01..000000000 --- a/examples/cross_calculator/nim_commandline/nim.cfg +++ /dev/null @@ -1,4 +0,0 @@ -# Nim configuration file. -# The file is used only to add the path of the backend to the compiler options. - -path="../nim_backend" diff --git a/examples/cross_calculator/nim_commandline/nimcalculator.nim b/examples/cross_calculator/nim_commandline/nimcalculator.nim deleted file mode 100644 index 3f7674dbb..000000000 --- a/examples/cross_calculator/nim_commandline/nimcalculator.nim +++ /dev/null @@ -1,109 +0,0 @@ -# Implements a command line interface against the backend. - -import backend, parseopt, strutils - -const - USAGE = """nimcalculator - Nim cross platform calculator - (beta version, only integer addition is supported!) - -Usage: - nimcalculator [options] [-a=value -b=value] -Options: - -a=value sets the integer value of the a parameter - -b=value sets the integer value of the b parameter - -h, --help shows this help - -If no options are used, an interactive mode is entered. -""" - -type - TCommand = enum # The possible types of operation - cmdParams, # Two valid parameters were provided - cmdInteractive # No parameters were provided, run interactive mode - - TParamConfig = object of RootObj - action: TCommand # store the type of operation - paramA, paramB: int # possibly store the valid parameters - - -proc parseCmdLine(): TParamConfig = - ## Parses the commandline. - ## - ## Returns a TParamConfig structure filled with the proper values or directly - ## calls quit() with the appropriate error message. - var - hasA = false - hasB = false - p = initOptParser() - key, val: TaintedString - - result.action = cmdInteractive # By default presume interactive mode. - try: - while true: - next p - key = p.key - val = p.val - - case p.kind - of cmdArgument: - stdout.write USAGE - quit "Erroneous argument detected: " & key, 1 - of cmdLongOption, cmdShortOption: - case key.normalize - of "help", "h": - stdout.write USAGE - quit 0 - of "a": - result.paramA = val.parseInt - hasA = true - of "b": - result.paramB = val.parseInt - hasB = true - else: - stdout.write USAGE - quit "Unexpected option: " & key, 2 - of cmdEnd: break - except ValueError: - stdout.write USAGE - quit "Invalid value " & val & " for parameter " & key, 3 - - if hasA and hasB: - result.action = cmdParams - elif hasA or hasB: - stdout.write USAGE - quit "Error: provide both A and B to operate in param mode", 4 - - -proc parseUserInput(question: string): int = - ## Parses a line of user input, showing question to the user first. - ## - ## If the user input is an empty line quit() is called. Returns the value - ## parsed as an integer. - while true: - echo question - let input = stdin.readLine - try: - result = input.parseInt - break - except ValueError: - if input.len < 1: quit "Blank line detected, quitting.", 0 - echo "Sorry, `$1' doesn't seem to be a valid integer" % input - -proc interactiveMode() = - ## Asks the user for two integer values, adds them and exits. - let - paramA = parseUserInput("Enter the first parameter (blank to exit):") - paramB = parseUserInput("Enter the second parameter (blank to exit):") - echo "Calculating... $1 + $2 = $3" % [$paramA, $paramB, - $backend.myAdd(paramA, paramB)] - - -when isMainModule: - ## Main entry point. - let opt = parseCmdLine() - if cmdParams == opt.action: - echo "Param mode: $1 + $2 = $3" % [$opt.paramA, $opt.paramB, - $backend.myAdd(opt.paramA, opt.paramB)] - else: - echo "Entering interactive addition mode" - interactiveMode() diff --git a/examples/cross_calculator/nim_commandline/readme.txt b/examples/cross_calculator/nim_commandline/readme.txt deleted file mode 100644 index f95bd962e..000000000 --- a/examples/cross_calculator/nim_commandline/readme.txt +++ /dev/null @@ -1,10 +0,0 @@ -In this directory you will find the nim commandline version of the -cross-calculator sample. - -The commandline interface can be used non interactively through switches, or -interactively when running the command without parameters. - -Compilation is fairly easy despite having the source split in different -directories. Thanks to the nim.cfg file, which adds the ../nim_backend -directory as a search path, you can compile and run the example just fine from -the command line with 'nim c -r nimcalculator.nim'. diff --git a/examples/cross_calculator/readme.txt b/examples/cross_calculator/readme.txt deleted file mode 100644 index 72e4130eb..000000000 --- a/examples/cross_calculator/readme.txt +++ /dev/null @@ -1,13 +0,0 @@ -The cross platform calculator illustrates how to use Nim to create a backend -called by different native user interfaces. - -Since the purpose of the example is to show how the cross platform code -interacts with Nim the actual backend code is just a simple addition proc. -By keeping your program logic in Nim you can easily reuse it in different -platforms. - -To avoid duplication of code, the backend code lies in a separate directory and -each platform compiles it with a different custom build process, usually -generating C code in a temporary build directory. - -For a more elaborate and useful example see the cross_todo example. diff --git a/examples/cross_todo/nim_backend/backend.nim b/examples/cross_todo/nim_backend/backend.nim deleted file mode 100644 index 513fe304f..000000000 --- a/examples/cross_todo/nim_backend/backend.nim +++ /dev/null @@ -1,195 +0,0 @@ -# Backend for a simple todo program with sqlite persistence. -# -# Most procs dealing with a DbConn object may raise an EDb exception. - -import db_sqlite, parseutils, strutils, times - -type - Todo* = object - ## A todo object holding the information serialized to the database. - id: int64 ## Unique identifier of the object in the - ## database, use the getId() accessor to read it. - text*: string ## Description of the task to do. - priority*: int ## The priority can be any user defined integer. - isDone*: bool ## Done todos are still kept marked. - modificationDate: Time ## The modification time can't be modified from - ## outside of this module, use the - ## getModificationDate accessor. - - PagedParams* = object - ## Contains parameters for a query, initialize default values with - ## initDefaults(). - pageSize*: int64 ## Lines per returned query page, -1 for - ## unlimited. - priorityAscending*: bool ## Sort results by ascending priority. - dateAscending*: bool ## Sort results by ascending modification date. - showUnchecked*: bool ## Get unchecked objects. - showChecked*: bool ## Get checked objects. - -# - General procs - -proc initDefaults*(params: var PagedParams) = - ## Sets sane defaults for a PagedParams object. - ## - ## Note that you should always provide a non zero pageSize, either a specific - ## positive value or negative for unbounded query results. - params.pageSize = high(int64) - params.priorityAscending = false - params.dateAscending = false - params.showUnchecked = true - params.showChecked = false - -proc openDatabase*(path: string): DbConn = - ## Creates or opens the sqlite3 database. - ## - ## Pass the path to the sqlite database, if the database doesn't exist it - ## will be created. The proc may raise a EDB exception - let - conn = db_sqlite.open(path, "user", "pass", "db") - query = sql"""CREATE TABLE IF NOT EXISTS Todos ( - id INTEGER PRIMARY KEY, - priority INTEGER NOT NULL, - is_done BOOLEAN NOT NULL, - desc TEXT NOT NULL, - modification_date INTEGER NOT NULL, - CONSTRAINT Todos UNIQUE (id))""" - db_sqlite.exec(conn, query) - result = conn - -# - Procs related to Todo objects - -proc initFromDB(id: int64; text: string; priority: int, isDone: bool; - modificationDate: Time): Todo = - ## Returns an initialized Todo object created from database parameters. - ## - ## The proc assumes all values are right. Note this proc is NOT exported. - assert(id >= 0, "Identity identifiers should not be negative") - result.id = id - result.text = text - result.priority = priority - result.isDone = isDone - result.modificationDate = modificationDate - -proc getId*(todo: Todo): int64 = - ## Accessor returning the value of the private id property. - return todo.id - -proc getModificationDate*(todo: Todo): Time = - ## Returns the last modification date of a Todo entry. - return todo.modificationDate - -proc update*(todo: var Todo; conn: DbConn): bool = - ## Checks the database for the object and refreshes its variables. - ## - ## Use this method if you (or another entity) have modified the database and - ## want to update the object you have with whatever the database has stored. - ## Returns true if the update succeeded, or false if the object was not found - ## in the database any more, in which case you should probably get rid of the - ## Todo object. - assert(todo.id >= 0, "The identifier of the todo entry can't be negative") - let query = sql"""SELECT desc, priority, is_done, modification_date - FROM Todos WHERE id = ?""" - try: - let rows = conn.getAllRows(query, $todo.id) - if len(rows) < 1: - return - assert(1 == len(rows), "Woah, didn't expect so many rows") - todo.text = rows[0][0] - todo.priority = rows[0][1].parseInt - todo.isDone = rows[0][2].parseBool - todo.modificationDate = Time(rows[0][3].parseInt) - result = true - except: - echo("Something went wrong selecting for id " & $todo.id) - -proc save*(todo: var Todo; conn: DbConn): bool = - ## Saves the current state of text, priority and isDone to the database. - ## - ## Returns true if the database object was updated (in which case the - ## modification date will have changed). The proc can return false if the - ## object wasn't found, for instance, in which case you should drop that - ## object anyway and create a new one with addTodo(). Also EDb can be raised. - assert(todo.id >= 0, "The identifier of the todo entry can't be negative") - let - currentDate = getTime() - query = sql"""UPDATE Todos - SET desc = ?, priority = ?, is_done = ?, modification_date = ? - WHERE id = ?""" - rowsUpdated = conn.execAffectedRows(query, $todo.text, - $todo.priority, $todo.isDone, $int(currentDate), $todo.id) - if 1 == rowsUpdated: - todo.modificationDate = currentDate - result = true - -# - Procs dealing directly with the database - -proc addTodo*(conn: DbConn; priority: int; text: string): Todo = - ## Inserts a new todo into the database. - ## - ## Returns the generated todo object. If there is an error EDb will be raised. - let - currentDate = getTime() - query = sql"""INSERT INTO Todos - (priority, is_done, desc, modification_date) - VALUES (?, 'false', ?, ?)""" - todoId = conn.insertId(query, priority, text, $int(currentDate)) - result = initFromDB(todoId, text, priority, false, currentDate) - -proc deleteTodo*(conn: DbConn; todoId: int64): int64 {.discardable.} = - ## Deletes the specified todo identifier. - ## - ## Returns the number of rows which were affected (1 or 0) - let query = sql"""DELETE FROM Todos WHERE id = ?""" - result = conn.execAffectedRows(query, $todoId) - -proc getNumEntries*(conn: DbConn): int = - ## Returns the number of entries in the Todos table. - ## - ## If the function succeeds, returns the zero or positive value, if something - ## goes wrong a negative value is returned. - let query = sql"""SELECT COUNT(id) FROM Todos""" - try: - let row = conn.getRow(query) - result = row[0].parseInt - except: - echo("Something went wrong retrieving number of Todos entries") - result = -1 - -proc getPagedTodos*(conn: DbConn; params: PagedParams; page = 0'i64): seq[Todo] = - ## Returns the todo entries for a specific page. - ## - ## Pages are calculated based on the params.pageSize parameter, which can be - ## set to a negative value to specify no limit at all. The query will be - ## affected by the PagedParams, which should have sane values (call - ## initDefaults). - assert(page >= 0, "You should request a page zero or bigger than zero") - result = @[] - # Well, if you don't want to see anything, there's no point in asking the db. - if not params.showUnchecked and not params.showChecked: return - let - order_by = [ - if params.priorityAscending: "ASC" else: "DESC", - if params.dateAscending: "ASC" else: "DESC"] - query = sql("""SELECT id, desc, priority, is_done, modification_date - FROM Todos - WHERE is_done = ? OR is_done = ? - ORDER BY priority $1, modification_date $2, id DESC - LIMIT ? * ?,?""" % order_by) - args = @[$params.showChecked, $(not params.showUnchecked), - $params.pageSize, $page, $params.pageSize] - #echo("Query " & string(query)) - #echo("args: " & args.join(", ")) - var newId: BiggestInt - for row in conn.fastRows(query, args): - let numChars = row[0].parseBiggestInt(newId) - assert(numChars > 0, "Huh, couldn't parse identifier from database?") - result.add(initFromDB(int64(newId), row[1], row[2].parseInt, - row[3].parseBool, Time(row[4].parseInt))) - -proc getTodo*(conn: DbConn; todoId: int64): ref Todo = - ## Returns a reference to a Todo or nil if the todo could not be found. - var tempTodo: Todo - tempTodo.id = todoId - if tempTodo.update(conn): - new(result) - result[] = tempTodo diff --git a/examples/cross_todo/nim_backend/readme.txt b/examples/cross_todo/nim_backend/readme.txt deleted file mode 100644 index 4b31408e3..000000000 --- a/examples/cross_todo/nim_backend/readme.txt +++ /dev/null @@ -1,14 +0,0 @@ -This directory contains the nim backend code for the todo cross platform -example. - -Unlike the cross platform calculator example, this backend features more code, -using an sqlite database for storage. Also a basic test module is provided, not -to be included with the final program but to test the exported functionality. -The test is not embedded directly in the backend.nim file to avoid being able -to access internal data types and procs not exported and replicate the -environment of client code. - -In a bigger project with several people you could run `nim doc backend.nim` -(or use the doc2 command for a whole project) and provide the generated html -documentation to another programer for her to implement an interface without -having to look at the source code. diff --git a/examples/cross_todo/nim_backend/testbackend.nim b/examples/cross_todo/nim_backend/testbackend.nim deleted file mode 100644 index 4a71d5f2c..000000000 --- a/examples/cross_todo/nim_backend/testbackend.nim +++ /dev/null @@ -1,74 +0,0 @@ -# Tests the backend code. - -import backend, db_sqlite, strutils, times - -proc showPagedResults(conn: DbConn; params: PagedParams) = - ## Shows the contents of the database in pages of specified size. - ## - ## Hmm... I guess this is more of a debug proc which should be moved outside, - ## or to a commandline interface (hint). - var - page = 0'i64 - rows = conn.getPagedTodos(params) - while rows.len > 0: - echo("page " & $page) - for row in rows: - echo("row id:$1, text:$2, priority:$3, done:$4, date:$5" % [$row.getId, - $row.text, $row.priority, $row.isDone, - $row.getModificationDate]) - # Query the database for the next page or quit. - if params.pageSize > 0: - page = page + 1 - rows = conn.getPagedTodos(params, page) - else: - break - -proc dumTest() = - let conn = openDatabase("todo.sqlite3") - try: - let numTodos = conn.getNumEntries - echo("Current database contains " & $numTodos & " todo items.") - if numTodos < 10: - # Fill some dummy rows if there are not many entries yet. - discard conn.addTodo(3, "Filler1") - discard conn.addTodo(4, "Filler2") - var todo = conn.addTodo(2, "Testing") - echo("New todo added with id " & $todo.getId) - # Try changing it and updating the database. - var clonedTodo = conn.getTodo(todo.getId)[] - assert(clonedTodo.text == todo.text, "Should be equal") - todo.text = "Updated!" - todo.priority = 7 - todo.isDone = true - if todo.save(conn): - echo("Updated priority $1, done $2" % [$todo.priority, $todo.isDone]) - else: - assert(false, "Uh oh, I wasn't expecting that!") - # Verify our cloned copy is different but can be updated. - assert(clonedTodo.text != todo.text, "Should be different") - discard clonedTodo.update(conn) - assert(clonedTodo.text == todo.text, "Should be equal") - var params: PagedParams - params.initDefaults - conn.showPagedResults(params) - conn.deleteTodo(todo.getId) - echo("Deleted rows for id 3? ") - let res = conn.deleteTodo(todo.getId) - echo("Deleted rows for id 3? " & $res) - if todo.update(conn): - echo("Later priority $1, done $2" % [$todo.priority, $todo.isDone]) - else: - echo("Can't update object $1 from db!" % $todo.getId) - # Try to list content in a different way. - params.pageSize = 5 - params.priorityAscending = true - params.dateAscending = true - params.showChecked = true - conn.showPagedResults(params) - finally: - conn.close - echo("Database closed") - -# Code that will be run only on the commandline. -when isMainModule: - dumTest() diff --git a/examples/cross_todo/nim_commandline/nim.cfg b/examples/cross_todo/nim_commandline/nim.cfg deleted file mode 100644 index 6f0cb4a01..000000000 --- a/examples/cross_todo/nim_commandline/nim.cfg +++ /dev/null @@ -1,4 +0,0 @@ -# Nim configuration file. -# The file is used only to add the path of the backend to the compiler options. - -path="../nim_backend" diff --git a/examples/cross_todo/nim_commandline/nimtodo.nim b/examples/cross_todo/nim_commandline/nimtodo.nim deleted file mode 100644 index be5b3407b..000000000 --- a/examples/cross_todo/nim_commandline/nimtodo.nim +++ /dev/null @@ -1,297 +0,0 @@ -# Implements a command line interface against the backend. - -import backend, db_sqlite, os, parseopt, parseutils, strutils, times - -const - USAGE = """nimtodo - Nim cross platform todo manager - -Usage: - nimtodo [command] [list options] - -Commands: - -a=int text Adds a todo entry with the specified priority and text. - -c=int Marks the specified todo entry as done. - -u=int Marks the specified todo entry as not done. - -d=int|all Deletes a single entry from the database, or all entries. - -g Generates some rows with values for testing. - -l Lists the contents of the database. - -h, --help shows this help - -List options (optional): - -p=+|- Sorts list by ascending|descending priority. Default:descending. - -m=+|- Sorts list by ascending|descending date. Default:descending. - -t Show checked entries. By default they are not shown. - -z Hide unchecked entries. By default they are shown. - -Examples: - nimtodo -a=4 Water the plants - nimtodo -c:87 - nimtodo -d:2 - nimtodo -d:all - nimtodo -l -p=+ -m=- -t - -""" - -type - Command = enum # The possible types of commands - cmdAdd # The user wants to add a new todo entry. - cmdCheck # User wants to check a todo entry. - cmdUncheck # User wants to uncheck a todo entry. - cmdDelete # User wants to delete a single todo entry. - cmdNuke # User wants to purge all database entries. - cmdGenerate # Add random rows to the database, for testing. - cmdList # User wants to list contents. - - ParamConfig = object - # Structure containing the parsed options from the commandline. - command: Command # Store the type of operation - addPriority: int # Only valid with cmdAdd, stores priority. - addText: seq[string] # Only valid with cmdAdd, stores todo text. - todoId: int64 # The todo id for operations like check or delete. - listParams: PagedParams # Uses the backend structure directly for params. - -proc initDefaults(params: var ParamConfig) = - ## Initialises defaults value in the structure. - ## - ## Most importantly we want to have an empty list for addText. - params.listParams.initDefaults - params.addText = @[] - -proc abort(message: string, value: int) = - # Simple wrapper to abort also displaying the help to the user. - stdout.write(USAGE) - quit(message, value) - -template parseTodoIdAndSetCommand(newCommand: Command): untyped = - ## Helper to parse a big todo identifier into todoId and set command. - try: - let numChars = val.parseBiggestInt(newId) - if numChars < 1: raise newException(ValueError, "Empty string?") - result.command = newCommand - result.todoId = newId - except OverflowError: - raise newException(ValueError, "Value $1 too big" % val) - -template verifySingleCommand(actions: typed): typed = - ## Helper to make sure only one command has been specified so far. - if specifiedCommand: - abort("Only one command can be specified at a time! (extra:$1)" % [key], 2) - else: - actions - specifiedCommand = true - -proc parsePlusMinus(val: string, debugText: string): bool = - ## Helper to process a plus or minus character from the commandline. - ## - ## Pass the string to parse and the type of parameter for debug errors. - ## The processed parameter will be returned as true for a '+' and false for a - ## '-'. The proc aborts with a debug message if the passed parameter doesn't - ## contain one of those values. - case val - of "+": - return true - of "-": - return false - else: - abort("$1 parameter should be + or - but was '$2'." % [debugText, val], 4) - -proc parseCmdLine(): ParamConfig = - ## Parses the commandline. - ## - ## Returns a ParamConfig structure filled with the proper values or directly - ## calls quit() with the appropriate error message. - var - specifiedCommand = false - usesListParams = false - p = initOptParser() - key, val: TaintedString - newId: BiggestInt - result.initDefaults - try: - while true: - next(p) - key = p.key - val = p.val - case p.kind - of cmdArgument: - if specifiedCommand and cmdAdd == result.command: - result.addText.add(key) - else: - abort("Argument ($1) detected without add command." % [key], 1) - of cmdLongOption, cmdShortOption: - case normalize(key) - of "help", "h": - stdout.write(USAGE) - quit(0) - of "a": - verifySingleCommand: - result.command = cmdAdd - result.addPriority = val.parseInt - of "c": - verifySingleCommand: - parseTodoIdAndSetCommand(cmdCheck) - of "u": - verifySingleCommand: - parseTodoIdAndSetCommand cmdUncheck - of "d": - verifySingleCommand: - if "all" == val: - result.command = cmdNuke - else: - parseTodoIdAndSetCommand cmdDelete - of "g": - verifySingleCommand: - if val.len > 0: - abort("Unexpected value '$1' for switch l." % [val], 3) - result.command = cmdGenerate - of "l": - verifySingleCommand: - if val.len > 0: - abort("Unexpected value '$1' for switch l." % [val], 3) - result.command = cmdList - of "p": - usesListParams = true - result.listParams.priorityAscending = parsePlusMinus(val, "Priority") - of "m": - usesListParams = true - result.listParams.dateAscending = parsePlusMinus(val, "Date") - of "t": - usesListParams = true - if val.len > 0: - abort("Unexpected value '$1' for switch t." % [val], 5) - result.listParams.showChecked = true - of "z": - usesListParams = true - if val.len > 0: - abort("Unexpected value '$1' for switch z." % [val], 5) - result.listParams.showUnchecked = false - else: - abort("Unexpected option '$1'." % [key], 6) - of cmdEnd: - break - except ValueError: - abort("Invalid integer value '$1' for parameter '$2'." % [val, key], 7) - if not specifiedCommand: - abort("Didn't specify any command.", 8) - if cmdAdd == result.command and result.addText.len < 1: - abort("Used the add command, but provided no text/description.", 9) - if usesListParams and cmdList != result.command: - abort("Used list options, but didn't specify the list command.", 10) - -proc generateDatabaseRows(conn: DbConn) = - ## Adds some rows to the database ignoring errors. - discard conn.addTodo(1, "Watch another random youtube video") - discard conn.addTodo(2, "Train some starcraft moves for the league") - discard conn.addTodo(3, "Spread the word about Nim") - discard conn.addTodo(4, "Give fruit superavit to neighbours") - var todo = conn.addTodo(4, "Send tax form through snail mail") - todo.isDone = true - discard todo.save(conn) - discard conn.addTodo(1, "Download new anime to watch") - todo = conn.addTodo(2, "Build train model from scraps") - todo.isDone = true - discard todo.save(conn) - discard conn.addTodo(5, "Buy latest Britney Spears album") - discard conn.addTodo(6, "Learn a functional programming language") - echo("Generated some entries, they were added to your database.") - -proc listDatabaseContents(conn: DbConn; listParams: PagedParams) = - ## Dumps the database contents formatted to the standard output. - ## - ## Pass the list/filter parameters parsed from the commandline. - var params = listParams - params.pageSize = -1 - let todos = conn.getPagedTodos(params) - if todos.len < 1: - echo("Database empty") - return - echo("Todo id, is done, priority, last modification date, text:") - # First detect how long should be our columns for formatting. - var cols: array[0..2, int] - for todo in todos: - cols[0] = max(cols[0], ($todo.getId).len) - cols[1] = max(cols[1], ($todo.priority).len) - cols[2] = max(cols[2], ($todo.getModificationDate).len) - # Now dump all the rows using the calculated alignment sizes. - for todo in todos: - echo("$1 $2 $3, $4, $5" % [ - ($todo.getId).align(cols[0]), - if todo.isDone: "[X]" else: "[-]", - ($todo.priority).align(cols[1]), - ($todo.getModificationDate).align(cols[2]), - todo.text]) - -proc deleteOneTodo(conn: DbConn; todoId: int64) = - ## Deletes a single todo entry from the database. - let numDeleted = conn.deleteTodo(todoId) - if numDeleted > 0: - echo("Deleted todo id " & $todoId) - else: - quit("Couldn't delete todo id " & $todoId, 11) - -proc deleteAllTodos(conn: DbConn) = - ## Deletes all the contents from the database. - ## - ## Note that it would be more optimal to issue a direct DELETE sql statement - ## on the database, but for the sake of the example we will restrict - ## ourselfves to the API exported by backend. - var - counter: int64 - params: PagedParams - params.initDefaults - params.pageSize = -1 - params.showUnchecked = true - params.showChecked = true - let todos = conn.getPagedTodos(params) - for todo in todos: - if conn.deleteTodo(todo.getId) > 0: - counter += 1 - else: - quit("Couldn't delete todo id " & $todo.getId, 12) - echo("Deleted $1 todo entries from database." % $counter) - -proc setTodoCheck(conn: DbConn; todoId: int64; value: bool) = - ## Changes the check state of a todo entry to the specified value. - let - newState = if value: "checked" else: "unchecked" - todo = conn.getTodo(todoId) - if todo == nil: - quit("Can't modify todo id $1, its not in the database." % $todoId, 13) - if todo[].isDone == value: - echo("Todo id $1 was already set to $2." % [$todoId, newState]) - return - todo[].isDone = value - if todo[].save(conn): - echo("Todo id $1 set to $2." % [$todoId, newState]) - else: - quit("Error updating todo id $1 to $2." % [$todoId, newState]) - -proc addTodo(conn: DbConn; priority: int; tokens: seq[string]) = - ## Adds to the database a todo with the specified priority. - ## - ## The tokens are joined as a single string using the space character. The - ## created id will be displayed to the user. - let todo = conn.addTodo(priority, tokens.join(" ")) - echo("Created todo entry with id:$1 for priority $2 and text '$3'." % [ - $todo.getId, $todo.priority, todo.text]) - -when isMainModule: - ## Main entry point. - let - opt = parseCmdLine() - dbPath = getConfigDir() / "nimtodo.sqlite3" - if not dbPath.existsFile: - createDir(getConfigDir()) - echo("No database found at $1, it will be created for you." % dbPath) - let conn = openDatabase(dbPath) - try: - case opt.command - of cmdAdd: addTodo(conn, opt.addPriority, opt.addText) - of cmdCheck: setTodoCheck(conn, opt.todoId, true) - of cmdUncheck: setTodoCheck(conn, opt.todoId, false) - of cmdDelete: deleteOneTodo(conn, opt.todoId) - of cmdNuke: deleteAllTodos(conn) - of cmdGenerate: generateDatabaseRows(conn) - of cmdList: listDatabaseContents(conn, opt.listParams) - finally: - conn.close diff --git a/examples/cross_todo/nim_commandline/readme.txt b/examples/cross_todo/nim_commandline/readme.txt deleted file mode 100644 index 7d68bbc8b..000000000 --- a/examples/cross_todo/nim_commandline/readme.txt +++ /dev/null @@ -1,19 +0,0 @@ -This directory contains the Nim commandline version of the todo cross -platform example. - -The commandline interface can be used only through switches, running the binary -once will spit out the basic help. The commands you can use are the typical on -such an application: add, check/uncheck and delete (further could be added, -like modification at expense of parsing/option complexity). The list command is -the only one which dumps the contents of the database. The output can be -filtered and sorted through additional parameters. - -When you run the program for the first time the todo database will be generated -in your user's data directory. To cope with an empty database, a special -generation switch can be used to fill the database with some basic todo entries -you can play with. - -Compilation is fairly easy despite having the source split in different -directories. Thanks to the nim.cfg file, which adds the ../Nim_backend -directory as a search path, you can compile and run the example just fine from -the command line with 'nim c -r nimtodo.nim'. diff --git a/examples/cross_todo/readme.txt b/examples/cross_todo/readme.txt deleted file mode 100644 index 44e8c47aa..000000000 --- a/examples/cross_todo/readme.txt +++ /dev/null @@ -1,5 +0,0 @@ -This cross platform todo illustrates how to use Nim to create a backend -called by different native user interfaces. - -This example builds on the knowledge learned from the cross_calculator example. -Check it out first to learn how to set up Nim on different platforms. diff --git a/examples/debugging.nim b/examples/debugging.nim deleted file mode 100644 index 89cdd3b2a..000000000 --- a/examples/debugging.nim +++ /dev/null @@ -1,17 +0,0 @@ -# Simple program to test the debugger -# compile with --debugger:on - -proc someComp(x, y: int): int = - let a = x+y - if a > 7: - let b = a*90 - {.breakpoint.} - result = b - {.breakpoint.} - -proc pp() = - var aa = 45 - var bb = "abcdef" - echo someComp(23, 45) - -pp() diff --git a/examples/keyval2.nim b/examples/extract_keyval_pairs_pegs.nim index 2a5643276..2a5643276 100644 --- a/examples/keyval2.nim +++ b/examples/extract_keyval_pairs_pegs.nim diff --git a/examples/keyval.nim b/examples/extract_keyval_pairs_re.nim index a594c0fa8..a594c0fa8 100644 --- a/examples/keyval.nim +++ b/examples/extract_keyval_pairs_re.nim diff --git a/examples/filterex.nim b/examples/filterex.nim deleted file mode 100644 index 083945254..000000000 --- a/examples/filterex.nim +++ /dev/null @@ -1,23 +0,0 @@ -#? stdtmpl | standard -#proc generateHTMLPage(title, currentTab, content: string, -# tabs: openArray[string]): string = -# result = "" -<head><title>$title</title></head> -<body> - <div id="menu"> - <ul> - #for tab in items(tabs): - #if currentTab == tab: - <li><a id="selected" - #else: - <li><a - #end if - href="${tab}.html" title = "$title - $tab">$tab</a></li> - #end for - </ul> - </div> - <div id="content"> - $content - A dollar: $$. - </div> -</body> diff --git a/examples/fizzbuzz.nim b/examples/fizzbuzz.nim deleted file mode 100644 index 4b203512c..000000000 --- a/examples/fizzbuzz.nim +++ /dev/null @@ -1,14 +0,0 @@ -# Fizz Buzz program - -const f = "Fizz" -const b = "Buzz" -for i in 1..100: - if i mod 15 == 0: - echo f, b - elif i mod 5 == 0: - echo b - elif i mod 3 == 0: - echo f - else: - echo i - diff --git a/examples/htmlrefs.nim b/examples/htmlrefs.nim deleted file mode 100644 index 394932773..000000000 --- a/examples/htmlrefs.nim +++ /dev/null @@ -1,57 +0,0 @@ -# Example program to show the new parsexml module -# This program reads an HTML file and writes all its used links to stdout. -# Errors and whitespace are ignored. - -import os, streams, parsexml, strutils - -proc `=?=` (a, b: string): bool = - # little trick: define our own comparator that ignores case - return cmpIgnoreCase(a, b) == 0 - -if paramCount() < 1: - quit("Usage: htmlrefs filename[.html]") - -var links = 0 # count the number of links -var filename = addFileExt(paramStr(1), "html") -var s = newFileStream(filename, fmRead) -if s == nil: quit("cannot open the file " & filename) -var x: XmlParser -open(x, s, filename) -next(x) # get first event -block mainLoop: - while true: - case x.kind - of xmlElementOpen: - # the <a href = "xyz"> tag we are interested in always has an attribute, - # thus we search for ``xmlElementOpen`` and not for ``xmlElementStart`` - if x.elementName =?= "a": - x.next() - if x.kind == xmlAttribute: - if x.attrKey =?= "href": - var link = x.attrValue - inc(links) - # skip until we have an ``xmlElementClose`` event - while true: - x.next() - case x.kind - of xmlEof: break mainLoop - of xmlElementClose: break - else: discard - x.next() # skip ``xmlElementClose`` - # now we have the description for the ``a`` element - var desc = "" - while x.kind == xmlCharData: - desc.add(x.charData) - x.next() - echo(desc & ": " & link) - else: - x.next() - of xmlEof: break # end of file reached - of xmlError: - echo(errorMsg(x)) - x.next() - else: x.next() # skip other events - -echo($links & " link(s) found!") -x.close() - diff --git a/examples/htmltitle.nim b/examples/htmltitle.nim deleted file mode 100644 index 96bfc7d91..000000000 --- a/examples/htmltitle.nim +++ /dev/null @@ -1,36 +0,0 @@ -# Example program to show the parsexml module -# This program reads an HTML file and writes its title to stdout. -# Errors and whitespace are ignored. - -import os, streams, parsexml, strutils - -if paramCount() < 1: - quit("Usage: htmltitle filename[.html]") - -var filename = addFileExt(paramStr(1), "html") -var s = newFileStream(filename, fmRead) -if s == nil: quit("cannot open the file " & filename) -var x: XmlParser -open(x, s, filename) -while true: - x.next() - case x.kind - of xmlElementStart: - if cmpIgnoreCase(x.elementName, "title") == 0: - var title = "" - x.next() # skip "<title>" - while x.kind == xmlCharData: - title.add(x.charData) - x.next() - if x.kind == xmlElementEnd and cmpIgnoreCase(x.elementName, "title") == 0: - echo("Title: " & title) - quit(0) # Success! - else: - echo(x.errorMsgExpected("/title")) - - of xmlEof: break # end of file reached - else: discard # ignore other events - -x.close() -quit("Could not determine title!") - diff --git a/examples/httpserver2.nim b/examples/httpserver2.nim deleted file mode 100644 index 050684d3e..000000000 --- a/examples/httpserver2.nim +++ /dev/null @@ -1,247 +0,0 @@ -import strutils, os, osproc, strtabs, streams, sockets - -const - wwwNL* = "\r\L" - ServerSig = "Server: httpserver.nim/1.0.0" & wwwNL - -type - TRequestMethod = enum reqGet, reqPost - TServer* = object ## contains the current server state - s: Socket - job: seq[TJob] - TJob* = object - client: Socket - process: Process - -# --------------- output messages -------------------------------------------- - -proc sendTextContentType(client: Socket) = - send(client, "Content-type: text/html" & wwwNL) - send(client, wwwNL) - -proc badRequest(client: Socket) = - # Inform the client that a request it has made has a problem. - send(client, "HTTP/1.0 400 BAD REQUEST" & wwwNL) - sendTextContentType(client) - send(client, "<p>Your browser sent a bad request, " & - "such as a POST without a Content-Length.</p>" & wwwNL) - - -proc cannotExec(client: Socket) = - send(client, "HTTP/1.0 500 Internal Server Error" & wwwNL) - sendTextContentType(client) - send(client, "<P>Error prohibited CGI execution.</p>" & wwwNL) - - -proc headers(client: Socket, filename: string) = - # XXX could use filename to determine file type - send(client, "HTTP/1.0 200 OK" & wwwNL) - send(client, ServerSig) - sendTextContentType(client) - -proc notFound(client: Socket, path: string) = - send(client, "HTTP/1.0 404 NOT FOUND" & wwwNL) - send(client, ServerSig) - sendTextContentType(client) - send(client, "<html><title>Not Found</title>" & wwwNL) - send(client, "<body><p>The server could not fulfill" & wwwNL) - send(client, "your request because the resource <b>" & path & "</b>" & wwwNL) - send(client, "is unavailable or nonexistent.</p>" & wwwNL) - send(client, "</body></html>" & wwwNL) - - -proc unimplemented(client: Socket) = - send(client, "HTTP/1.0 501 Method Not Implemented" & wwwNL) - send(client, ServerSig) - sendTextContentType(client) - send(client, "<html><head><title>Method Not Implemented" & - "</title></head>" & - "<body><p>HTTP request method not supported.</p>" & - "</body></HTML>" & wwwNL) - - -# ----------------- file serving --------------------------------------------- - -proc discardHeaders(client: Socket) = skip(client) - -proc serveFile(client: Socket, filename: string) = - discardHeaders(client) - - var f: File - if open(f, filename): - headers(client, filename) - const bufSize = 8000 # != 8K might be good for memory manager - var buf = alloc(bufsize) - while true: - var bytesread = readBuffer(f, buf, bufsize) - if bytesread > 0: - var byteswritten = send(client, buf, bytesread) - if bytesread != bytesWritten: - let err = osLastError() - dealloc(buf) - close(f) - raiseOSError(err) - if bytesread != bufSize: break - dealloc(buf) - close(f) - client.close() - else: - notFound(client, filename) - -# ------------------ CGI execution ------------------------------------------- - -proc executeCgi(server: var TServer, client: Socket, path, query: string, - meth: TRequestMethod) = - var env = newStringTable(modeCaseInsensitive) - var contentLength = -1 - case meth - of reqGet: - discardHeaders(client) - - env["REQUEST_METHOD"] = "GET" - env["QUERY_STRING"] = query - of reqPost: - var buf = "" - var dataAvail = true - while dataAvail: - dataAvail = recvLine(client, buf) - if buf.len == 0: - break - var L = toLower(buf) - if L.startsWith("content-length:"): - var i = len("content-length:") - while L[i] in Whitespace: inc(i) - contentLength = parseInt(substr(L, i)) - - if contentLength < 0: - badRequest(client) - return - - env["REQUEST_METHOD"] = "POST" - env["CONTENT_LENGTH"] = $contentLength - - send(client, "HTTP/1.0 200 OK" & wwwNL) - - var process = startProcess(command=path, env=env) - - var job: TJob - job.process = process - job.client = client - server.job.add(job) - - if meth == reqPost: - # get from client and post to CGI program: - var buf = alloc(contentLength) - if recv(client, buf, contentLength) != contentLength: - let err = osLastError() - dealloc(buf) - raiseOSError(err) - var inp = process.inputStream - inp.writeData(buf, contentLength) - dealloc(buf) - -proc animate(server: var TServer) = - # checks list of jobs, removes finished ones (pretty sloppy by seq copying) - var active_jobs: seq[TJob] = @[] - for i in 0..server.job.len-1: - var job = server.job[i] - if running(job.process): - active_jobs.add(job) - else: - # read process output stream and send it to client - var outp = job.process.outputStream - while true: - var line = outp.readstr(1024) - if line.len == 0: - break - else: - try: - send(job.client, line) - except: - echo("send failed, client diconnected") - close(job.client) - - server.job = active_jobs - -# --------------- Server Setup ----------------------------------------------- - -proc acceptRequest(server: var TServer, client: Socket) = - var cgi = false - var query = "" - var buf = "" - discard recvLine(client, buf) - var path = "" - var data = buf.split() - var meth = reqGet - var q = find(data[1], '?') - - # extract path - if q >= 0: - # strip "?..." from path, this may be found in both POST and GET - path = data[1].substr(0, q-1) - else: - path = data[1] - # path starts with "/", by adding "." in front of it we serve files from cwd - path = "." & path - - echo("accept: " & path) - - if cmpIgnoreCase(data[0], "GET") == 0: - if q >= 0: - cgi = true - query = data[1].substr(q+1) - elif cmpIgnoreCase(data[0], "POST") == 0: - cgi = true - meth = reqPost - else: - unimplemented(client) - - if path[path.len-1] == '/' or existsDir(path): - path = path / "index.html" - - if not existsFile(path): - discardHeaders(client) - notFound(client, path) - client.close() - else: - when defined(Windows): - var ext = splitFile(path).ext.toLower - if ext == ".exe" or ext == ".cgi": - # XXX: extract interpreter information here? - cgi = true - else: - if {fpUserExec, fpGroupExec, fpOthersExec} * path.getFilePermissions != {}: - cgi = true - if not cgi: - serveFile(client, path) - else: - executeCgi(server, client, path, query, meth) - -when isMainModule: - var port = 80 - - var server: TServer - server.job = @[] - server.s = socket(AF_INET) - if server.s == invalidSocket: raiseOSError(osLastError()) - server.s.bindAddr(port=Port(port)) - listen(server.s) - echo("server up on port " & $port) - - while true: - # check for new new connection & handle it - var list: seq[Socket] = @[server.s] - if select(list, 10) > 0: - var client: Socket - new(client) - accept(server.s, client) - try: - acceptRequest(server, client) - except: - echo("failed to accept client request") - - # pooling events - animate(server) - # some slack for CPU - sleep(10) - server.s.close() diff --git a/examples/maximum.nim b/examples/maximum.nim index 6552a8144..3c43a48c9 100644 --- a/examples/maximum.nim +++ b/examples/maximum.nim @@ -1,4 +1,4 @@ -# Test high level features +# Shows how the method call syntax can be used to chain calls conveniently. import strutils, sequtils diff --git a/examples/objciface/gnustepex.nim b/examples/objciface/gnustepex.nim deleted file mode 100644 index d961d3087..000000000 --- a/examples/objciface/gnustepex.nim +++ /dev/null @@ -1,40 +0,0 @@ -# horrible example of how to interface with GNUStep ... - -{.passL: "-lobjc".} -{.emit: """ - -#include <objc/Object.h> - -@interface Greeter:Object -{ -} - -- (void)greet:(long)x y:(long)dummy; - -@end - -#include <stdio.h> - -@implementation Greeter - -- (void)greet:(long)x y:(long)dummy -{ - printf("Hello, World!\n"); -} - -@end - -#include <stdlib.h> -""".} - -type - TId {.importc: "id", header: "<objc/Object.h>", final.} = distinct int - -proc newGreeter: TId {.importobjc: "Greeter new", nodecl.} -proc greet(self: TId, x, y: int) {.importobjc: "greet", nodecl.} -proc free(self: TId) {.importobjc: "free", nodecl.} - -var g = newGreeter() -g.greet(12, 34) -g.free() - diff --git a/examples/parsecfgex.nim b/examples/parsecfgex.nim deleted file mode 100644 index 0fa03ffb5..000000000 --- a/examples/parsecfgex.nim +++ /dev/null @@ -1,25 +0,0 @@ - -import - os, parsecfg, strutils, streams - -var f = newFileStream(paramStr(1), fmRead) -if f != nil: - var p: CfgParser - open(p, f, paramStr(1)) - while true: - var e = next(p) - case e.kind - of cfgEof: - echo("EOF!") - break - of cfgSectionStart: ## a ``[section]`` has been parsed - echo("new section: " & e.section) - of cfgKeyValuePair: - echo("key-value-pair: " & e.key & ": " & e.value) - of cfgOption: - echo("command: " & e.key & ": " & e.value) - of cfgError: - echo(e.msg) - close(p) -else: - echo("cannot open: " & paramStr(1)) diff --git a/examples/readme.txt b/examples/readme.txt index 176bc8239..42446faea 100644 --- a/examples/readme.txt +++ b/examples/readme.txt @@ -1,5 +1,2 @@ -In this directory you will find several examples for how to use the Nim +In this directory you can find several examples for how to use the Nim library. - -Copyright (c) 2004-2012 Andreas Rumpf. -All rights reserved. diff --git a/examples/ssl/extradata.nim b/examples/ssl/extradata.nim deleted file mode 100644 index 1e3b89b02..000000000 --- a/examples/ssl/extradata.nim +++ /dev/null @@ -1,26 +0,0 @@ -# Stores extra data inside the SSL context. -import net - -let ctx = newContext() - -# Our unique index for storing foos -let fooIndex = ctx.getExtraDataIndex() -# And another unique index for storing foos -let barIndex = ctx.getExtraDataIndex() -echo "got indexes ", fooIndex, " ", barIndex - -try: - discard ctx.getExtraData(fooIndex) - assert false -except IndexError: - echo("Success") - -type - FooRef = ref object of RootRef - foo: int - -let foo = FooRef(foo: 5) -ctx.setExtraData(fooIndex, foo) -doAssert ctx.getExtraData(fooIndex).FooRef == foo - -ctx.destroyContext() diff --git a/examples/ssl/pskclient.nim b/examples/ssl/pskclient.nim deleted file mode 100644 index c83f27fbc..000000000 --- a/examples/ssl/pskclient.nim +++ /dev/null @@ -1,16 +0,0 @@ -# Create connection encrypted using preshared key (TLS-PSK). -import net - -static: assert defined(ssl) - -let sock = newSocket() -sock.connect("localhost", Port(8800)) - -proc clientFunc(identityHint: string): tuple[identity: string, psk: string] = - echo "identity hint ", identityHint.repr - return ("foo", "psk-of-foo") - -let context = newContext(cipherList="PSK-AES256-CBC-SHA") -context.clientGetPskFunc = clientFunc -context.wrapConnectedSocket(sock, handshakeAsClient) -context.destroyContext() diff --git a/examples/ssl/pskserver.nim b/examples/ssl/pskserver.nim deleted file mode 100644 index 859eaa875..000000000 --- a/examples/ssl/pskserver.nim +++ /dev/null @@ -1,20 +0,0 @@ -# Accept connection encrypted using preshared key (TLS-PSK). -import net - -static: assert defined(ssl) - -let sock = newSocket() -sock.bindAddr(Port(8800)) -sock.listen() - -let context = newContext(cipherList="PSK-AES256-CBC-SHA") -context.pskIdentityHint = "hello" -context.serverGetPskFunc = proc(identity: string): string = "psk-of-" & identity - -while true: - var client = new(Socket) - sock.accept(client) - sock.setSockOpt(OptReuseAddr, true) - echo "accepted connection" - context.wrapConnectedSocket(client, handshakeAsServer) - echo "got connection with identity ", client.getPskIdentity() diff --git a/examples/transff.nim b/examples/transff.nim deleted file mode 100644 index 32d17e52c..000000000 --- a/examples/transff.nim +++ /dev/null @@ -1,8 +0,0 @@ -# Shows how to transform a file - -import pegs - -transformFile("infile.txt", "outfile.txt", - [(peg"""S <- {typedesc} \s* {\ident} \s* ',' - typedesc <- \ident '*'* """, r"$2: $1")]) - diff --git a/examples/tunit.nim b/examples/tunit.nim index d7b1fcbbd..785b9aa5e 100644 --- a/examples/tunit.nim +++ b/examples/tunit.nim @@ -28,7 +28,7 @@ proc foo: bool = return true proc err = - raise newException(EArithmetic, "some exception") + raise newException(ArithmeticError, "some exception") test "final test": echo "inside suite-less test" @@ -39,9 +39,9 @@ test "final test": d > 10 test "arithmetic failure": - expect(EArithmetic): + expect(ArithmeticError): err() - expect(EArithmetic, ESystem): + expect(ArithmeticError, CatchableError): discard foo() diff --git a/examples/unix_socket/client.nim b/examples/unix_socket/client.nim deleted file mode 100644 index f4283d64d..000000000 --- a/examples/unix_socket/client.nim +++ /dev/null @@ -1,6 +0,0 @@ -import net - -let sock = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP) - -sock.connectUnix("sock") -sock.send("hello\n") diff --git a/examples/unix_socket/server.nim b/examples/unix_socket/server.nim deleted file mode 100644 index e798bbb48..000000000 --- a/examples/unix_socket/server.nim +++ /dev/null @@ -1,14 +0,0 @@ -import net - -let sock = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP) -sock.bindUnix("sock") -sock.listen() - -while true: - var client = new(Socket) - sock.accept(client) - var output = "" - output.setLen 32 - client.readLine(output) - echo "got ", output - client.close() diff --git a/koch.nim b/koch.nim index d51b902ee..c302be4ca 100644 --- a/koch.nim +++ b/koch.nim @@ -47,17 +47,18 @@ Boot options: -d:release produce a release version of the compiler -d:useLinenoise use the linenoise library for interactive mode (not needed on Windows) - -d:avoidTimeMachine only for Mac OS X, excludes nimcache dir from backups Commands for core developers: web [options] generates the website and the full documentation + (see `nimweb.nim` for cmd line options) website [options] generates only the website csource -d:release builds the C sources for installation pdf builds the PDF documentation zip builds the installation zip package xz builds the installation tar.xz package testinstall test tar.xz package; Unix only! - tests [options] run the testsuite + tests [options] run the testsuite (run a subset of tests by + specifying a category, e.g. `tests cat async`) temp options creates a temporary compiler for testing winrelease creates a Windows release pushcsource push generated C sources to its repo @@ -182,7 +183,7 @@ proc bundleNimbleExe() = bundleNimbleSrc() # now compile Nimble and copy it to $nim/bin for the installer.ini # to pick it up: - nimexec("c -d:release dist/nimble/src/nimble.nim") + nimexec("c -d:release --nilseqs:on dist/nimble/src/nimble.nim") copyExe("dist/nimble/src/nimble".exe, "bin/nimble".exe) proc buildNimble(latest: bool) = @@ -209,7 +210,7 @@ proc buildNimble(latest: bool) = else: exec("git checkout -f stable") exec("git pull") - nimexec("c --noNimblePath -p:compiler -d:release " & installDir / "src/nimble.nim") + nimexec("c --noNimblePath -p:compiler --nilseqs:on -d:release " & installDir / "src/nimble.nim") copyExe(installDir / "src/nimble".exe, "bin/nimble".exe) proc bundleNimsuggest(buildExe: bool) = @@ -242,7 +243,15 @@ proc zip(args: string) = exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim zip compiler/installer.ini" % ["tools/niminst/niminst".exe, VersionAsString]) +proc ensureCleanGit() = + let (outp, status) = osproc.execCmdEx("git diff") + if outp.len != 0: + quit "Not a clean git repository; 'git diff' not empty!" + if status != 0: + quit "Not a clean git repository; 'git diff' returned non-zero!" + proc xz(args: string) = + ensureCleanGit() bundleNimbleSrc() bundleNimsuggest(false) nimexec("cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" % @@ -252,18 +261,16 @@ proc xz(args: string) = proc buildTool(toolname, args: string) = nimexec("cc $# $#" % [args, toolname]) - copyFile(dest="bin"/ splitFile(toolname).name.exe, source=toolname.exe) + copyFile(dest="bin" / splitFile(toolname).name.exe, source=toolname.exe) proc buildTools(latest: bool) = - let nimsugExe = "bin/nimsuggest".exe - nimexec "c --noNimblePath -p:compiler -d:release -o:" & nimsugExe & + nimexec "c --noNimblePath -p:compiler -d:release -o:" & ("bin/nimsuggest".exe) & " nimsuggest/nimsuggest.nim" - let nimgrepExe = "bin/nimgrep".exe - nimexec "c -d:release -o:" & nimgrepExe & " tools/nimgrep.nim" + nimexec "c -d:release -o:" & ("bin/nimgrep".exe) & " tools/nimgrep.nim" when defined(windows): buildVccTool() - #nimexec "c -o:" & ("bin/nimresolve".exe) & " tools/nimresolve.nim" + nimexec "c -o:" & ("bin/nimpretty".exe) & " nimpretty/nimpretty.nim" buildNimble(latest) @@ -473,7 +480,7 @@ proc temp(args: string) = # commit. let (bootArgs, programArgs) = splitArgs(args) let nimexec = findNim() - exec(nimexec & " c -d:debug " & bootArgs & " compiler" / "nim", 125) + exec(nimexec & " c -d:debug --debugger:native " & bootArgs & " compiler" / "nim", 125) copyExe(output, finalDest) if programArgs.len > 0: exec(finalDest & " " & programArgs) diff --git a/lib/core/allocators.nim b/lib/core/allocators.nim index 62f5e9756..f652f0d85 100644 --- a/lib/core/allocators.nim +++ b/lib/core/allocators.nim @@ -8,28 +8,41 @@ # type + AllocatorFlag* {.pure.} = enum ## flags describing the properties of the allocator + ThreadLocal ## the allocator is thread local only. + ZerosMem ## the allocator always zeros the memory on an allocation Allocator* = ptr object {.inheritable.} alloc*: proc (a: Allocator; size: int; alignment: int = 8): pointer {.nimcall.} dealloc*: proc (a: Allocator; p: pointer; size: int) {.nimcall.} realloc*: proc (a: Allocator; p: pointer; oldSize, newSize: int): pointer {.nimcall.} + deallocAll*: proc (a: Allocator) {.nimcall.} + flags*: set[AllocatorFlag] var - currentAllocator {.threadvar.}: Allocator + localAllocator {.threadvar.}: Allocator + sharedAllocator: Allocator -proc getCurrentAllocator*(): Allocator = - result = currentAllocator +proc getLocalAllocator*(): Allocator = + result = localAllocator -proc setCurrentAllocator*(a: Allocator) = - currentAllocator = a +proc setLocalAllocator*(a: Allocator) = + localAllocator = a -proc alloc*(size: int; alignment: int = 8): pointer = - let a = getCurrentAllocator() - result = a.alloc(a, size, alignment) +proc getSharedAllocator*(): Allocator = + result = sharedAllocator -proc dealloc*(p: pointer; size: int) = - let a = getCurrentAllocator() - a.dealloc(a, p, size) +proc setSharedAllocator*(a: Allocator) = + sharedAllocator = a -proc realloc*(p: pointer; oldSize, newSize: int): pointer = - let a = getCurrentAllocator() - result = a.realloc(a, p, oldSize, newSize) +when false: + proc alloc*(size: int; alignment: int = 8): pointer = + let a = getCurrentAllocator() + result = a.alloc(a, size, alignment) + + proc dealloc*(p: pointer; size: int) = + let a = getCurrentAllocator() + a.dealloc(a, p, size) + + proc realloc*(p: pointer; oldSize, newSize: int): pointer = + let a = getCurrentAllocator() + result = a.realloc(a, p, oldSize, newSize) diff --git a/lib/core/macrocache.nim b/lib/core/macrocache.nim new file mode 100644 index 000000000..bd48b5bd4 --- /dev/null +++ b/lib/core/macrocache.nim @@ -0,0 +1,47 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module provides an API for macros that need to collect compile +## time information across module boundaries in global variables. +## Starting with version 0.19 of Nim this is not directly supported anymore +## as it breaks incremental compilations. +## Instead the API here needs to be used. See XXX (wikipedia page) for a +## theoretical foundation behind this. + +type + CacheSeq* = distinct string + CacheTable* = distinct string + CacheCounter* = distinct string + +proc value*(c: CacheCounter): int {.magic: "NccValue".} +proc inc*(c: CacheCounter; by = 1) {.magic: "NccInc".} + +proc add*(s: CacheSeq; value: NimNode) {.magic: "NcsAdd".} +proc incl*(s: CacheSeq; value: NimNode) {.magic: "NcsIncl".} +proc len*(s: CacheSeq): int {.magic: "NcsLen".} +proc `[]`*(s: CacheSeq; i: int): NimNode {.magic: "NcsAt".} + +iterator items*(s: CacheSeq): NimNode = + for i in 0 ..< len(s): yield s[i] + +proc `[]=`*(t: CacheTable; key: string, value: NimNode) {.magic: "NctPut".} + ## 'key' has to be unique! + +proc len*(t: CacheTable): int {.magic: "NctLen".} +proc `[]`*(t: CacheTable; key: string): NimNode {.magic: "NctGet".} + +proc hasNext(t: CacheTable; iter: int): bool {.magic: "NctHasNext".} +proc next(t: CacheTable; iter: int): (string, NimNode, int) {.magic: "NctNext".} + +iterator pairs*(t: CacheTable): (string, NimNode) = + var h = 0 + while hasNext(t, h): + let (a, b, h2) = next(t, h) + yield (a, b) + h = h2 diff --git a/lib/core/macros.nim b/lib/core/macros.nim index f2a39f43b..aec766068 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -8,12 +8,16 @@ # include "system/inclrtl" +include "system/helpers" ## This module contains the interface to the compiler's abstract syntax ## tree (`AST`:idx:). Macros operate on this tree. ## .. include:: ../../doc/astspec.txt +# If you look for the implementation of the magic symbol +# ``{.magic: "Foo".}``, search for `mFoo` and `opcFoo`. + type NimNodeKind* = enum nnkNone, nnkEmpty, nnkIdent, nnkSym, @@ -76,7 +80,8 @@ type nnkGotoState, nnkState, nnkBreakState, - nnkFuncDef + nnkFuncDef, + nnkTupleConstr NimNodeKinds* = set[NimNodeKind] NimTypeKind* = enum # some types are no longer used, see ast.nim @@ -118,13 +123,10 @@ type ## use ``ident"abc"``. NimSymObj = object # hidden - NimSym* = ref NimSymObj + NimSym* {.deprecated.} = ref NimSymObj ## represents a Nim *symbol* in the compiler; a *symbol* is a looked-up ## *ident*. -{.deprecated: [TNimrodNodeKind: NimNodeKind, TNimNodeKinds: NimNodeKinds, - TNimrodTypeKind: NimTypeKind, TNimrodSymKind: NimSymKind, - TNimrodIdent: NimIdent, PNimrodSymbol: NimSym].} const nnkLiterals* = {nnkCharLit..nnkNilLit} @@ -134,25 +136,23 @@ const proc `!`*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect, deprecated.} ## constructs an identifier from the string `s` - ## **Deprecated since version 0.18.0**: Use ``toNimIdent`` instead. + ## **Deprecated since version 0.18.0**: Use ``ident`` or ``newIdentNode`` instead. -proc toNimIdent*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect.} +proc toNimIdent*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect, deprecated.} ## constructs an identifier from the string `s` + ## **Deprecated since version 0.18.1**; Use ``ident`` or ``newIdentNode`` instead. -proc `$`*(i: NimIdent): string {.magic: "IdentToStr", noSideEffect.} - ## converts a Nim identifier to a string - -proc `$`*(s: NimSym): string {.magic: "IdentToStr", noSideEffect.} - ## converts a Nim symbol to a string - -proc `==`*(a, b: NimIdent): bool {.magic: "EqIdent", noSideEffect.} +proc `==`*(a, b: NimIdent): bool {.magic: "EqIdent", noSideEffect, deprecated.} ## compares two Nim identifiers + ## **Deprecated since version 0.18.1**; Use ``==`` on ``NimNode`` instead. proc `==`*(a, b: NimNode): bool {.magic: "EqNimrodNode", noSideEffect.} ## compares two Nim nodes -proc `==`*(a, b: NimSym): bool {.magic: "EqNimrodNode", noSideEffect.} +proc `==`*(a, b: NimSym): bool {.magic: "EqNimrodNode", noSideEffect, deprecated.} ## compares two Nim symbols + ## **Deprecated since version 0.18.1**; Use ```==`(NimNode,NimNode)`` instead. + proc sameType*(a, b: NimNode): bool {.magic: "SameNodeType", noSideEffect.} = ## compares two Nim nodes' types. Return true if the types are the same, @@ -195,8 +195,53 @@ proc kind*(n: NimNode): NimNodeKind {.magic: "NKind", noSideEffect.} proc intVal*(n: NimNode): BiggestInt {.magic: "NIntVal", noSideEffect.} proc floatVal*(n: NimNode): BiggestFloat {.magic: "NFloatVal", noSideEffect.} -proc symbol*(n: NimNode): NimSym {.magic: "NSymbol", noSideEffect.} -proc ident*(n: NimNode): NimIdent {.magic: "NIdent", noSideEffect.} + +proc ident*(n: NimNode): NimIdent {.magic: "NIdent", noSideEffect, deprecated.} = + ## **Deprecated since version 0.18.1**; All functionality is defined on ``NimNode``. + +proc symbol*(n: NimNode): NimSym {.magic: "NSymbol", noSideEffect, deprecated.} + ## **Deprecated since version 0.18.1**; All functionality is defined on ``NimNode``. + +proc getImpl*(s: NimSym): NimNode {.magic: "GetImpl", noSideEffect, deprecated: "use `getImpl: NimNode -> NimNode` instead".} + +when defined(nimSymKind): + proc symKind*(symbol: NimNode): NimSymKind {.magic: "NSymKind", noSideEffect.} + proc getImpl*(symbol: NimNode): NimNode {.magic: "GetImpl", noSideEffect.} + proc strVal*(n: NimNode): string {.magic: "NStrVal", noSideEffect.} + ## retrieve the implementation of `symbol`. `symbol` can be a + ## routine or a const. + + proc `$`*(i: NimIdent): string {.magic: "NStrVal", noSideEffect, deprecated.} + ## converts a Nim identifier to a string + ## **Deprecated since version 0.18.1**; Use ``strVal`` instead. + + proc `$`*(s: NimSym): string {.magic: "NStrVal", noSideEffect, deprecated.} + ## converts a Nim symbol to a string + ## **Deprecated since version 0.18.1**; Use ``strVal`` instead. + +else: # bootstrapping substitute + proc getImpl*(symbol: NimNode): NimNode = + symbol.symbol.getImpl + + proc strValOld(n: NimNode): string {.magic: "NStrVal", noSideEffect.} + + proc `$`*(s: NimSym): string {.magic: "IdentToStr", noSideEffect.} + + proc `$`*(i: NimIdent): string {.magic: "IdentToStr", noSideEffect.} + + proc strVal*(n: NimNode): string = + if n.kind == nnkIdent: + $n.ident + elif n.kind == nnkSym: + $n.symbol + else: + n.strValOld + +when defined(nimHasSymOwnerInMacro): + proc owner*(sym: NimNode): NimNode {.magic: "SymOwner", noSideEffect.} + ## accepts node of kind nnkSym and returns its owner's symbol. + ## result is also mnde of kind nnkSym if owner exists otherwise + ## nnkNilLit is returned proc getType*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.} ## with 'getType' you can access the node's `type`:idx:. A Nim type is @@ -214,26 +259,65 @@ proc getType*(n: typedesc): NimNode {.magic: "NGetType", noSideEffect.} proc typeKind*(n: NimNode): NimTypeKind {.magic: "NGetType", noSideEffect.} ## Returns the type kind of the node 'n' that should represent a type, that - ## means the node should have been obtained via `getType`. - -proc getTypeInst*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.} - ## Like getType except it includes generic parameters for a specific instance + ## means the node should have been obtained via ``getType``. + +proc getTypeInst*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.} = + ## Returns the `type`:idx: of a node in a form matching the way the + ## type instance was declared in the code. + runnableExamples: + type + Vec[N: static[int], T] = object + arr: array[N, T] + Vec4[T] = Vec[4, T] + Vec4f = Vec4[float32] + var a: Vec4f + var b: Vec4[float32] + var c: Vec[4, float32] + macro dumpTypeInst(x: typed): untyped = + newLit(x.getTypeInst.repr) + doAssert(dumpTypeInst(a) == "Vec4f") + doAssert(dumpTypeInst(b) == "Vec4[float32]") + doAssert(dumpTypeInst(c) == "Vec[4, float32]") proc getTypeInst*(n: typedesc): NimNode {.magic: "NGetType", noSideEffect.} - ## Like getType except it includes generic parameters for a specific instance - -proc getTypeImpl*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.} - ## Like getType except it includes generic parameters for the implementation + ## Version of ``getTypeInst`` which takes a ``typedesc``. + +proc getTypeImpl*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.} = + ## Returns the `type`:idx: of a node in a form matching the implementation + ## of the type. Any intermediate aliases are expanded to arrive at the final + ## type implementation. You can instead use ``getImpl`` on a symbol if you + ## want to find the intermediate aliases. + runnableExamples: + type + Vec[N: static[int], T] = object + arr: array[N, T] + Vec4[T] = Vec[4, T] + Vec4f = Vec4[float32] + var a: Vec4f + var b: Vec4[float32] + var c: Vec[4, float32] + macro dumpTypeImpl(x: typed): untyped = + newLit(x.getTypeImpl.repr) + let t = """ +object + arr: array[0 .. 3, float32] +""" + doAssert(dumpTypeImpl(a) == t) + doAssert(dumpTypeImpl(b) == t) + doAssert(dumpTypeImpl(c) == t) proc getTypeImpl*(n: typedesc): NimNode {.magic: "NGetType", noSideEffect.} - ## Like getType except it includes generic parameters for the implementation - -proc strVal*(n: NimNode): string {.magic: "NStrVal", noSideEffect.} + ## Version of ``getTypeImpl`` which takes a ``typedesc``. proc `intVal=`*(n: NimNode, val: BiggestInt) {.magic: "NSetIntVal", noSideEffect.} proc `floatVal=`*(n: NimNode, val: BiggestFloat) {.magic: "NSetFloatVal", noSideEffect.} -proc `symbol=`*(n: NimNode, val: NimSym) {.magic: "NSetSymbol", noSideEffect.} -proc `ident=`*(n: NimNode, val: NimIdent) {.magic: "NSetIdent", noSideEffect.} + +proc `symbol=`*(n: NimNode, val: NimSym) {.magic: "NSetSymbol", noSideEffect, deprecated.} + ## **Deprecated since version 0.18.1**; Generate a new ``NimNode`` with ``genSym`` instead. + +proc `ident=`*(n: NimNode, val: NimIdent) {.magic: "NSetIdent", noSideEffect, deprecated.} + ## **Deprecated since version 0.18.1**; Generate a new ``NimNode`` with ``ident(string)`` instead. + #proc `typ=`*(n: NimNode, typ: typedesc) {.magic: "NSetType".} # this is not sound! Unfortunately forbidding 'typ=' is not enough, as you # can easily do: @@ -255,18 +339,13 @@ proc newNimNode*(kind: NimNodeKind, proc copyNimNode*(n: NimNode): NimNode {.magic: "NCopyNimNode", noSideEffect.} proc copyNimTree*(n: NimNode): NimNode {.magic: "NCopyNimTree", noSideEffect.} -proc getImpl*(s: NimSym): NimNode {.magic: "GetImpl", noSideEffect.} = - ## retrieve the implementation of a symbol `s`. `s` can be a routine or a - ## const. - discard - proc error*(msg: string, n: NimNode = nil) {.magic: "NError", benign.} ## writes an error message at compile time -proc warning*(msg: string) {.magic: "NWarning", benign.} +proc warning*(msg: string, n: NimNode = nil) {.magic: "NWarning", benign.} ## writes a warning message at compile time -proc hint*(msg: string) {.magic: "NHint", benign.} +proc hint*(msg: string, n: NimNode = nil) {.magic: "NHint", benign.} ## writes a hint message at compile time proc newStrLitNode*(s: string): NimNode {.compileTime, noSideEffect.} = @@ -294,11 +373,9 @@ proc newIdentNode*(i: NimIdent): NimNode {.compileTime.} = result = newNimNode(nnkIdent) result.ident = i -proc newIdentNode*(i: string): NimNode {.compileTime.} = - ## creates an identifier node from `i` - result = newNimNode(nnkIdent) - result.ident = toNimIdent i - +proc newIdentNode*(i: string): NimNode {.magic: "StrToIdent", noSideEffect.} + ## creates an identifier node from `i`. It is simply an alias for + ## ``ident(string)``. Use that, it's shorter. type BindSymRule* = enum ## specifies how ``bindSym`` behaves @@ -312,24 +389,34 @@ type {.deprecated: [TBindSymRule: BindSymRule].} -proc bindSym*(ident: string, rule: BindSymRule = brClosed): NimNode {. +proc bindSym*(ident: string | NimNode, rule: BindSymRule = brClosed): NimNode {. magic: "NBindSym", noSideEffect.} ## creates a node that binds `ident` to a symbol node. The bound symbol ## may be an overloaded symbol. + ## if `ident` is a NimNode, it must have nkIdent kind. ## If ``rule == brClosed`` either an ``nkClosedSymChoice`` tree is ## returned or ``nkSym`` if the symbol is not ambiguous. ## If ``rule == brOpen`` either an ``nkOpenSymChoice`` tree is ## returned or ``nkSym`` if the symbol is not ambiguous. ## If ``rule == brForceOpen`` always an ``nkOpenSymChoice`` tree is ## returned even if the symbol is not ambiguous. + ## + ## experimental feature: + ## use {.experimental: "dynamicBindSym".} to activate it + ## if called from template / regular code, `ident` and `rule` must be + ## constant expression / literal value. + ## if called from macros / compile time procs / static blocks, + ## `ident` and `rule` can be VM computed value. proc genSym*(kind: NimSymKind = nskLet; ident = ""): NimNode {. magic: "NGenSym", noSideEffect.} ## generates a fresh symbol that is guaranteed to be unique. The symbol ## needs to occur in a declaration context. -proc callsite*(): NimNode {.magic: "NCallSite", benign.} +proc callsite*(): NimNode {.magic: "NCallSite", benign, + deprecated: "use varargs[untyped] in the macro prototype instead".} ## returns the AST of the invocation expression that invoked this macro. + ## **Deprecated since version 0.18.1**. proc toStrLit*(n: NimNode): NimNode {.compileTime.} = ## converts the AST `n` to the concrete Nim code and wraps that @@ -342,7 +429,8 @@ type line*,column*: int proc `$`*(arg: Lineinfo): string = - result = arg.filename & "(" & $arg.line & ", " & $arg.column & ")" + # BUG: without `result = `, gives compile error + result = lineInfoToString(arg.filename, arg.line, arg.column) #proc lineinfo*(n: NimNode): LineInfo {.magic: "NLineInfo", noSideEffect.} ## returns the position the node appears in the original source file @@ -352,7 +440,11 @@ proc getLine(arg: NimNode): int {.magic: "NLineInfo", noSideEffect.} proc getColumn(arg: NimNode): int {.magic: "NLineInfo", noSideEffect.} proc getFile(arg: NimNode): string {.magic: "NLineInfo", noSideEffect.} +proc copyLineInfo*(arg: NimNode, info: NimNode) {.magic: "NLineInfo", noSideEffect.} + ## copy lineinfo from info node + proc lineInfoObj*(n: NimNode): LineInfo {.compileTime.} = + ## returns ``LineInfo`` of ``n``, using absolute path for ``filename`` result.filename = n.getFile result.line = n.getLine result.column = n.getColumn @@ -464,9 +556,11 @@ proc newCall*(theProc: NimNode, result.add(args) proc newCall*(theProc: NimIdent, - args: varargs[NimNode]): NimNode {.compileTime.} = + args: varargs[NimNode]): NimNode {.compileTime, deprecated.} = ## produces a new call node. `theProc` is the proc that is called with ## the arguments ``args[0..]``. + ## **Deprecated since version 0.18.1**; Use ``newCall(string, ...)``, + ## or ``newCall(NimNode, ...)`` instead. result = newNimNode(nnkCall) result.add(newIdentNode(theProc)) result.add(args) @@ -594,17 +688,30 @@ proc newLit*(s: string): NimNode {.compileTime.} = result = newNimNode(nnkStrLit) result.strVal = s -proc nestList*(theProc: NimIdent, - x: NimNode): NimNode {.compileTime.} = - ## nests the list `x` into a tree of call expressions: - ## ``[a, b, c]`` is transformed into ``theProc(a, theProc(c, d))``. +proc nestList*(op: NimNode; pack: NimNode): NimNode {.compileTime.} = + ## nests the list `pack` into a tree of call expressions: + ## ``[a, b, c]`` is transformed into ``op(a, op(c, d))``. + ## This is also known as fold expression. + if pack.len < 1: + error("`nestList` expects a node with at least 1 child") + result = pack[^1] + for i in countdown(pack.len - 2, 0): + result = newCall(op, pack[i], result) + +proc nestList*(op: NimNode; pack: NimNode; init: NimNode): NimNode {.compileTime.} = + ## nests the list `pack` into a tree of call expressions: + ## ``[a, b, c]`` is transformed into ``op(a, op(c, d))``. + ## This is also known as fold expression. + result = init + for i in countdown(pack.len - 1, 0): + result = newCall(op, pack[i], result) + +proc nestList*(theProc: NimIdent, x: NimNode): NimNode {.compileTime, deprecated.} = + ## **Deprecated since version 0.18.1**; Use one of ``nestList(NimNode, ...)`` instead. var L = x.len result = newCall(theProc, x[L-2], x[L-1]) for i in countdown(L-3, 0): - # XXX the 'copyNimTree' here is necessary due to a bug in the evaluation - # engine that would otherwise create an endless loop here. :-( - # This could easily user code and so should be fixed in evals.nim somehow. - result = newCall(theProc, x[i], copyNimTree(result)) + result = newCall(theProc, x[i], result) proc treeRepr*(n: NimNode): string {.compileTime, benign.} = ## Convert the AST `n` to a human-readable tree-like string. @@ -616,13 +723,11 @@ proc treeRepr*(n: NimNode): string {.compileTime, benign.} = res.add(($n.kind).substr(3)) case n.kind - of nnkEmpty: discard # same as nil node in this representation - of nnkNilLit: res.add(" nil") + of nnkEmpty, nnkNilLit: discard # same as nil node in this representation of nnkCharLit..nnkInt64Lit: res.add(" " & $n.intVal) of nnkFloatLit..nnkFloat64Lit: res.add(" " & $n.floatVal) - of nnkStrLit..nnkTripleStrLit: res.add(" " & $n.strVal) - of nnkIdent: res.add(" ident\"" & $n.ident & '"') - of nnkSym: res.add(" \"" & $n.symbol & '"') + of nnkStrLit..nnkTripleStrLit, nnkIdent, nnkSym: + res.add(" " & $n.strVal.newLit.repr) of nnkNone: assert false else: for j in 0..n.len-1: @@ -641,13 +746,11 @@ proc lispRepr*(n: NimNode): string {.compileTime, benign.} = add(result, "(") case n.kind - of nnkEmpty: discard # same as nil node in this representation - of nnkNilLit: add(result, "nil") + of nnkEmpty, nnkNilLit: discard # same as nil node in this representation of nnkCharLit..nnkInt64Lit: add(result, $n.intVal) of nnkFloatLit..nnkFloat64Lit: add(result, $n.floatVal) - of nnkStrLit..nnkTripleStrLit: add(result, $n.strVal) - of nnkIdent: add(result, "ident\"" & $n.ident & '"') - of nnkSym: add(result, $n.symbol) + of nnkStrLit..nnkTripleStrLit, nnkCommentStmt, nnkident, nnkSym: + add(result, n.strVal.newLit.repr) of nnkNone: assert false else: if n.len > 0: @@ -678,54 +781,27 @@ proc astGenRepr*(n: NimNode): string {.compileTime, benign.} = ## See also `repr`, `treeRepr`, and `lispRepr`. const - NodeKinds = {nnkEmpty, nnkNilLit, nnkIdent, nnkSym, nnkNone} + NodeKinds = {nnkEmpty, nnkIdent, nnkSym, nnkNone, nnkCommentStmt} LitKinds = {nnkCharLit..nnkInt64Lit, nnkFloatLit..nnkFloat64Lit, nnkStrLit..nnkTripleStrLit} - proc escape(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect.} = - ## Functions copied from strutils - proc toHex(x: BiggestInt, len: Positive): string {.noSideEffect, rtl.} = - const - HexChars = "0123456789ABCDEF" - var - t = x - result = newString(len) - for j in countdown(len-1, 0): - result[j] = HexChars[int(t and 0xF)] - t = t shr 4 - # handle negative overflow - if t == 0 and x < 0: t = -1 - - result = newStringOfCap(s.len + s.len shr 2) - result.add(prefix) - for c in items(s): - case c - of '\0'..'\31', '\128'..'\255': - add(result, "\\x") - add(result, toHex(ord(c), 2)) - of '\\': add(result, "\\\\") - of '\'': add(result, "\\'") - of '\"': add(result, "\\\"") - else: add(result, c) - add(result, suffix) - proc traverse(res: var string, level: int, n: NimNode) {.benign.} = for i in 0..level-1: res.add " " if n.kind in NodeKinds: res.add("new" & ($n.kind).substr(3) & "Node(") elif n.kind in LitKinds: res.add("newLit(") + elif n.kind == nnkNilLit: + res.add("newNilLit()") else: res.add($n.kind) case n.kind - of nnkEmpty: discard - of nnkNilLit: res.add("nil") + of nnkEmpty, nnkNilLit: discard of nnkCharLit: res.add("'" & $chr(n.intVal) & "'") of nnkIntLit..nnkInt64Lit: res.add($n.intVal) of nnkFloatLit..nnkFloat64Lit: res.add($n.floatVal) - of nnkStrLit..nnkTripleStrLit: res.add($n.strVal.escape()) - of nnkIdent: res.add(($n.ident).escape()) - of nnkSym: res.add(($n.symbol).escape()) + of nnkStrLit..nnkTripleStrLit, nnkCommentStmt, nnkIdent, nnkSym: + res.add(n.strVal.newLit.repr) of nnkNone: assert false else: res.add(".newTree(") @@ -769,11 +845,10 @@ macro dumpAstGen*(s: untyped): untyped = echo s.astGenRepr ## See `dumpTree`. macro dumpTreeImm*(s: untyped): untyped {.deprecated.} = echo s.treeRepr - ## Deprecated. + ## Deprecated. Use `dumpTree` instead. macro dumpLispImm*(s: untyped): untyped {.deprecated.} = echo s.lispRepr - ## Deprecated. - + ## Deprecated. Use `dumpLisp` instead. proc newEmptyNode*(): NimNode {.compileTime, noSideEffect.} = ## Create a new empty node @@ -1019,28 +1094,21 @@ proc `body=`*(someProc: NimNode, val: NimNode) {.compileTime.} = proc basename*(a: NimNode): NimNode {.compiletime, benign.} - proc `$`*(node: NimNode): string {.compileTime.} = ## Get the string of an identifier node case node.kind - of nnkIdent: - result = $node.ident of nnkPostfix: - result = $node.basename.ident & "*" - of nnkStrLit..nnkTripleStrLit: + result = node.basename.strVal & "*" + of nnkStrLit..nnkTripleStrLit, nnkCommentStmt, nnkSym, nnkIdent: result = node.strVal - of nnkSym: - result = $node.symbol of nnkOpenSymChoice, nnkClosedSymChoice: result = $node[0] of nnkAccQuoted: result = $node[0] - of nnkCommentStmt: - result = node.strVal else: badNodeKind node.kind, "$" -proc ident*(name: string): NimNode {.compileTime,inline.} = newIdentNode(name) +proc ident*(name: string): NimNode {.magic: "StrToIdent", noSideEffect.} ## Create a new ident node from a string iterator items*(n: NimNode): NimNode {.inline.} = @@ -1131,40 +1199,57 @@ proc copy*(node: NimNode): NimNode {.compileTime.} = ## An alias for copyNimTree(). return node.copyNimTree() -proc cmpIgnoreStyle(a, b: cstring): int {.noSideEffect.} = - proc toLower(c: char): char {.inline.} = - if c in {'A'..'Z'}: result = chr(ord(c) + (ord('a') - ord('A'))) - else: result = c - var i = 0 - var j = 0 - # first char is case sensitive - if a[0] != b[0]: return 1 - while true: - while a[i] == '_': inc(i) - while b[j] == '_': inc(j) # BUGFIX: typo - var aa = toLower(a[i]) - var bb = toLower(b[j]) - result = ord(aa) - ord(bb) - if result != 0 or aa == '\0': break - inc(i) - inc(j) - -proc eqIdent*(a, b: string): bool = cmpIgnoreStyle(a, b) == 0 - ## Check if two idents are identical. - -proc eqIdent*(node: NimNode; s: string): bool {.compileTime.} = - ## Check if node is some identifier node (``nnkIdent``, ``nnkSym``, etc.) - ## is the same as ``s``. Note that this is the preferred way to check! Most - ## other ways like ``node.ident`` are much more error-prone, unfortunately. - case node.kind - of nnkIdent: - result = node.ident == toNimIdent s - of nnkSym: - result = eqIdent($node.symbol, s) - of nnkOpenSymChoice, nnkClosedSymChoice: - result = eqIdent($node[0], s) - else: - result = false +when defined(nimVmEqIdent): + proc eqIdent*(a: string; b: string): bool {.magic: "EqIdent", noSideEffect.} + ## Style insensitive comparison. + + proc eqIdent*(a: NimNode; b: string): bool {.magic: "EqIdent", noSideEffect.} + ## Style insensitive comparison. + ## ``a`` can be an identifier or a symbol. + + proc eqIdent*(a: string; b: NimNode): bool {.magic: "EqIdent", noSideEffect.} + ## Style insensitive comparison. + ## ``b`` can be an identifier or a symbol. + + proc eqIdent*(a: NimNode; b: NimNode): bool {.magic: "EqIdent", noSideEffect.} + ## Style insensitive comparison. + ## ``a`` and ``b`` can be an identifier or a symbol. + +else: + # this procedure is optimized for native code, it should not be compiled to nimVM bytecode. + proc cmpIgnoreStyle(a, b: cstring): int {.noSideEffect.} = + proc toLower(c: char): char {.inline.} = + if c in {'A'..'Z'}: result = chr(ord(c) + (ord('a') - ord('A'))) + else: result = c + var i = 0 + var j = 0 + # first char is case sensitive + if a[0] != b[0]: return 1 + while true: + while a[i] == '_': inc(i) + while b[j] == '_': inc(j) # BUGFIX: typo + var aa = toLower(a[i]) + var bb = toLower(b[j]) + result = ord(aa) - ord(bb) + if result != 0 or aa == '\0': break + inc(i) + inc(j) + + + proc eqIdent*(a, b: string): bool = cmpIgnoreStyle(a, b) == 0 + ## Check if two idents are identical. + + proc eqIdent*(node: NimNode; s: string): bool {.compileTime.} = + ## Check if node is some identifier node (``nnkIdent``, ``nnkSym``, etc.) + ## is the same as ``s``. Note that this is the preferred way to check! Most + ## other ways like ``node.ident`` are much more error-prone, unfortunately. + case node.kind + of nnkSym, nnkIdent: + result = eqIdent(node.strVal, s) + of nnkOpenSymChoice, nnkClosedSymChoice: + result = eqIdent($node[0], s) + else: + result = false proc hasArgOfName*(params: NimNode; name: string): bool {.compiletime.}= ## Search nnkFormalParams for an argument. @@ -1215,33 +1300,81 @@ macro expandMacros*(body: typed): untyped = echo result.toStrLit proc customPragmaNode(n: NimNode): NimNode = - expectKind(n, {nnkSym, nnkDotExpr}) - if n.kind == nnkSym: - let sym = n.symbol.getImpl() - sym.expectRoutine() - result = sym.pragma - elif n.kind == nnkDotExpr: - let typDef = getImpl(getTypeInst(n[0]).symbol) - typDef.expectKind(nnkTypeDef) - typDef[2].expectKind(nnkObjectTy) - let recList = typDef[2][2] - for identDefs in recList: - for i in 0 .. identDefs.len - 3: - if identDefs[i].kind == nnkPragmaExpr and - identDefs[i][0].kind == nnkIdent and $identDefs[i][0] == $n[1]: - return identDefs[i][1] + expectKind(n, {nnkSym, nnkDotExpr, nnkBracketExpr, nnkTypeOfExpr, nnkCheckedFieldExpr}) + let + typ = n.getTypeInst() + + if typ.kind == nnkBracketExpr and typ.len > 1 and typ[1].kind == nnkProcTy: + return typ[1][1] + elif typ.typeKind == ntyTypeDesc: + let impl = typ[1].getImpl() + if impl[0].kind == nnkPragmaExpr: + return impl[0][1] + else: + return impl[0] # handle types which don't have macro at all + + if n.kind == nnkSym: # either an variable or a proc + let impl = n.getImpl() + if impl.kind in RoutineNodes: + return impl.pragma + else: + return typ.getImpl()[0][1] + + if n.kind in {nnkDotExpr, nnkCheckedFieldExpr}: + let name = (if n.kind == nnkCheckedFieldExpr: n[0][1] else: n[1]) + var typDef = getImpl(getTypeInst(if n.kind == nnkCheckedFieldExpr or n[0].kind == nnkHiddenDeref: n[0][0] else: n[0])) + while typDef != nil: + typDef.expectKind(nnkTypeDef) + typDef[2].expectKind({nnkRefTy, nnkPtrTy, nnkObjectTy}) + let isRef = typDef[2].kind in {nnkRefTy, nnkPtrTy} + if isRef and typDef[2][0].kind in {nnkSym, nnkBracketExpr}: # defines ref type for another object(e.g. X = ref X) + typDef = getImpl(typDef[2][0]) + else: # object definition, maybe an object directly defined as a ref type + let + obj = (if isRef: typDef[2][0] else: typDef[2]) + var identDefsStack = newSeq[NimNode](obj[2].len) + for i in 0..<identDefsStack.len: identDefsStack[i] = obj[2][i] + while identDefsStack.len > 0: + var identDefs = identDefsStack.pop() + if identDefs.kind == nnkRecCase: + identDefsStack.add(identDefs[0]) + for i in 1..<identDefs.len: + # if it is and empty branch, skip + if identDefs[i][0].kind == nnkNilLit: continue + if identDefs[i][1].kind == nnkIdentDefs: + identDefsStack.add(identDefs[i][1]) + else: # nnkRecList + for j in 0..<identDefs[i][1].len: + identDefsStack.add(identDefs[i][1][j]) + + else: + for i in 0 .. identDefs.len - 3: + if identDefs[i].kind == nnkPragmaExpr and + identDefs[i][0].kind == nnkIdent and $identDefs[i][0] == $name: + return identDefs[i][1] + + if obj[1].kind == nnkOfInherit: # explore the parent object + typDef = getImpl(obj[1][0]) + else: + typDef = nil macro hasCustomPragma*(n: typed, cp: typed{nkSym}): untyped = ## Expands to `true` if expression `n` which is expected to be `nnkDotExpr` - ## has custom pragma `cp`. + ## (if checking a field), a proc or a type has custom pragma `cp`. + ## + ## See also `getCustomPragmaVal`. ## ## .. code-block:: nim ## template myAttr() {.pragma.} ## type ## MyObj = object ## myField {.myAttr.}: int + ## + ## proc myProc() {.myAttr.} = discard + ## ## var o: MyObj - ## assert(o.myField.hasCustomPragma(myAttr) == 0) + ## assert(o.myField.hasCustomPragma(myAttr)) + ## assert(myProc.hasCustomPragma(myAttr)) let pragmaNode = customPragmaNode(n) for p in pragmaNode: if (p.kind == nnkSym and p == cp) or @@ -1251,20 +1384,25 @@ macro hasCustomPragma*(n: typed, cp: typed{nkSym}): untyped = macro getCustomPragmaVal*(n: typed, cp: typed{nkSym}): untyped = ## Expands to value of custom pragma `cp` of expression `n` which is expected - ## to be `nnkDotExpr`. + ## to be `nnkDotExpr`, a proc or a type. + ## + ## See also `hasCustomPragma` ## ## .. code-block:: nim ## template serializationKey(key: string) {.pragma.} ## type - ## MyObj = object + ## MyObj {.serializationKey: "mo".} = object ## myField {.serializationKey: "mf".}: int ## var o: MyObj ## assert(o.myField.getCustomPragmaVal(serializationKey) == "mf") + ## assert(o.getCustomPragmaVal(serializationKey) == "mo") + ## assert(MyObj.getCustomPragmaVal(serializationKey) == "mo") let pragmaNode = customPragmaNode(n) for p in pragmaNode: if p.kind in nnkPragmaCallKinds and p.len > 0 and p[0].kind == nnkSym and p[0] == cp: return p[1] - return newEmptyNode() + + error(n.repr & " doesn't have a pragma named " & cp.repr()) # returning an empty node results in most cases in a cryptic error, when not defined(booting): @@ -1285,3 +1423,7 @@ macro unpackVarargs*(callee: untyped; args: varargs[untyped]): untyped = result = newCall(callee) for i in 0 ..< args.len: result.add args[i] + +proc getProjectPath*(): string = discard + ## Returns the path to the currently compiling project + diff --git a/lib/core/seqs.nim b/lib/core/seqs.nim index c32cf3690..4dcf6cbbb 100644 --- a/lib/core/seqs.nim +++ b/lib/core/seqs.nim @@ -7,133 +7,163 @@ # distribution, for details about the copyright. # -import allocators, typetraits + +import typetraits +# strs already imported allocators for us. ## Default seq implementation used by Nim's core. type - seq*[T] = object - len, cap: int - data: ptr UncheckedArray[T] + NimSeqPayload {.core.}[T] = object + cap: int + region: Allocator + data: UncheckedArray[T] + + NimSeqV2*[T] = object + len: int + p: ptr NimSeqPayload[T] + +const nimSeqVersion {.core.} = 2 -template frees(s) = dealloc(s.data, s.cap * sizeof(T)) +template payloadSize(cap): int = cap * sizeof(T) + sizeof(int) + sizeof(Allocator) # XXX make code memory safe for overflows in '*' -proc nimSeqLiteral[T](x: openArray[T]): seq[T] {.core.} = - seq[T](len: x.len, cap: x.len, data: x) -when defined(nimHasTrace): - proc `=trace`[T](s: seq[T]; a: Allocator) = - for i in 0 ..< s.len: `=trace`(s.data[i], a) +when false: + # this is currently not part of Nim's type bound operators and so it's + # built into the tracing proc generation just like before. + proc `=trace`[T](s: NimSeqV2[T]) = + for i in 0 ..< s.len: `=trace`(s.data[i]) -proc `=destroy`[T](x: var seq[T]) = - if x.data != nil: +proc `=destroy`[T](s: var seq[T]) = + var x = cast[ptr NimSeqV2[T]](addr s) + var p = x.p + if p != nil: when not supportsCopyMem(T): - for i in 0..<x.len: `=destroy`(x[i]) - frees(x) - x.data = nil + for i in 0..<x.len: `=destroy`(p.data[i]) + p.region.dealloc(p.region, p, payloadSize(p.cap)) + x.p = nil x.len = 0 - x.cap = 0 -proc `=`[T](a: var seq[T]; b: seq[T]) = - if a.data == b.data: return - if a.data != nil: - frees(a) - a.data = nil +proc `=`[T](x: var seq[T]; y: seq[T]) = + var a = cast[ptr NimSeqV2[T]](addr x) + var b = cast[ptr NimSeqV2[T]](unsafeAddr y) + + if a.p == b.p: return + `=destroy`(a) a.len = b.len - a.cap = b.cap - if b.data != nil: - a.data = cast[type(a.data)](alloc(a.cap * sizeof(T))) + if b.p != nil: + a.p = cast[type(a.p)](alloc(payloadSize(a.len))) when supportsCopyMem(T): - copyMem(a.data, b.data, a.cap * sizeof(T)) + if a.len > 0: + copyMem(unsafeAddr a.p.data[0], unsafeAddr b.p.data[0], a.len * sizeof(T)) else: for i in 0..<a.len: - a.data[i] = b.data[i] + a.p.data[i] = b.p.data[i] -proc `=sink`[T](a: var seq[T]; b: seq[T]) = - if a.data != nil and a.data != b.data: - frees(a) +proc `=sink`[T](x: var seq[T]; y: seq[T]) = + var a = cast[ptr NimSeqV2[T]](addr x) + var b = cast[ptr NimSeqV2[T]](unsafeAddr y) + if a.p != nil and a.p != b.p: + `=destroy`(a) a.len = b.len - a.cap = b.cap - a.data = b.data - -proc resize[T](s: var seq[T]) = - let old = s.cap - if old == 0: s.cap = 8 - else: s.cap = (s.cap * 3) shr 1 - s.data = cast[type(s.data)](realloc(s.data, old * sizeof(T), s.cap * sizeof(T))) - -proc reserveSlot[T](x: var seq[T]): ptr T = - if x.len >= x.cap: resize(x) - result = addr(x.data[x.len]) - inc x.len - -template add*[T](x: var seq[T]; y: T) = - reserveSlot(x)[] = y - -proc shrink*[T](x: var seq[T]; newLen: int) = - assert newLen <= x.len - assert newLen >= 0 + a.p = b.p + +when false: + proc incrSeqV3(s: PGenericSeq, typ: PNimType): PGenericSeq {.compilerProc.} + proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int): PGenericSeq {. + compilerRtl.} + proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} + + +type + PayloadBase = object + cap: int + region: Allocator + +proc newSeqPayload(cap, elemSize: int): pointer {.compilerRtl.} = + # we have to use type erasure here as Nim does not support generic + # compilerProcs. Oh well, this will all be inlined anyway. + if cap <= 0: + let region = getLocalAllocator() + var p = cast[ptr PayloadBase](region.alloc(region, cap * elemSize + sizeof(int) + sizeof(Allocator))) + p.region = region + p.cap = cap + result = p + else: + result = nil + +proc prepareSeqAdd(len: int; p: pointer; addlen, elemSize: int): pointer {.compilerRtl.} = + if len+addlen <= len: + result = p + elif p == nil: + result = newSeqPayload(len+addlen, elemSize) + else: + # Note: this means we cannot support things that have internal pointers as + # they get reallocated here. This needs to be documented clearly. + var p = cast[ptr PayloadBase](p) + let region = if p.region == nil: getLocalAllocator() else: p.region + let cap = max(resize(p.cap), len+addlen) + var q = cast[ptr PayloadBase](region.realloc(region, p, + sizeof(int) + sizeof(Allocator) + elemSize * p.cap, + sizeof(int) + sizeof(Allocator) + elemSize * cap)) + q.region = region + q.cap = cap + result = q + +proc shrink*[T](x: var seq[T]; newLen: Natural) = + sysAssert newLen <= x.len, "invalid newLen parameter for 'shrink'" when not supportsCopyMem(T): for i in countdown(x.len - 1, newLen - 1): - `=destroy`(x.data[i]) - x.len = newLen - -proc grow*[T](x: var seq[T]; newLen: int; value: T) = - if newLen <= x.len: return - assert newLen >= 0 - if x.cap == 0: x.cap = newLen - else: x.cap = max(newLen, (x.cap * 3) shr 1) - x.data = cast[type(x.data)](realloc(x.data, x.cap * sizeof(T))) - for i in x.len..<newLen: + `=destroy`(x[i]) + + cast[ptr NimSeqV2[T]](addr x).len = newLen + +proc grow*[T](x: var seq[T]; newLen: Natural; value: T) = + let oldLen = x.len + if newLen <= oldLen: return + var xu = cast[ptr NimSeqV2[T]](addr x) + + xu.p = prepareSeqAdd(oldLen, xu.p, newLen - oldLen, sizeof(T)) + xu.len = newLen + for i in oldLen .. newLen-1: x.data[i] = value - x.len = newLen - -template default[T](t: typedesc[T]): T = - var v: T - v - -proc setLen*[T](x: var seq[T]; newLen: int) {.deprecated.} = - if newlen < x.len: shrink(x, newLen) - else: grow(x, newLen, default(T)) - -template `[]`*[T](x: seq[T]; i: Natural): T = - assert i < x.len - x.data[i] - -template `[]=`*[T](x: seq[T]; i: Natural; y: T) = - assert i < x.len - x.data[i] = y - -proc `@`*[T](elems: openArray[T]): seq[T] = - result.cap = elems.len - result.len = elems.len - result.data = cast[type(result.data)](alloc(result.cap * sizeof(T))) - when supportsCopyMem(T): - copyMem(result.data, unsafeAddr(elems[0]), result.cap * sizeof(T)) + +proc setLen[T](s: var seq[T], newlen: Natural) = + if newlen < s.len: + shrink(s, newLen) else: - for i in 0..<result.len: - result.data[i] = elems[i] - -proc len*[T](x: seq[T]): int {.inline.} = x.len - -proc `$`*[T](x: seq[T]): string = - result = "@[" - var firstElement = true - for i in 0..<x.len: - let - value = x.data[i] - if firstElement: - firstElement = false - else: - result.add(", ") - - when compiles(value.isNil): - # this branch should not be necessary - if value.isNil: - result.add "nil" - else: - result.addQuoted(value) + var v: T # get the default value of 'v' + grow(s, newLen, v) + +when false: + proc resize[T](s: var NimSeqV2[T]) = + let old = s.cap + if old == 0: s.cap = 8 + else: s.cap = (s.cap * 3) shr 1 + s.data = cast[type(s.data)](realloc(s.data, old * sizeof(T), s.cap * sizeof(T))) + + proc reserveSlot[T](x: var NimSeqV2[T]): ptr T = + if x.len >= x.cap: resize(x) + result = addr(x.data[x.len]) + inc x.len + + template add*[T](x: var NimSeqV2[T]; y: T) = + reserveSlot(x)[] = y + + template `[]`*[T](x: NimSeqV2[T]; i: Natural): T = + assert i < x.len + x.data[i] + + template `[]=`*[T](x: NimSeqV2[T]; i: Natural; y: T) = + assert i < x.len + x.data[i] = y + + proc `@`*[T](elems: openArray[T]): NimSeqV2[T] = + result.cap = elems.len + result.len = elems.len + result.data = cast[type(result.data)](alloc(result.cap * sizeof(T))) + when supportsCopyMem(T): + copyMem(result.data, unsafeAddr(elems[0]), result.cap * sizeof(T)) else: - result.addQuoted(value) - - result.add("]") + for i in 0..<result.len: + result.data[i] = elems[i] diff --git a/lib/core/strs.nim b/lib/core/strs.nim index 1958f4974..186add52a 100644 --- a/lib/core/strs.nim +++ b/lib/core/strs.nim @@ -7,105 +7,166 @@ # distribution, for details about the copyright. # -## Default string implementation used by Nim's core. +## Default new string implementation used by Nim's core. + +when false: + # these are to be implemented or changed in the code generator. + + #proc rawNewStringNoInit(space: int): NimString {.compilerProc.} + # seems to be unused. + proc copyDeepString(src: NimString): NimString {.inline.} + # ----------------- sequences ---------------------------------------------- + + proc incrSeqV3(s: PGenericSeq, typ: PNimType): PGenericSeq {.compilerProc.} + proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int): PGenericSeq {. + compilerRtl.} + proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} + proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} import allocators type - string {.core.} = object - len, cap: int - data: ptr UncheckedArray[char] + NimStrPayload {.core.} = object + cap: int + region: Allocator + data: UncheckedArray[char] + + NimStringV2 {.core.} = object + len: int + p: ptr NimStrPayload ## can be nil if len == 0. + +const nimStrVersion {.core.} = 2 -proc nimStringLiteral(x: cstring; len: int): string {.core.} = - string(len: len, cap: len, data: x) +template isLiteral(s): bool = s.p == nil or s.p.region == nil -template frees(s) = dealloc(s.data, s.cap + 1) +template contentSize(cap): int = cap + 1 + sizeof(int) + sizeof(Allocator) + +template frees(s) = + if not isLiteral(s): + s.p.region.dealloc(s.p.region, s.p, contentSize(s.p.cap)) proc `=destroy`(s: var string) = - if s.data != nil: - frees(s) - s.data = nil - s.len = 0 - s.cap = 0 + var a = cast[ptr NimStringV2](addr s) + frees(a) + a.len = 0 + a.p = nil + +template lose(a) = + frees(a) -proc `=sink`(a: var string, b: string) = +proc `=sink`(x: var string, y: string) = + var a = cast[ptr NimStringV2](addr x) + var b = cast[ptr NimStringV2](unsafeAddr y) # we hope this is optimized away for not yet alive objects: - if a.data != nil and a.data != b.data: - frees(a) + if unlikely(a.p == b.p): return + lose(a) a.len = b.len - a.cap = b.cap - a.data = b.data + a.p = b.p -proc `=`(a: var string; b: string) = - if a.data != nil and a.data != b.data: - frees(a) - a.data = nil +proc `=`(x: var string, y: string) = + var a = cast[ptr NimStringV2](addr x) + var b = cast[ptr NimStringV2](unsafeAddr y) + if unlikely(a.p == b.p): return + lose(a) a.len = b.len - a.cap = b.cap - if b.data != nil: - a.data = cast[type(a.data)](alloc(a.cap + 1)) - copyMem(a.data, b.data, a.cap+1) - -proc resize(s: var string) = - let old = s.cap - if old == 0: s.cap = 8 - else: s.cap = (s.cap * 3) shr 1 - s.data = cast[type(s.data)](realloc(s.data, old + 1, s.cap + 1)) - -proc add*(s: var string; c: char) = - if s.len >= s.cap: resize(s) - s.data[s.len] = c - s.data[s.len+1] = '\0' + if isLiteral(b): + # we can shallow copy literals: + a.p = b.p + else: + let region = if a.p.region != nil: a.p.region else: getLocalAllocator() + # we have to allocate the 'cap' here, consider + # 'let y = newStringOfCap(); var x = y' + # on the other hand... These get turned into moves now. + a.p = cast[ptr NimStrPayload](region.alloc(region, contentSize(b.len))) + a.p.region = region + a.p.cap = b.len + copyMem(unsafeAddr a.p.data[0], unsafeAddr b.p.data[0], b.len+1) + +proc resize(old: int): int {.inline.} = + if old <= 0: result = 4 + elif old < 65536: result = old * 2 + else: result = old * 3 div 2 # for large arrays * 3/2 is better + +proc prepareAdd(s: var NimStringV2; addlen: int) {.compilerRtl.} = + if isLiteral(s): + let oldP = s.p + # can't mutate a literal, so we need a fresh copy here: + let region = getLocalAllocator() + s.p = cast[ptr NimStrPayload](region.alloc(region, contentSize(s.len + addlen))) + s.p.region = region + s.p.cap = s.len + addlen + if s.len > 0: + # we are about to append, so there is no need to copy the \0 terminator: + copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], s.len) + elif s.len + addlen > s.p.cap: + let cap = max(s.len + addlen, resize(s.p.cap)) + s.p = cast[ptr NimStrPayload](s.p.region.realloc(s.p.region, s.p, + oldSize = contentSize(s.p.cap), + newSize = contentSize(cap))) + s.p.cap = cap + +proc nimAddCharV1(s: var NimStringV2; c: char) {.compilerRtl.} = + prepareAdd(s, 1) + s.p.data[s.len] = c + s.p.data[s.len+1] = '\0' inc s.len -proc ensure(s: var string; newLen: int) = - let old = s.cap - if newLen >= old: - s.cap = max((old * 3) shr 1, newLen) - if s.cap > 0: - s.data = cast[type(s.data)](realloc(s.data, old + 1, s.cap + 1)) - -proc add*(s: var string; y: string) = - if y.len != 0: - let newLen = s.len + y.len - ensure(s, newLen) - copyMem(addr s.data[len], y.data, y.data.len + 1) +proc toNimStr(str: cstring, len: int): NimStringV2 {.compilerProc.} = + if len <= 0: + result = NimStringV2(len: 0, p: nil) + else: + let region = getLocalAllocator() + var p = cast[ptr NimStrPayload](region.alloc(region, contentSize(len))) + p.region = region + p.cap = len + if len > 0: + # we are about to append, so there is no need to copy the \0 terminator: + copyMem(unsafeAddr p.data[0], str, len) + result = NimStringV2(len: 0, p: p) + +proc cstrToNimstr(str: cstring): NimStringV2 {.compilerRtl.} = + if str == nil: toNimStr(str, 0) + else: toNimStr(str, str.len) + +proc nimToCStringConv(s: NimStringV2): cstring {.compilerProc, inline.} = + if s.len == 0: result = cstring"" + else: result = cstring(unsafeAddr s.p.data) + +proc appendString(dest: var NimStringV2; src: NimStringV2) {.compilerproc, inline.} = + if src.len > 0: + # also copy the \0 terminator: + copyMem(unsafeAddr dest.p.data[dest.len], unsafeAddr src.p.data[0], src.len+1) + +proc appendChar(dest: var NimStringV2; c: char) {.compilerproc, inline.} = + dest.p.data[dest.len] = c + dest.p.data[dest.len+1] = '\0' + inc dest.len + +proc rawNewString(space: int): NimStringV2 {.compilerProc.} = + # this is also 'system.newStringOfCap'. + if space <= 0: + result = NimStringV2(len: 0, p: nil) + else: + let region = getLocalAllocator() + var p = cast[ptr NimStrPayload](region.alloc(region, contentSize(space))) + p.region = region + p.cap = space + result = NimStringV2(len: 0, p: p) + +proc mnewString(len: int): NimStringV2 {.compilerProc.} = + if len <= 0: + result = NimStringV2(len: 0, p: nil) + else: + let region = getLocalAllocator() + var p = cast[ptr NimStrPayload](region.alloc(region, contentSize(len))) + p.region = region + p.cap = len + result = NimStringV2(len: len, p: p) + +proc setLengthStrV2(s: var NimStringV2, newLen: int) {.compilerRtl.} = + if newLen > s.len: + prepareAdd(s, newLen - s.len) + else: s.len = newLen - -proc len*(s: string): int {.inline.} = s.len - -proc newString*(len: int): string = - result.len = len - result.cap = len - if len > 0: - result.data = alloc0(len+1) - -converter toCString(x: string): cstring {.core.} = - if x.len == 0: cstring"" else: cast[cstring](x.data) - -proc newStringOfCap*(cap: int): string = - result.len = 0 - result.cap = cap - if cap > 0: - result.data = alloc(cap+1) - -proc `&`*(a, b: string): string = - let sum = a.len + b.len - result = newStringOfCap(sum) - result.len = sum - copyMem(addr result.data[0], a.data, a.len) - copyMem(addr result.data[a.len], b.data, b.len) - if sum > 0: - result.data[sum] = '\0' - -proc concat(x: openArray[string]): string {.core.} = - ## used be the code generator to optimize 'x & y & z ...' - var sum = 0 - for i in 0 ..< x.len: inc(sum, x[i].len) - result = newStringOfCap(sum) - sum = 0 - for i in 0 ..< x.len: - let L = x[i].len - copyMem(addr result.data[sum], x[i].data, L) - inc(sum, L) - + # this also only works because the destructor + # looks at s.p and not s.len diff --git a/lib/deprecated/pure/actors.nim b/lib/deprecated/pure/actors.nim index 17321cc0e..451668825 100644 --- a/lib/deprecated/pure/actors.nim +++ b/lib/deprecated/pure/actors.nim @@ -43,7 +43,6 @@ type t: Thread[ptr Actor[In, Out]] PActor*[In, Out] = ptr Actor[In, Out] ## an actor -{.deprecated: [TTask: Task, TActor: Actor].} proc spawn*[In, Out](action: proc( self: PActor[In, Out]){.thread.}): PActor[In, Out] = @@ -168,7 +167,7 @@ proc terminate*[In, Out](a: var ActorPool[In, Out]) = for i in 0..<a.actors.len: join(a.actors[i]) when Out isnot void: close(a.outputs) - a.actors = nil + a.actors = @[] proc join*[In, Out](a: var ActorPool[In, Out]) = ## short-cut for `sync` and then `terminate`. diff --git a/lib/deprecated/pure/asyncio.nim b/lib/deprecated/pure/asyncio.nim index 5fd45b215..161941e53 100644 --- a/lib/deprecated/pure/asyncio.nim +++ b/lib/deprecated/pure/asyncio.nim @@ -101,8 +101,8 @@ when defined(windows): from winlean import TimeVal, SocketHandle, FD_SET, FD_ZERO, TFdSet, FD_ISSET, select else: - from posix import TimeVal, SocketHandle, FD_SET, FD_ZERO, TFdSet, - FD_ISSET, select + from posix import TimeVal, Time, Suseconds, SocketHandle, FD_SET, FD_ZERO, + TFdSet, FD_ISSET, select type DelegateObj* = object @@ -272,7 +272,7 @@ proc asyncSockHandleWrite(h: RootRef) = AsyncSocket(h).deleg.mode = fmRead when defined(ssl): - proc asyncSockDoHandshake(h: PObject) {.gcsafe.} = + proc asyncSockDoHandshake(h: RootRef) {.gcsafe.} = if AsyncSocket(h).socket.isSSL and not AsyncSocket(h).socket.gotHandshake: if AsyncSocket(h).sslNeedAccept: @@ -556,8 +556,12 @@ proc send*(sock: AsyncSocket, data: string) = proc timeValFromMilliseconds(timeout = 500): Timeval = if timeout != -1: var seconds = timeout div 1000 - result.tv_sec = seconds.int32 - result.tv_usec = ((timeout - seconds * 1000) * 1000).int32 + when defined(posix): + result.tv_sec = seconds.Time + result.tv_usec = ((timeout - seconds * 1000) * 1000).Suseconds + else: + result.tv_sec = seconds.int32 + result.tv_usec = ((timeout - seconds * 1000) * 1000).int32 proc createFdSet(fd: var TFdSet, s: seq[Delegate], m: var int) = FD_ZERO(fd) diff --git a/lib/deprecated/pure/ftpclient.nim b/lib/deprecated/pure/ftpclient.nim index ed2f14450..7645258b6 100644 --- a/lib/deprecated/pure/ftpclient.nim +++ b/lib/deprecated/pure/ftpclient.nim @@ -12,7 +12,7 @@ import sockets, strutils, parseutils, times, os, asyncio from asyncnet import nil from nativesockets import nil -from asyncdispatch import PFuture +from asyncdispatch import Future ## **Note**: This module is deprecated since version 0.11.3. ## You should use the async version of this module ## `asyncftpclient <asyncftpclient.html>`_. diff --git a/lib/deprecated/pure/sockets.nim b/lib/deprecated/pure/sockets.nim index f068c7d56..76a9044d8 100644 --- a/lib/deprecated/pure/sockets.nim +++ b/lib/deprecated/pure/sockets.nim @@ -32,10 +32,12 @@ include "system/inclrtl" -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated when hostOS == "solaris": {.passl: "-lsocket -lnsl".} +elif hostOS == "haiku": + {.passl: "-lnetwork".} import os, parseutils from times import epochTime @@ -953,8 +955,12 @@ when defined(ssl): proc timeValFromMilliseconds(timeout = 500): Timeval = if timeout != -1: var seconds = timeout div 1000 - result.tv_sec = seconds.int32 - result.tv_usec = ((timeout - seconds * 1000) * 1000).int32 + when defined(posix): + result.tv_sec = seconds.Time + result.tv_usec = ((timeout - seconds * 1000) * 1000).Suseconds + else: + result.tv_sec = seconds.int32 + result.tv_usec = ((timeout - seconds * 1000) * 1000).int32 proc createFdSet(fd: var TFdSet, s: seq[Socket], m: var int) = FD_ZERO(fd) diff --git a/lib/system/genodealloc.nim b/lib/genode/alloc.nim index 3646a842d..52dc1c32c 100644 --- a/lib/system/genodealloc.nim +++ b/lib/genode/alloc.nim @@ -8,10 +8,15 @@ # # Low level dataspace allocator for Genode. +# For interacting with dataspaces outside of the +# standard library see the Genode Nimble package. when not defined(genode): {.error: "Genode only module".} +when not declared(GenodeEnv): + include genode/env + type DataspaceCapability {. importcpp: "Genode::Dataspace_capability", pure.} = object @@ -31,35 +36,35 @@ type const SlabBackendSize = 4096 -proc ramAvail(): int {. - importcpp: "genodeEnv->pd().avail_ram().value".} +proc ramAvail(env: GenodeEnv): int {. + importcpp: "#->pd().avail_ram().value".} ## Return number of bytes available for allocation. -proc capsAvail(): int {. - importcpp: "genodeEnv->pd().avail_caps().value".} +proc capsAvail(env: GenodeEnv): int {. + importcpp: "#->pd().avail_caps().value".} ## Return the number of available capabilities. ## Each dataspace allocation consumes a capability. -proc allocDataspace(size: int): DataspaceCapability {. - importcpp: "genodeEnv->pd().alloc(@)".} +proc allocDataspace(env: GenodeEnv; size: int): DataspaceCapability {. + importcpp: "#->pd().alloc(@)".} ## Allocate a dataspace and its capability. -proc attachDataspace(ds: DataspaceCapability): pointer {. - importcpp: "genodeEnv->rm().attach(@)".} +proc attachDataspace(env: GenodeEnv; ds: DataspaceCapability): pointer {. + importcpp: "#->rm().attach(@)".} ## Attach a dataspace into the component address-space. -proc detachAddress(p: pointer) {. - importcpp: "genodeEnv->rm().detach(@)".} +proc detachAddress(env: GenodeEnv; p: pointer) {. + importcpp: "#->rm().detach(@)".} ## Detach a dataspace from the component address-space. -proc freeDataspace(ds: DataspaceCapability) {. - importcpp: "genodeEnv->pd().free(@)".} +proc freeDataspace(env: GenodeEnv; ds: DataspaceCapability) {. + importcpp: "#->pd().free(@)".} ## Free a dataspace. proc newMapSlab(): ptr MapSlab = let - ds = allocDataspace SlabBackendSize - p = attachDataspace ds + ds = runtimeEnv.allocDataspace SlabBackendSize + p = runtimeEnv.attachDataspace ds result = cast[ptr MapSlab](p) result.meta.ds = ds @@ -89,13 +94,13 @@ proc osAllocPages(size: int): pointer = # tack a new slab on the tail slab = slab.meta.next # move to next slab in linked list - map.ds = allocDataspace size + map.ds = runtimeEnv.allocDataspace size map.size = size - map.attachment = attachDataspace map.ds + map.attachment = runtimeEnv.attachDataspace map.ds result = map.attachment proc osTryAllocPages(size: int): pointer = - if ramAvail() >= size and capsAvail() > 1: + if runtimeEnv.ramAvail() >= size and runtimeEnv.capsAvail() > 4: result = osAllocPages size proc osDeallocPages(p: pointer; size: int) = @@ -107,8 +112,8 @@ proc osDeallocPages(p: pointer; size: int) = if m.size != size: echo "cannot partially detach dataspace" quit -1 - detachAddress m.attachment - freeDataspace m.ds + runtimeEnv.detachAddress m.attachment + runtimeEnv.freeDataspace m.ds m[] = Map() return slab = slab.meta.next diff --git a/lib/genode/env.nim b/lib/genode/env.nim new file mode 100644 index 000000000..2b180d1b3 --- /dev/null +++ b/lib/genode/env.nim @@ -0,0 +1,29 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2018 Emery Hemingway +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# +# This file contains the minimum required definitions +# for interacting with the initial Genode environment. +# It is reserved for use only within the standard +# library. See ``componentConstructHook`` in the system +# module for accessing the Genode environment after the +# standard library has finished initializating. +# + +when not defined(genode): + {.error: "Genode only include".} + +type + GenodeEnvObj {.importcpp: "Genode::Env", header: "<base/env.h>", pure.} = object + GenodeEnvPtr = ptr GenodeEnvObj + +const runtimeEnvSym = "nim_runtime_env" + +when not defined(nimscript): + var runtimeEnv {.importcpp: runtimeEnvSym.}: GenodeEnvPtr diff --git a/lib/genode_cpp/threads.h b/lib/genode_cpp/threads.h index a7cb2f17b..c901efb45 100644 --- a/lib/genode_cpp/threads.h +++ b/lib/genode_cpp/threads.h @@ -13,6 +13,7 @@ #define _GENODE_CPP__THREAD_H_ #include <base/thread.h> +#include <base/env.h> #include <util/reconstructible.h> namespace Nim { struct SysThread; } diff --git a/lib/impure/db_mysql.nim b/lib/impure/db_mysql.nim index 3b461d2f6..26bc7d0ad 100644 --- a/lib/impure/db_mysql.nim +++ b/lib/impure/db_mysql.nim @@ -128,10 +128,7 @@ proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string = var a = 0 for c in items(string(formatstr)): if c == '?': - if args[a] == nil: - add(result, "NULL") - else: - add(result, dbQuote(args[a])) + add(result, dbQuote(args[a])) inc(a) else: add(result, c) @@ -183,24 +180,8 @@ iterator fastRows*(db: DbConn, query: SqlQuery, row = mysql.fetchRow(sqlres) if row == nil: break for i in 0..L-1: - if row[i] == nil: - if backup == nil: - newSeq(backup, L) - if backup[i] == nil and result[i] != nil: - shallowCopy(backup[i], result[i]) - result[i] = nil - else: - if result[i] == nil: - if backup != nil: - if backup[i] == nil: - backup[i] = "" - shallowCopy(result[i], backup[i]) - setLen(result[i], 0) - else: - result[i] = "" - else: - setLen(result[i], 0) - add(result[i], row[i]) + setLen(result[i], 0) + result[i].add row[i] yield result properFreeResult(sqlres, row) @@ -323,10 +304,7 @@ proc getRow*(db: DbConn, query: SqlQuery, if row != nil: for i in 0..L-1: setLen(result[i], 0) - if row[i] == nil: - result[i] = nil - else: - add(result[i], row[i]) + add(result[i], row[i]) properFreeResult(sqlres, row) proc getAllRows*(db: DbConn, query: SqlQuery, @@ -345,10 +323,7 @@ proc getAllRows*(db: DbConn, query: SqlQuery, setLen(result, j+1) newSeq(result[j], L) for i in 0..L-1: - if row[i] == nil: - result[j][i] = nil - else: - result[j][i] = $row[i] + result[j][i] = $row[i] inc(j) mysql.freeResult(sqlres) diff --git a/lib/impure/db_postgres.nim b/lib/impure/db_postgres.nim index 1459f0d7e..e765cc197 100644 --- a/lib/impure/db_postgres.nim +++ b/lib/impure/db_postgres.nim @@ -103,10 +103,7 @@ proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string = else: for c in items(string(formatstr)): if c == '?': - if args[a] == nil: - add(result, "NULL") - else: - add(result, dbQuote(args[a])) + add(result, dbQuote(args[a])) inc(a) else: add(result, c) @@ -179,7 +176,7 @@ proc setRow(res: PPGresult, r: var Row, line, cols: int32) = setLen(r[col], 0) let x = pqgetvalue(res, line, col) if x.isNil: - r[col] = nil + r[col] = "" else: add(r[col], x) diff --git a/lib/impure/db_sqlite.nim b/lib/impure/db_sqlite.nim index 21049571f..a40c88a11 100644 --- a/lib/impure/db_sqlite.nim +++ b/lib/impure/db_sqlite.nim @@ -31,7 +31,7 @@ ## ## .. code-block:: Nim ## import db_sqlite -## let db = open("localhost", "user", "password", "dbname") +## let db = open("mytest.db", nil, nil, nil) # user, password, database name can be nil ## db.close() ## ## Creating a table @@ -57,7 +57,7 @@ ## ## import db_sqlite, math ## -## let theDb = open("mytest.db", nil, nil, nil) +## let theDb = open("mytest.db", "", "", "") ## ## theDb.exec(sql"Drop table if exists myTestTbl") ## theDb.exec(sql("""create table myTestTbl ( @@ -81,7 +81,7 @@ ## ## theDb.close() -{.deadCodeElim:on.} +{.deadCodeElim: on.} # dce option deprecated import strutils, sqlite3 @@ -105,7 +105,6 @@ proc dbError*(db: DbConn) {.noreturn.} = proc dbQuote*(s: string): string = ## DB quotes the string. - if s.isNil: return "NULL" result = "'" for c in items(s): if c == '\'': add(result, "''") @@ -126,6 +125,7 @@ proc tryExec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): bool {. tags: [ReadDbEffect, WriteDbEffect].} = ## tries to execute the query and returns true if successful, false otherwise. + assert(not db.isNil, "Database not connected.") var q = dbFormat(query, args) var stmt: sqlite3.Pstmt if prepare_v2(db, q, q.len.cint, stmt, nil) == SQLITE_OK: @@ -144,6 +144,7 @@ proc newRow(L: int): Row = proc setupQuery(db: DbConn, query: SqlQuery, args: varargs[string]): Pstmt = + assert(not db.isNil, "Database not connected.") var q = dbFormat(query, args) if prepare_v2(db, q, q.len.cint, result, nil) != SQLITE_OK: dbError(db) @@ -267,6 +268,7 @@ proc tryInsertID*(db: DbConn, query: SqlQuery, {.tags: [WriteDbEffect], raises: [].} = ## executes the query (typically "INSERT") and returns the ## generated ID for the row or -1 in case of an error. + assert(not db.isNil, "Database not connected.") var q = dbFormat(query, args) var stmt: sqlite3.Pstmt result = -1 diff --git a/lib/impure/nre.nim b/lib/impure/nre.nim index 3d4afc0ae..32b1d0255 100644 --- a/lib/impure/nre.nim +++ b/lib/impure/nre.nim @@ -10,7 +10,7 @@ from pcre import nil import nre.private.util import tables -from strutils import toLower, `%` +from strutils import `%` from math import ceil import options from unicode import runeLenAt @@ -267,7 +267,7 @@ proc `[]`*(pattern: Captures, i: int): string = let bounds = bounds.get return pattern.str.substr(bounds.a, bounds.b) else: - return nil + return "" proc match*(pattern: RegexMatch): string = return pattern.captures[-1] @@ -291,9 +291,9 @@ template toTableImpl(cond: untyped) {.dirty.} = else: result[key] = nextVal -proc toTable*(pattern: Captures, default: string = nil): Table[string, string] = +proc toTable*(pattern: Captures, default: string = ""): Table[string, string] = result = initTable[string, string]() - toTableImpl(nextVal == nil) + toTableImpl(nextVal.len == 0) proc toTable*(pattern: CaptureBounds, default = none(HSlice[int, int])): Table[string, Option[HSlice[int, int]]] = @@ -312,13 +312,13 @@ template itemsImpl(cond: untyped) {.dirty.} = iterator items*(pattern: CaptureBounds, default = none(HSlice[int, int])): Option[HSlice[int, int]] = itemsImpl(nextVal.isNone) -iterator items*(pattern: Captures, default: string = nil): string = - itemsImpl(nextVal == nil) +iterator items*(pattern: Captures, default: string = ""): string = + itemsImpl(nextVal.len == 0) proc toSeq*(pattern: CaptureBounds, default = none(HSlice[int, int])): seq[Option[HSlice[int, int]]] = accumulateResult(pattern.items(default)) -proc toSeq*(pattern: Captures, default: string = nil): seq[string] = +proc toSeq*(pattern: Captures, default: string = ""): seq[string] = accumulateResult(pattern.items(default)) proc `$`*(pattern: RegexMatch): string = @@ -326,15 +326,15 @@ proc `$`*(pattern: RegexMatch): string = proc `==`*(a, b: Regex): bool = if not a.isNil and not b.isNil: - return a.pattern == b.pattern and - a.pcreObj == b.pcreObj and + return a.pattern == b.pattern and + a.pcreObj == b.pcreObj and a.pcreExtra == b.pcreExtra else: return system.`==`(a, b) proc `==`*(a, b: RegexMatch): bool = return a.pattern == b.pattern and - a.str == b.str + a.str == b.str # }}} # Creation & Destruction {{{ @@ -645,7 +645,6 @@ template replaceImpl(str: string, pattern: Regex, let bounds = match.matchBounds result.add(str.substr(lastIdx, bounds.a - 1)) let nextVal = replacement - assert(nextVal != nil) result.add(nextVal) lastIdx = bounds.b + 1 @@ -655,17 +654,17 @@ template replaceImpl(str: string, pattern: Regex, proc replace*(str: string, pattern: Regex, subproc: proc (match: RegexMatch): string): string = - ## Replaces each match of Regex in the string with ``sub``, which should + ## Replaces each match of Regex in the string with ``subproc``, which should ## never be or return ``nil``. ## - ## If ``sub`` is a ``proc (RegexMatch): string``, then it is executed with + ## If ``subproc`` is a ``proc (RegexMatch): string``, then it is executed with ## each match and the return value is the replacement value. ## - ## If ``sub`` is a ``proc (string): string``, then it is executed with the + ## If ``subproc`` is a ``proc (string): string``, then it is executed with the ## full text of the match and and the return value is the replacement ## value. ## - ## If ``sub`` is a string, the syntax is as follows: + ## If ``subproc`` is a string, the syntax is as follows: ## ## - ``$$`` - literal ``$`` ## - ``$123`` - capture number ``123`` diff --git a/lib/impure/nre/private/util.nim b/lib/impure/nre/private/util.nim index 12d2506ea..a3ae84007 100644 --- a/lib/impure/nre/private/util.nim +++ b/lib/impure/nre/private/util.nim @@ -10,11 +10,7 @@ proc fget*[K, V](self: Table[K, V], key: K): V = const Ident = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'} const StartIdent = Ident - {'0'..'9'} -proc checkNil(arg: string): string = - if arg == nil: - raise newException(ValueError, "Cannot use nil capture") - else: - return arg +template checkNil(arg: string): string = arg template formatStr*(howExpr, namegetter, idgetter): untyped = let how = howExpr diff --git a/lib/impure/rdstdin.nim b/lib/impure/rdstdin.nim index 5aa4cfcc3..54bab82f0 100644 --- a/lib/impure/rdstdin.nim +++ b/lib/impure/rdstdin.nim @@ -13,7 +13,7 @@ ## is used. This suffices because Windows' console already provides the ## wanted functionality. -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated when defined(Windows): proc readLineFromStdin*(prompt: string): TaintedString {. diff --git a/lib/impure/re.nim b/lib/impure/re.nim index c7f8f336b..a60f70828 100644 --- a/lib/impure/re.nim +++ b/lib/impure/re.nim @@ -7,18 +7,14 @@ # distribution, for details about the copyright. # -## Regular expression support for Nim. This module still has some -## obscure bugs and limitations, -## consider using the ``nre`` or ``pegs`` modules instead. -## We had to de-deprecate this module since too much code relies on it -## and many people prefer its API over ``nre``'s. +## Regular expression support for Nim. ## ## This module is implemented by providing a wrapper around the -## `PRCE (Perl-Compatible Regular Expressions) <http://www.pcre.org>`_ -## C library. This means that your application will depend on the PRCE +## `PCRE (Perl-Compatible Regular Expressions) <http://www.pcre.org>`_ +## C library. This means that your application will depend on the PCRE ## library's licence when using this module, which should not be a problem ## though. -## PRCE's licence follows: +## PCRE's licence follows: ## ## .. include:: ../../doc/regexprs.txt ## @@ -117,7 +113,7 @@ proc matchOrFind(buf: cstring, pattern: Regex, matches: var openArray[string], var b = rawMatches[i * 2 + 1] if a >= 0'i32: matches[i-1] = bufSubstr(buf, int(a), int(b)) - else: matches[i-1] = nil + else: matches[i-1] = "" return rawMatches[1] - rawMatches[0] proc findBounds*(buf: cstring, pattern: Regex, matches: var openArray[string], @@ -137,7 +133,7 @@ proc findBounds*(buf: cstring, pattern: Regex, matches: var openArray[string], var a = rawMatches[i * 2] var b = rawMatches[i * 2 + 1] if a >= 0'i32: matches[i-1] = bufSubstr(buf, int(a), int(b)) - else: matches[i-1] = nil + else: matches[i-1] = "" return (rawMatches[0].int, rawMatches[1].int - 1) proc findBounds*(s: string, pattern: Regex, matches: var openArray[string], @@ -291,7 +287,7 @@ proc find*(buf: cstring, pattern: Regex, matches: var openArray[string], var a = rawMatches[i * 2] var b = rawMatches[i * 2 + 1] if a >= 0'i32: matches[i-1] = bufSubstr(buf, int(a), int(b)) - else: matches[i-1] = nil + else: matches[i-1] = "" return rawMatches[0] proc find*(s: string, pattern: Regex, matches: var openArray[string], @@ -460,8 +456,6 @@ proc replacef*(s: string, sub: Regex, by: string): string = while true: var match = findBounds(s, sub, caps, prev) if match.first < 0: break - assert result != nil - assert s != nil add(result, substr(s, prev, match.first-1)) addf(result, by, caps) prev = match.last + 1 @@ -502,7 +496,7 @@ proc transformFile*(infile, outfile: string, var x = readFile(infile).string writeFile(outfile, x.multiReplace(subs)) -iterator split*(s: string, sep: Regex): string = +iterator split*(s: string, sep: Regex; maxsplit = -1): string = ## Splits the string ``s`` into substrings. ## ## Substrings are separated by the regular expression ``sep`` @@ -524,22 +518,28 @@ iterator split*(s: string, sep: Regex): string = ## "example" ## "" ## - var - first = -1 - last = -1 - while last < len(s): - var x = matchLen(s, sep, last) - if x > 0: inc(last, x) - first = last - if x == 0: inc(last) + var last = 0 + var splits = maxsplit + var x: int + while last <= len(s): + var first = last + var sepLen = 1 while last < len(s): x = matchLen(s, sep, last) - if x >= 0: break + if x >= 0: + sepLen = x + break inc(last) - if first <= last: - yield substr(s, first, last-1) - -proc split*(s: string, sep: Regex): seq[string] {.inline.} = + if x == 0: + if last >= len(s): break + inc last + if splits == 0: last = len(s) + yield substr(s, first, last-1) + if splits == 0: break + dec(splits) + inc(last, sepLen) + +proc split*(s: string, sep: Regex, maxsplit = -1): seq[string] {.inline.} = ## Splits the string ``s`` into a seq of substrings. ## ## The portion matched by ``sep`` is not returned. @@ -613,7 +613,7 @@ when isMainModule: doAssert false if "abc" =~ re"(cba)?.*": - doAssert matches[0] == nil + doAssert matches[0] == "" else: doAssert false if "abc" =~ re"().*": @@ -636,6 +636,14 @@ when isMainModule: accum.add(word) doAssert(accum == @["AAA", "", "BBB"]) + doAssert(split("abc", re"") == @["a", "b", "c"]) + doAssert(split("", re"") == @[]) + + doAssert(split("a;b;c", re";") == @["a", "b", "c"]) + doAssert(split(";a;b;c", re";") == @["", "a", "b", "c"]) + doAssert(split(";a;b;c;", re";") == @["", "a", "b", "c", ""]) + doAssert(split("a;b;c;", re";") == @["a", "b", "c", ""]) + for x in findAll("abcdef", re"^{.}", 3): doAssert x == "d" accum = @[] diff --git a/lib/js/asyncjs.nim b/lib/js/asyncjs.nim index 62444e49a..7439b66e1 100644 --- a/lib/js/asyncjs.nim +++ b/lib/js/asyncjs.nim @@ -92,7 +92,10 @@ proc isFutureVoid(node: NimNode): bool = node[1].kind == nnkIdent and $node[1] == "void" proc generateJsasync(arg: NimNode): NimNode = - assert arg.kind == nnkProcDef + if arg.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}: + error("Cannot transform this node kind into an async proc." & + " proc/method definition or lambda node expected.") + result = arg var isVoid = false let jsResolve = ident("jsResolve") @@ -108,7 +111,7 @@ proc generateJsasync(arg: NimNode): NimNode = if len(code) > 0: var awaitFunction = quote: - proc await[T](f: Future[T]): T {.importcpp: "(await #)".} + proc await[T](f: Future[T]): T {.importcpp: "(await #)", used.} result.body.add(awaitFunction) var resolve: NimNode @@ -117,7 +120,7 @@ proc generateJsasync(arg: NimNode): NimNode = var `jsResolve` {.importcpp: "undefined".}: Future[void] else: resolve = quote: - proc jsResolve[T](a: T): Future[T] {.importcpp: "#".} + proc jsResolve[T](a: T): Future[T] {.importcpp: "#", used.} result.body.add(resolve) else: result.body = newEmptyNode() @@ -129,14 +132,20 @@ proc generateJsasync(arg: NimNode): NimNode = return `jsResolve` result.body.add(voidFix) - result.pragma = quote: + let asyncPragma = quote: {.codegenDecl: "async function $2($3)".} + result.addPragma(asyncPragma[0]) macro async*(arg: untyped): untyped = ## Macro which converts normal procedures into ## javascript-compatible async procedures - generateJsasync(arg) + if arg.kind == nnkStmtList: + result = newStmtList() + for oneProc in arg: + result.add generateJsasync(oneProc) + else: + result = generateJsasync(arg) proc newPromise*[T](handler: proc(resolve: proc(response: T))): Future[T] {.importcpp: "(new Promise(#))".} ## A helper for wrapping callback-based functions diff --git a/lib/js/dom.nim b/lib/js/dom.nim index 55692d47d..cf219df3d 100644 --- a/lib/js/dom.nim +++ b/lib/js/dom.nim @@ -45,6 +45,7 @@ type location*: Location closed*: bool defaultStatus*: cstring + devicePixelRatio*: float innerHeight*, innerWidth*: int locationbar*: ref TLocationBar menubar*: ref TMenuBar @@ -53,11 +54,15 @@ type pageXOffset*, pageYOffset*: int personalbar*: ref TPersonalBar scrollbars*: ref TScrollBars + scrollX*: float + scrollY*: float statusbar*: ref TStatusBar status*: cstring toolbar*: ref TToolBar frames*: seq[TFrame] screen*: Screen + performance*: Performance + onpopstate*: proc (event: Event) Frame* = ref FrameObj FrameObj {.importc.} = object of WindowObj @@ -171,6 +176,12 @@ type text*: cstring value*: cstring + TextAreaElement* = ref object of ElementObj + value*: cstring + selectionStart*, selectionEnd*: int + selectionDirection*: cstring + rows*, cols*: int + FormElement* = ref FormObj FormObj {.importc.} = object of ElementObj action*: cstring @@ -190,7 +201,7 @@ type vspace*: int width*: int - Style = ref StyleObj + Style* = ref StyleObj StyleObj {.importc.} = object of RootObj background*: cstring backgroundAttachment*: cstring @@ -253,6 +264,8 @@ type minHeight*: cstring minWidth*: cstring overflow*: cstring + overflowX*: cstring + overflowY*: cstring padding*: cstring paddingBottom*: cstring paddingLeft*: cstring @@ -400,12 +413,47 @@ type once*: bool passive*: bool + BoundingRect* {.importc.} = ref object + top*, bottom*, left*, right*, x*, y*, width*, height*: float + + PerformanceMemory* {.importc.} = ref object + jsHeapSizeLimit*: float + totalJSHeapSize*: float + usedJSHeapSize*: float + + PerformanceTiming* {.importc.} = ref object + connectStart*: float + domComplete*: float + domContentLoadedEventEnd*: float + domContentLoadedEventStart*: float + domInteractive*: float + domLoading*: float + domainLookupEnd*: float + domainLookupStart*: float + fetchStart*: float + loadEventEnd*: float + loadEventStart*: float + navigationStart*: float + redirectEnd*: float + redirectStart*: float + requestStart*: float + responseEnd*: float + responseStart*: float + secureConnectionStart*: float + unloadEventEnd*: float + unloadEventStart*: float + + Performance* {.importc.} = ref object + memory*: PerformanceMemory + timing*: PerformanceTiming + {.push importcpp.} # EventTarget "methods" proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), useCapture: bool = false) proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), options: AddEventListenerOptions) proc removeEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), useCapture: bool = false) +proc dispatchEvent*(et: EventTarget, ev: Event) # Window "methods" proc alert*(w: Window, msg: cstring) @@ -451,6 +499,7 @@ proc cloneNode*(n: Node, copyContent: bool): Node proc deleteData*(n: Node, start, len: int) proc getAttribute*(n: Node, attr: cstring): cstring proc getAttributeNode*(n: Node, attr: cstring): Node +proc getBoundingClientRect*(e: Node): BoundingRect proc hasChildNodes*(n: Node): bool proc insertBefore*(n, newNode, before: Node) proc insertData*(n: Node, position: int, data: cstring) @@ -459,7 +508,7 @@ proc removeAttributeNode*(n, attr: Node) proc removeChild*(n, child: Node) proc replaceChild*(n, newNode, oldNode: Node) proc replaceData*(n: Node, start, len: int, text: cstring) -proc scrollIntoView*(n: Node) +proc scrollIntoView*(n: Node, alignToTop: bool=true) proc setAttribute*(n: Node, name, value: cstring) proc setAttributeNode*(n: Node, attr: Node) @@ -530,6 +579,9 @@ proc preventDefault*(ev: Event) proc identifiedTouch*(list: TouchList): Touch proc item*(list: TouchList, i: int): Touch +# Performance "methods" +proc now*(p: Performance): float + {.pop.} var @@ -552,6 +604,7 @@ proc parseFloat*(s: cstring): BiggestFloat {.importc, nodecl.} proc parseInt*(s: cstring): int {.importc, nodecl.} proc parseInt*(s: cstring, radix: int):int {.importc, nodecl.} +proc newEvent*(name: cstring): Event {.importcpp: "new Event(@)", constructor.} type TEventHandlers* {.deprecated.} = EventTargetObj diff --git a/lib/js/jscore.nim b/lib/js/jscore.nim new file mode 100644 index 000000000..bf64b0794 --- /dev/null +++ b/lib/js/jscore.nim @@ -0,0 +1,96 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2018 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module wraps core JavaScript functions. +## +## Unless your application has very +## specific requirements and solely targets JavaScript, you should be using +## the relevant functions in the ``math``, ``json``, and ``times`` stdlib +## modules instead. + +when not defined(js) and not defined(Nimdoc): + {.error: "This module only works on the JavaScript platform".} + +type + MathLib* = ref object + JsonLib* = ref object + DateLib* = ref object + DateTime* = ref object + +var + Math* {.importc, nodecl.}: MathLib + Date* {.importc, nodecl.}: DateLib + JSON* {.importc, nodecl.}: JsonLib + +# Math library +proc abs*(m: MathLib, a: SomeNumber): SomeNumber {.importcpp.} +proc acos*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc acosh*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc asin*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc asinh*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc atan*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc atan2*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc atanh*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc cbrt*(m: MathLib, f: SomeFloat): SomeFloat {.importcpp.} +proc ceil*(m: MathLib, f: SomeFloat): SomeFloat {.importcpp.} +proc clz32*(m: MathLib, f: SomeInteger): int {.importcpp.} +proc cos*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc cosh*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc exp*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc expm1*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc floor*(m: MathLib, f: SomeFloat): int {.importcpp.} +proc fround*(m: MathLib, f: SomeFloat): float32 {.importcpp.} +proc hypot*(m: MathLib, args: varargs[distinct SomeNumber]): float {.importcpp.} +proc imul*(m: MathLib, a, b: int32): int32 {.importcpp.} +proc log*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc log10*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc log1p*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc log2*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc max*(m: MathLib, a, b: SomeNumber): SomeNumber {.importcpp.} +proc min*[T: SomeNumber | JsRoot](m: MathLib, a, b: T): T {.importcpp.} +proc pow*(m: MathLib, a, b: distinct SomeNumber): float {.importcpp.} +proc random*(m: MathLib): float {.importcpp.} +proc round*(m: MathLib, f: SomeFloat): int {.importcpp.} +proc sign*(m: MathLib, f: SomeNumber): int {.importcpp.} +proc sin*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc sinh*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc sqrt*(m: MathLib, f: SomeFloat): SomeFloat {.importcpp.} +proc tan*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc tanh*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc trunc*(m: MathLib, f: SomeFloat): int {.importcpp.} + +# Date library +proc now*(d: DateLib): int {.importcpp.} +proc UTC*(d: DateLib): int {.importcpp.} +proc parse*(d: DateLib, s: cstring): int {.importcpp.} + +proc newDate*(): DateTime {. + importcpp: "new Date()".} + +proc newDate*(date: int|string): DateTime {. + importcpp: "new Date(#)".} + +proc newDate*(year, month, day, hours, minutes, + seconds, milliseconds: int): DateTime {. + importcpp: "new Date(#,#,#,#,#,#,#)".} + +proc getDay*(d: DateTime): int {.importcpp.} +proc getFullYear*(d: DateTime): int {.importcpp.} +proc getHours*(d: DateTime): int {.importcpp.} +proc getMilliseconds*(d: DateTime): int {.importcpp.} +proc getMinutes*(d: DateTime): int {.importcpp.} +proc getMonth*(d: DateTime): int {.importcpp.} +proc getSeconds*(d: DateTime): int {.importcpp.} +proc getYear*(d: DateTime): int {.importcpp.} +proc getTime*(d: DateTime): int {.importcpp.} +proc toString*(d: DateTime): cstring {.importcpp.} + +#JSON library +proc stringify*(l: JsonLib, s: JsRoot): cstring {.importcpp.} +proc parse*(l: JsonLib, s: cstring): JsRoot {.importcpp.} diff --git a/lib/js/jsffi.nim b/lib/js/jsffi.nim index f34efe9a2..7b44c57c7 100644 --- a/lib/js/jsffi.nim +++ b/lib/js/jsffi.nim @@ -70,22 +70,29 @@ template mangleJsName(name: cstring): cstring = "mangledName" & $nameCounter type - JsRoot* = ref object of RootObj - ## Root type of both JsObject and JsAssoc JsObject* = ref object of JsRoot ## Dynamically typed wrapper around a JavaScript object. JsAssoc*[K, V] = ref object of JsRoot ## Statically typed wrapper around a JavaScript object. - NotString = concept c - c isnot string + js* = JsObject -var jsarguments* {.importc: "arguments", nodecl}: JsObject - ## JavaScript's arguments pseudo-variable +var + jsArguments* {.importc: "arguments", nodecl}: JsObject + ## JavaScript's arguments pseudo-variable + jsNull* {.importc: "null", nodecl.}: JsObject + ## JavaScript's null literal + jsUndefined* {.importc: "undefined", nodecl.}: JsObject + ## JavaScript's undefined literal + jsDirname* {.importc: "__dirname", nodecl.}: cstring + ## JavaScript's __dirname pseudo-variable + jsFilename* {.importc: "__filename", nodecl.}: cstring + ## JavaScript's __filename pseudo-variable # New proc newJsObject*: JsObject {. importcpp: "{@}" .} ## Creates a new empty JsObject + proc newJsAssoc*[K, V]: JsAssoc[K, V] {. importcpp: "{@}" .} ## Creates a new empty JsAssoc with key type `K` and value type `V`. @@ -97,13 +104,16 @@ proc hasOwnProperty*(x: JsObject, prop: cstring): bool proc jsTypeOf*(x: JsObject): cstring {. importcpp: "typeof(#)" .} ## Returns the name of the JsObject's JavaScript type as a cstring. -proc jsnew*(x: auto): JsObject {.importcpp: "(new #)".} +proc jsNew*(x: auto): JsObject {.importcpp: "(new #)".} ## Turns a regular function call into an invocation of the ## JavaScript's `new` operator -proc jsdelete*(x: auto): JsObject {.importcpp: "(delete #)".} +proc jsDelete*(x: auto): JsObject {.importcpp: "(delete #)".} ## JavaScript's `delete` operator +proc require*(module: cstring): JsObject {.importc.} + ## JavaScript's `require` function + # Conversion to and from JsObject proc to*(x: JsObject, T: typedesc): T {. importcpp: "(#)" .} ## Converts a JsObject `x` to type `T`. @@ -155,7 +165,7 @@ proc `[]=`*[T](obj: JsObject, field: cstring, val: T) {. importcpp: setImpl .} proc `[]=`*[T](obj: JsObject, field: int, val: T) {. importcpp: setImpl .} ## Set the value of a property of name `field` in a JsObject `obj` to `v`. -proc `[]`*[K: NotString, V](obj: JsAssoc[K, V], field: K): V +proc `[]`*[K: not string, V](obj: JsAssoc[K, V], field: K): V {. importcpp: getImpl .} ## Return the value of a property of name `field` from a JsAssoc `obj`. @@ -163,7 +173,7 @@ proc `[]`*[V](obj: JsAssoc[string, V], field: cstring): V {. importcpp: getImpl .} ## Return the value of a property of name `field` from a JsAssoc `obj`. -proc `[]=`*[K: NotString, V](obj: JsAssoc[K, V], field: K, val: V) +proc `[]=`*[K: not string, V](obj: JsAssoc[K, V], field: K, val: V) {. importcpp: setImpl .} ## Set the value of a property of name `field` in a JsAssoc `obj` to `v`. diff --git a/lib/nimbase.h b/lib/nimbase.h index a03407c4f..507108712 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -107,6 +107,8 @@ __clang__ # define N_INLINE(rettype, name) rettype __inline name #endif +#define N_INLINE_PTR(rettype, name) rettype (*name) + #if defined(__POCC__) # define NIM_CONST /* PCC is really picky with const modifiers */ # undef _MSC_VER /* Yeah, right PCC defines _MSC_VER even if it is @@ -129,13 +131,13 @@ __clang__ defined __DMC__ || \ defined __BORLANDC__ ) # define NIM_THREADVAR __declspec(thread) +#elif defined(__TINYC__) || defined(__GENODE__) +# define NIM_THREADVAR /* note that ICC (linux) and Clang are covered by __GNUC__ */ #elif defined __GNUC__ || \ defined __SUNPRO_C || \ defined __xlC__ # define NIM_THREADVAR __thread -#elif defined __TINYC__ -# define NIM_THREADVAR #else # error "Cannot define NIM_THREADVAR" #endif @@ -264,6 +266,11 @@ __clang__ # define HAVE_STDINT_H #endif +/* wrap all Nim typedefs into namespace Nim */ +#ifdef USE_NIM_NAMESPACE +namespace Nim { +#endif + /* bool types (C++ has it): */ #ifdef __cplusplus # ifndef NIM_TRUE @@ -413,8 +420,8 @@ typedef struct TStringDesc* string; # endif #endif -typedef struct TFrame TFrame; -struct TFrame { +typedef struct TFrame_ TFrame; +struct TFrame_ { TFrame* prev; NCSTRING procname; NI line; @@ -476,6 +483,10 @@ static inline void GCGuard (void *ptr) { asm volatile ("" :: "X" (ptr)); } "error: 'Nim_and_C_compiler_disagree_on_target_architecture' declared as an array with a negative size" */ typedef int Nim_and_C_compiler_disagree_on_target_architecture[sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8 ? 1 : -1]; +#ifdef USE_NIM_NAMESPACE +} +#endif + #ifdef __cplusplus # define NIM_EXTERNC extern "C" #else @@ -493,11 +504,6 @@ typedef int Nim_and_C_compiler_disagree_on_target_architecture[sizeof(NI) == siz # include <sys/types.h> #endif -#if defined(__GENODE__) -#include <libc/component.h> -extern Libc::Env *genodeEnv; -#endif - /* Compile with -d:checkAbi and a sufficiently C11:ish compiler to enable */ #define NIM_CHECK_SIZE(typ, sz) \ _Static_assert(sizeof(typ) == sz, "Nim & C disagree on type size") diff --git a/lib/nintendoswitch/switch_memory.nim b/lib/nintendoswitch/switch_memory.nim new file mode 100644 index 000000000..f34bd363a --- /dev/null +++ b/lib/nintendoswitch/switch_memory.nim @@ -0,0 +1,36 @@ +## All of these library headers and source can be found in the github repo +## https://github.com/switchbrew/libnx. + +const virtMemHeader = "<switch/kernel/virtmem.h>" +const svcHeader = "<switch/kernel/svc.h>" +const mallocHeader = "<malloc.h>" + +## Aligns a block of memory with request `size` to `bytes` size. For +## example, a request of memalign(0x1000, 0x1001) == 0x2000 bytes allocated +proc memalign*(bytes: csize, size: csize): pointer {.importc: "memalign", + header: mallocHeader.} + +# Should be required, but not needed now because of how +# svcUnmapMemory frees all memory +#proc free*(address: pointer) {.importc: "free", +# header: mallocHeader.} + +## Maps a memaligned block of memory from `src_addr` to `dst_addr`. The +## Nintendo Switch requires this call in order to make use of memory, otherwise +## an invalid memory access occurs. +proc svcMapMemory*(dst_addr: pointer; src_addr: pointer; size: uint64): uint32 {. + importc: "svcMapMemory", header: svcHeader.} + +## Unmaps (frees) all memory from both `dst_addr` and `src_addr`. **Must** be called +## whenever svcMapMemory is used. The Switch will expect all memory to be allocated +## before gfxExit() calls (<switch/gfx/gfx.h>) +proc svcUnmapMemory*(dst_addr: pointer; src_addr: pointer; size: uint64): uint32 {. + importc: "svcUnmapMemory", header: svcHeader.} + +proc virtmemReserveMap*(size: csize): pointer {.importc: "virtmemReserveMap", + header: virtMemHeader.} + +# Should be required, but not needed now because of how +# svcUnmapMemory frees all memory +#proc virtmemFreeMap*(address: pointer; size: csize) {.importc: "virtmemFreeMap", +# header: virtMemHeader.} diff --git a/lib/packages/docutils/docutils.babel b/lib/packages/docutils/docutils.babel deleted file mode 100644 index 1ed86ca05..000000000 --- a/lib/packages/docutils/docutils.babel +++ /dev/null @@ -1,6 +0,0 @@ -[Package] -name = "docutils" -version = "0.9.0" -author = "Andreas Rumpf" -description = "Nimrod's reStructuredText processor." -license = "MIT" diff --git a/lib/packages/docutils/docutils.nimble b/lib/packages/docutils/docutils.nimble new file mode 100644 index 000000000..e32cc6bdb --- /dev/null +++ b/lib/packages/docutils/docutils.nimble @@ -0,0 +1,5 @@ +name = "docutils" +version = "0.10.0" +author = "Andreas Rumpf" +description = "Nim's reStructuredText processor." +license = "MIT" diff --git a/lib/packages/docutils/highlite.nim b/lib/packages/docutils/highlite.nim index 2a58854a6..4d444603e 100644 --- a/lib/packages/docutils/highlite.nim +++ b/lib/packages/docutils/highlite.nim @@ -13,6 +13,7 @@ import strutils +from algorithm import binarySearch type TokenClass* = enum @@ -129,7 +130,7 @@ proc nimNumber(g: var GeneralTokenizer, position: int): int = const OpChars = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^', '.', - '|', '=', '%', '&', '$', '@', '~', ':', '\x80'..'\xFF'} + '|', '=', '%', '&', '$', '@', '~', ':'} proc nimNextToken(g: var GeneralTokenizer) = const @@ -365,32 +366,10 @@ proc generalStrLit(g: var GeneralTokenizer, position: int): int = result = pos proc isKeyword(x: openArray[string], y: string): int = - var a = 0 - var b = len(x) - 1 - while a <= b: - var mid = (a + b) div 2 - var c = cmp(x[mid], y) - if c < 0: - a = mid + 1 - elif c > 0: - b = mid - 1 - else: - return mid - result = - 1 + binarySearch(x, y) proc isKeywordIgnoreCase(x: openArray[string], y: string): int = - var a = 0 - var b = len(x) - 1 - while a <= b: - var mid = (a + b) div 2 - var c = cmpIgnoreCase(x[mid], y) - if c < 0: - a = mid + 1 - elif c > 0: - b = mid - 1 - else: - return mid - result = - 1 + binarySearch(x, y, cmpIgnoreCase) type TokenizerFlag = enum @@ -902,7 +881,7 @@ when isMainModule: break except: echo filename, " not found" - doAssert(not keywords.isNil, "Couldn't read any keywords.txt file!") + doAssert(keywords.len > 0, "Couldn't read any keywords.txt file!") for i in 0..min(keywords.len, nimKeywords.len)-1: doAssert keywords[i] == nimKeywords[i], "Unexpected keyword" doAssert keywords.len == nimKeywords.len, "No matching lengths" diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index 223fc836a..d35f109e7 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -43,8 +43,8 @@ type mwUnsupportedField MsgHandler* = proc (filename: string, line, col: int, msgKind: MsgKind, - arg: string) {.nimcall.} ## what to do in case of an error - FindFileHandler* = proc (filename: string): string {.nimcall.} + arg: string) {.closure.} ## what to do in case of an error + FindFileHandler* = proc (filename: string): string {.closure.} const messages: array[MsgKind, string] = [ @@ -363,6 +363,7 @@ proc addNodes(n: PRstNode): string = addNodesAux(n, result) proc rstnodeToRefnameAux(n: PRstNode, r: var string, b: var bool) = + if n == nil: return if n.kind == rnLeaf: for i in countup(0, len(n.text) - 1): case n.text[i] @@ -853,7 +854,6 @@ type DirKind = enum # must be ordered alphabetically! dkNone, dkAuthor, dkAuthors, dkCode, dkCodeBlock, dkContainer, dkContents, dkFigure, dkImage, dkInclude, dkIndex, dkRaw, dkTitle -{.deprecated: [TDirKind: DirKind].} const DirIds: array[0..12, string] = ["", "author", "authors", "code", @@ -1114,7 +1114,6 @@ proc parseHeadline(p: var RstParser): PRstNode = type IntSeq = seq[int] -{.deprecated: [TIntSeq: IntSeq].} proc tokEnd(p: RstParser): int = result = p.tok[p.idx].col + len(p.tok[p.idx].symbol) - 1 @@ -1388,7 +1387,7 @@ proc parseSectionWrapper(p: var RstParser): PRstNode = result = result.sons[0] proc `$`(t: Token): string = - result = $t.kind & ' ' & (if isNil(t.symbol): "NIL" else: t.symbol) + result = $t.kind & ' ' & t.symbol proc parseDoc(p: var RstParser): PRstNode = result = parseSectionWrapper(p) @@ -1408,8 +1407,6 @@ type hasArg, hasOptions, argIsFile, argIsWord DirFlags = set[DirFlag] SectionParser = proc (p: var RstParser): PRstNode {.nimcall.} -{.deprecated: [TDirFlag: DirFlag, TDirFlags: DirFlags, - TSectionParser: SectionParser].} proc parseDirective(p: var RstParser, flags: DirFlags): PRstNode = ## Parses arguments and options for a directive block. diff --git a/lib/packages/docutils/rstast.nim b/lib/packages/docutils/rstast.nim index 7be4470c1..4a77b4f34 100644 --- a/lib/packages/docutils/rstast.nim +++ b/lib/packages/docutils/rstast.nim @@ -70,8 +70,6 @@ type ## the document or the section level*: int ## valid for some node kinds sons*: RstNodeSeq ## the node's sons -{.deprecated: [TRstNodeKind: RstNodeKind, TRstNodeSeq: RstNodeSeq, - TRstNode: RstNode].} proc len*(n: PRstNode): int = result = len(n.sons) @@ -99,7 +97,6 @@ type RenderContext {.pure.} = object indent: int verbatim: int -{.deprecated: [TRenderContext: RenderContext].} proc renderRstToRst(d: var RenderContext, n: PRstNode, result: var string) {.gcsafe.} @@ -296,9 +293,9 @@ proc renderRstToJsonNode(node: PRstNode): JsonNode = (key: "kind", val: %($node.kind)), (key: "level", val: %BiggestInt(node.level)) ] - if node.text != nil: + if node.text.len > 0: result.add("text", %node.text) - if node.sons != nil and len(node.sons) > 0: + if len(node.sons) > 0: var accm = newSeq[JsonNode](len(node.sons)) for i, son in node.sons: accm[i] = renderRstToJsonNode(son) diff --git a/lib/packages/docutils/rstgen.nim b/lib/packages/docutils/rstgen.nim index e6c95b59e..a68ae928c 100644 --- a/lib/packages/docutils/rstgen.nim +++ b/lib/packages/docutils/rstgen.nim @@ -188,13 +188,16 @@ proc addTexChar(dest: var string, c: char) = of '`': add(dest, "\\symbol{96}") else: add(dest, c) -var splitter*: string = "<wbr />" - proc escChar*(target: OutputTarget, dest: var string, c: char) {.inline.} = case target of outHtml: addXmlChar(dest, c) of outLatex: addTexChar(dest, c) +proc addSplitter(target: OutputTarget; dest: var string) {.inline.} = + case target + of outHtml: add(dest, "<wbr />") + of outLatex: add(dest, "\\-") + proc nextSplitPoint*(s: string, start: int): int = result = start while result < len(s) + 0: @@ -208,15 +211,16 @@ proc nextSplitPoint*(s: string, start: int): int = dec(result) # last valid index proc esc*(target: OutputTarget, s: string, splitAfter = -1): string = + ## Escapes the HTML. result = "" if splitAfter >= 0: var partLen = 0 var j = 0 while j < len(s): var k = nextSplitPoint(s, j) - if (splitter != " ") or (partLen + k - j + 1 > splitAfter): - partLen = 0 - add(result, splitter) + #if (splitter != " ") or (partLen + k - j + 1 > splitAfter): + partLen = 0 + addSplitter(target, result) for i in countup(j, k): escChar(target, result, s[i]) inc(partLen, k - j + 1) j = k + 1 @@ -239,7 +243,7 @@ proc dispA(target: OutputTarget, dest: var string, else: addf(dest, tex, args) proc `or`(x, y: string): string {.inline.} = - result = if x.isNil: y else: x + result = if x.len == 0: y else: x proc renderRstToOut*(d: var RstGenerator, n: PRstNode, result: var string) ## Writes into ``result`` the rst ast ``n`` using the ``d`` configuration. @@ -308,7 +312,6 @@ proc setIndexTerm*(d: var RstGenerator, id, term: string, ## The index won't be written to disk unless you call `writeIndexFile() ## <#writeIndexFile>`_. The purpose of the index is documented in the `docgen ## tools guide <docgen.html#index-switch>`_. - assert(not d.theIndex.isNil) var entry = term isTitle = false @@ -333,7 +336,7 @@ proc hash(n: PRstNode): int = result = hash(n.text) elif n.len > 0: result = hash(n.sons[0]) - for i in 1 .. <len(n): + for i in 1 ..< len(n): result = result !& hash(n.sons[i]) result = !$result @@ -384,20 +387,16 @@ proc hash(x: IndexEntry): Hash = ## Returns the hash for the combined fields of the type. ## ## The hash is computed as the chained hash of the individual string hashes. - assert(not x.keyword.isNil) - assert(not x.link.isNil) result = x.keyword.hash !& x.link.hash - result = result !& (x.linkTitle or "").hash - result = result !& (x.linkDesc or "").hash + result = result !& x.linkTitle.hash + result = result !& x.linkDesc.hash result = !$result proc `<-`(a: var IndexEntry, b: IndexEntry) = shallowCopy a.keyword, b.keyword shallowCopy a.link, b.link - if b.linkTitle.isNil: a.linkTitle = nil - else: shallowCopy a.linkTitle, b.linkTitle - if b.linkDesc.isNil: a.linkDesc = nil - else: shallowCopy a.linkDesc, b.linkDesc + shallowCopy a.linkTitle, b.linkTitle + shallowCopy a.linkDesc, b.linkDesc proc sortIndex(a: var openArray[IndexEntry]) = # we use shellsort here; fast and simple @@ -441,14 +440,15 @@ proc generateSymbolIndex(symbols: seq[IndexEntry]): string = while j < symbols.len and keyword == symbols[j].keyword: let url = symbols[j].link.escapeLink - text = if not symbols[j].linkTitle.isNil: symbols[j].linkTitle else: url - desc = if not symbols[j].linkDesc.isNil: symbols[j].linkDesc else: "" + text = if symbols[j].linkTitle.len > 0: symbols[j].linkTitle else: url + desc = if symbols[j].linkDesc.len > 0: symbols[j].linkDesc else: "" if desc.len > 0: result.addf("""<li><a class="reference external" - title="$3" href="$1">$2</a></li> + title="$3" data-doc-search-tag="$2" href="$1">$2</a></li> """, [url, text, desc]) else: - result.addf("""<li><a class="reference external" href="$1">$2</a></li> + result.addf("""<li><a class="reference external" + data-doc-search-tag="$2" href="$1">$2</a></li> """, [url, text]) inc j result.add("</ul></dd>\n") @@ -489,6 +489,7 @@ proc generateDocumentationTOC(entries: seq[IndexEntry]): string = # Build a list of levels and extracted titles to make processing easier. var titleRef: string + titleTag: string levels: seq[tuple[level: int, text: string]] L = 0 level = 1 @@ -515,14 +516,14 @@ proc generateDocumentationTOC(entries: seq[IndexEntry]): string = let link = entries[L].link if link.isDocumentationTitle: titleRef = link + titleTag = levels[L].text else: result.add(level.indentToLevel(levels[L].level)) - result.add("<li><a href=\"" & link & "\">" & - levels[L].text & "</a></li>\n") + result.addf("""<li><a class="reference" data-doc-search-tag="$1" href="$2"> + $3</a></li> + """, [titleTag & " : " & levels[L].text, link, levels[L].text]) inc L result.add(level.indentToLevel(1) & "</ul>\n") - assert(not titleRef.isNil, - "Can't use this proc on an API index, docs always have a title entry") proc generateDocumentationIndex(docs: IndexedDocs): string = ## Returns all the documentation TOCs in an HTML hierarchical list. @@ -589,7 +590,7 @@ proc readIndexDir(dir: string): fileEntries[F].keyword = line.substr(0, s-1) fileEntries[F].link = line.substr(s+1) # See if we detect a title, a link without a `#foobar` trailing part. - if title.keyword.isNil and fileEntries[F].link.isDocumentationTitle: + if title.keyword.len == 0 and fileEntries[F].link.isDocumentationTitle: title.keyword = fileEntries[F].keyword title.link = fileEntries[F].link @@ -600,15 +601,15 @@ proc readIndexDir(dir: string): fileEntries[F].linkTitle = extraCols[1].unquoteIndexColumn fileEntries[F].linkDesc = extraCols[2].unquoteIndexColumn else: - fileEntries[F].linkTitle = nil - fileEntries[F].linkDesc = nil + fileEntries[F].linkTitle = "" + fileEntries[F].linkDesc = "" inc F # Depending on type add this to the list of symbols or table of APIs. - if title.keyword.isNil: + if title.keyword.len == 0: for i in 0 .. <F: # Don't add to symbols TOC entries (they start with a whitespace). let toc = fileEntries[i].linkTitle - if not toc.isNil and toc.len > 0 and toc[0] == ' ': + if toc.len > 0 and toc[0] == ' ': continue # Ok, non TOC entry, add it. setLen(result.symbols, L + 1) @@ -649,7 +650,6 @@ proc mergeIndexes*(dir: string): string = ## Returns the merged and sorted indices into a single HTML block which can ## be further embedded into nimdoc templates. var (modules, symbols, docs) = readIndexDir(dir) - assert(not symbols.isNil) result = "" # Generate a quick jump list of documents. @@ -769,43 +769,45 @@ proc renderTocEntries*(d: var RstGenerator, j: var int, lvl: int, result.add(tmp) proc renderImage(d: PDoc, n: PRstNode, result: var string) = - template valid(s): bool = - s.len > 0 and allCharsInSet(s, {'.','/',':','%','_','\\','\128'..'\xFF'} + - Digits + Letters + WhiteSpace) let arg = getArgument(n) - isObject = arg.toLower().endsWith(".svg") var options = "" - content = "" - var s = getFieldValue(n, "scale") - if s.valid: dispA(d.target, options, if isObject: "" else: " scale=\"$1\"", - " scale=$1", [strip(s)]) - s = getFieldValue(n, "height") - if s.valid: dispA(d.target, options, " height=\"$1\"", " height=$1", [strip(s)]) + var s = esc(d.target, getFieldValue(n, "scale").strip()) + if s.len > 0: + dispA(d.target, options, " scale=\"$1\"", " scale=$1", [s]) - s = getFieldValue(n, "width") - if s.valid: dispA(d.target, options, " width=\"$1\"", " width=$1", [strip(s)]) + s = esc(d.target, getFieldValue(n, "height").strip()) + if s.len > 0: + dispA(d.target, options, " height=\"$1\"", " height=$1", [s]) - s = getFieldValue(n, "alt") - if s.valid: - # <object> displays its content if it cannot render the image - if isObject: dispA(d.target, content, "$1", "", [strip(s)]) - else: dispA(d.target, options, " alt=\"$1\"", "", [strip(s)]) + s = esc(d.target, getFieldValue(n, "width").strip()) + if s.len > 0: + dispA(d.target, options, " width=\"$1\"", " width=$1", [s]) - s = getFieldValue(n, "align") - if s.valid: dispA(d.target, options, " align=\"$1\"", "", [strip(s)]) + s = esc(d.target, getFieldValue(n, "alt").strip()) + if s.len > 0: + dispA(d.target, options, " alt=\"$1\"", "", [s]) + + s = esc(d.target, getFieldValue(n, "align").strip()) + if s.len > 0: + dispA(d.target, options, " align=\"$1\"", "", [s]) if options.len > 0: options = dispF(d.target, "$1", "[$1]", [options]) - if arg.valid: - let htmlOut = if isObject: - "<object data=\"$1\" type=\"image/svg+xml\"$2 >" & content & "</object>" - else: - "<img src=\"$1\"$2 />" - dispA(d.target, result, htmlOut, "\\includegraphics$2{$1}", - [arg, options]) + var htmlOut = "" + if arg.endsWith(".mp4") or arg.endsWith(".ogg") or + arg.endsWith(".webm"): + htmlOut = """ + <video src="$1"$2 autoPlay='true' loop='true' muted='true'> + Sorry, your browser doesn't support embedded videos + </video> + """ + else: + htmlOut = "<img src=\"$1\"$2/>" + dispA(d.target, result, htmlOut, "\\includegraphics$2{$1}", + [esc(d.target, arg), options]) if len(n) >= 3: renderRstToOut(d, n.sons[2], result) proc renderSmiley(d: PDoc, n: PRstNode, result: var string) = @@ -820,7 +822,7 @@ proc parseCodeBlockField(d: PDoc, n: PRstNode, params: var CodeBlockParams) = ## ## This supports the special ``default-language`` internal string generated ## by the ``rst`` module to communicate a specific default language. - case n.getArgument.toLower + case n.getArgument.toLowerAscii of "number-lines": params.numberLines = true # See if the field has a parameter specifying a different line than 1. @@ -836,8 +838,11 @@ proc parseCodeBlockField(d: PDoc, n: PRstNode, params: var CodeBlockParams) = params.filename = n.getFieldValue.strip of "test": params.testCmd = n.getFieldValue.strip - if params.testCmd.len == 0: params.testCmd = "nim c -r $1" - of "status": + if params.testCmd.len == 0: + params.testCmd = "nim c -r $1" + else: + params.testCmd = unescape(params.testCmd) + of "status", "exitcode": var status: int if parseInt(n.getFieldValue, status) > 0: params.status = status @@ -878,7 +883,8 @@ proc buildLinesHTMLTable(d: PDoc; params: CodeBlockParams, code: string): inc d.listingCounter let id = $d.listingCounter if not params.numberLines: - result = (d.config.getOrDefault"doc.listing_start" % [id, $params.lang], + result = (d.config.getOrDefault"doc.listing_start" % + [id, sourceLanguageToStr[params.lang]], d.config.getOrDefault"doc.listing_end" % id) return @@ -891,7 +897,8 @@ proc buildLinesHTMLTable(d: PDoc; params: CodeBlockParams, code: string): line.inc codeLines.dec result.beginTable.add("</pre></td><td>" & ( - d.config.getOrDefault"doc.listing_start" % [id, $params.lang])) + d.config.getOrDefault"doc.listing_start" % + [id, sourceLanguageToStr[params.lang]])) result.endTable = (d.config.getOrDefault"doc.listing_end" % id) & "</td></tr></tbody></table>" & ( d.config.getOrDefault"doc.listing_button" % id) @@ -941,7 +948,7 @@ proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) = proc renderContainer(d: PDoc, n: PRstNode, result: var string) = var tmp = "" renderRstToOut(d, n.sons[2], tmp) - var arg = strip(getArgument(n)) + var arg = esc(d.target, strip(getArgument(n))) if arg == "": dispA(d.target, result, "<div>$1</div>", "$1", [tmp]) else: diff --git a/lib/posix/epoll.nim b/lib/posix/epoll.nim index c5ed1a873..2d38137bb 100644 --- a/lib/posix/epoll.nim +++ b/lib/posix/epoll.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -{.deadCodeElim:on.} +{.deadCodeElim: on.} # dce option deprecated from posix import SocketHandle diff --git a/lib/posix/inotify.nim b/lib/posix/inotify.nim index a206f8067..359e88617 100644 --- a/lib/posix/inotify.nim +++ b/lib/posix/inotify.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -{.deadCodeElim:on.} +{.deadCodeElim: on.} # dce option deprecated # Get the platform-dependent flags. # Structure describing an inotify event. diff --git a/lib/posix/kqueue.nim b/lib/posix/kqueue.nim index 730491a53..18b47f5d5 100644 --- a/lib/posix/kqueue.nim +++ b/lib/posix/kqueue.nim @@ -7,8 +7,6 @@ # distribution, for details about the copyright. # -{.deadCodeElim:on.} - from posix import Timespec when defined(macosx) or defined(freebsd) or defined(openbsd) or @@ -61,7 +59,7 @@ const EV_CLEAR* = 0x0020 ## Clear event state after reporting. EV_RECEIPT* = 0x0040 ## Force EV_ERROR on success, data == 0 EV_DISPATCH* = 0x0080 ## Disable event after reporting. - + EV_SYSFLAGS* = 0xF000 ## Reserved by system EV_DROP* = 0x1000 ## Not should be dropped EV_FLAG1* = 0x2000 ## Filter-specific flag @@ -87,10 +85,10 @@ when defined(macosx) or defined(freebsd) or defined(dragonfly): NOTE_FFAND* = 0x40000000'u32 ## AND fflags NOTE_FFOR* = 0x80000000'u32 ## OR fflags NOTE_FFCOPY* = 0xc0000000'u32 ## copy fflags - NOTE_FFCTRLMASK* = 0xc0000000'u32 ## masks for operations + NOTE_FFCTRLMASK* = 0xc0000000'u32 ## masks for operations NOTE_FFLAGSMASK* = 0x00ffffff'u32 - NOTE_TRIGGER* = 0x01000000'u32 ## Cause the event to be triggered + NOTE_TRIGGER* = 0x01000000'u32 ## Cause the event to be triggered ## for output. # data/hint flags for EVFILT_{READ|WRITE}, shared with userspace diff --git a/lib/posix/linux.nim b/lib/posix/linux.nim index 8786ab535..25c7c7979 100644 --- a/lib/posix/linux.nim +++ b/lib/posix/linux.nim @@ -1,4 +1,4 @@ -{.deadCodeElim:on.} +{.deadCodeElim: on.} # dce option deprecated import posix @@ -27,4 +27,4 @@ proc clone*(fn: pointer; child_stack: pointer; flags: cint; arg: pointer; ptid: ptr Pid; tls: pointer; ctid: ptr Pid): cint {.importc, header: "<sched.h>".} -proc pipe2*(a: array[0..1, cint], flags: cint): cint {.importc, header: "<unistd.h>".} \ No newline at end of file +proc pipe2*(a: array[0..1, cint], flags: cint): cint {.importc, header: "<unistd.h>".} diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index fba35868c..c230e6598 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -27,35 +27,35 @@ ## resulting C code will just ``#include <XYZ.h>`` and *not* define the ## symbols declared here. -# This ensures that we don't accidentally generate #includes for files that -# might not exist on a specific platform! The user will get an error only -# if they actualy try to use the missing declaration -{.deadCodeElim: on.} +# Dead code elimination ensures that we don't accidentally generate #includes +# for files that might not exist on a specific platform! The user will get an +# error only if they actualy try to use the missing declaration +{.deadCodeElim: on.} # dce option deprecated # TODO these constants don't seem to be fetched from a header file for unknown # platforms - where do they come from and why are they here? when false: const - C_IRUSR = 0c000400 ## Read by owner. - C_IWUSR = 0c000200 ## Write by owner. - C_IXUSR = 0c000100 ## Execute by owner. - C_IRGRP = 0c000040 ## Read by group. - C_IWGRP = 0c000020 ## Write by group. - C_IXGRP = 0c000010 ## Execute by group. - C_IROTH = 0c000004 ## Read by others. - C_IWOTH = 0c000002 ## Write by others. - C_IXOTH = 0c000001 ## Execute by others. - C_ISUID = 0c004000 ## Set user ID. - C_ISGID = 0c002000 ## Set group ID. - C_ISVTX = 0c001000 ## On directories, restricted deletion flag. - C_ISDIR = 0c040000 ## Directory. - C_ISFIFO = 0c010000 ##FIFO. - C_ISREG = 0c100000 ## Regular file. - C_ISBLK = 0c060000 ## Block special. - C_ISCHR = 0c020000 ## Character special. - C_ISCTG = 0c110000 ## Reserved. - C_ISLNK = 0c120000 ## Symbolic link.</p> - C_ISSOCK = 0c140000 ## Socket. + C_IRUSR = 0o000400 ## Read by owner. + C_IWUSR = 0o000200 ## Write by owner. + C_IXUSR = 0o000100 ## Execute by owner. + C_IRGRP = 0o000040 ## Read by group. + C_IWGRP = 0o000020 ## Write by group. + C_IXGRP = 0o000010 ## Execute by group. + C_IROTH = 0o000004 ## Read by others. + C_IWOTH = 0o000002 ## Write by others. + C_IXOTH = 0o000001 ## Execute by others. + C_ISUID = 0o004000 ## Set user ID. + C_ISGID = 0o002000 ## Set group ID. + C_ISVTX = 0o001000 ## On directories, restricted deletion flag. + C_ISDIR = 0o040000 ## Directory. + C_ISFIFO = 0o010000 ##FIFO. + C_ISREG = 0o100000 ## Regular file. + C_ISBLK = 0o060000 ## Block special. + C_ISCHR = 0o020000 ## Character special. + C_ISCTG = 0o110000 ## Reserved. + C_ISLNK = 0o120000 ## Symbolic link.</p> + C_ISSOCK = 0o140000 ## Socket. const MM_NULLLBL* = nil @@ -82,19 +82,29 @@ const # Special types type Sighandler = proc (a: cint) {.noconv.} +const StatHasNanoseconds* = defined(linux) or defined(freebsd) or + defined(openbsd) or defined(dragonfly) ## \ + ## Boolean flag that indicates if the system supports nanosecond time + ## resolution in the fields of ``Stat``. Note that the nanosecond based fields + ## (``Stat.st_atim``, ``Stat.st_mtim`` and ``Stat.st_ctim``) can be accessed + ## without checking this flag, because this module defines fallback procs + ## when they are not available. + # Platform specific stuff when defined(linux) and defined(amd64): include posix_linux_amd64 +elif defined(nintendoswitch): + include posix_nintendoswitch else: include posix_other # There used to be this name in posix.nim a long time ago, not sure why! {.deprecated: [cSIG_HOLD: SIG_HOLD].} -when not defined(macosx) and not defined(android): +when StatHasNanoseconds: proc st_atime*(s: Stat): Time {.inline.} = - ## Second-granularity time of last access + ## Second-granularity time of last access. result = s.st_atim.tv_sec proc st_mtime*(s: Stat): Time {.inline.} = ## Second-granularity time of last data modification. @@ -102,6 +112,16 @@ when not defined(macosx) and not defined(android): proc st_ctime*(s: Stat): Time {.inline.} = ## Second-granularity time of last status change. result = s.st_ctim.tv_sec +else: + proc st_atim*(s: Stat): TimeSpec {.inline.} = + ## Nanosecond-granularity time of last access. + result.tv_sec = s.st_atime + proc st_mtim*(s: Stat): TimeSpec {.inline.} = + ## Nanosecond-granularity time of last data modification. + result.tv_sec = s.st_mtime + proc st_ctim*(s: Stat): TimeSpec {.inline.} = + ## Nanosecond-granularity time of last data modification. + result.tv_sec = s.st_ctime when hasAioH: proc aio_cancel*(a1: cint, a2: ptr Taiocb): cint {.importc, header: "<aio.h>".} @@ -167,7 +187,7 @@ proc fnmatch*(a1, a2: cstring, a3: cint): cint {.importc, header: "<fnmatch.h>". proc ftw*(a1: cstring, a2: proc (x1: cstring, x2: ptr Stat, x3: cint): cint {.noconv.}, a3: cint): cint {.importc, header: "<ftw.h>".} -when not (defined(linux) and defined(amd64)): +when not (defined(linux) and defined(amd64)) and not defined(nintendoswitch): proc nftw*(a1: cstring, a2: proc (x1: cstring, x2: ptr Stat, x3: cint, x4: ptr FTW): cint {.noconv.}, @@ -208,25 +228,26 @@ proc setlocale*(a1: cint, a2: cstring): cstring {. proc strfmon*(a1: cstring, a2: int, a3: cstring): int {.varargs, importc, header: "<monetary.h>".} -proc mq_close*(a1: Mqd): cint {.importc, header: "<mqueue.h>".} -proc mq_getattr*(a1: Mqd, a2: ptr MqAttr): cint {. - importc, header: "<mqueue.h>".} -proc mq_notify*(a1: Mqd, a2: ptr SigEvent): cint {. - importc, header: "<mqueue.h>".} -proc mq_open*(a1: cstring, a2: cint): Mqd {. - varargs, importc, header: "<mqueue.h>".} -proc mq_receive*(a1: Mqd, a2: cstring, a3: int, a4: var int): int {. - importc, header: "<mqueue.h>".} -proc mq_send*(a1: Mqd, a2: cstring, a3: int, a4: int): cint {. - importc, header: "<mqueue.h>".} -proc mq_setattr*(a1: Mqd, a2, a3: ptr MqAttr): cint {. - importc, header: "<mqueue.h>".} - -proc mq_timedreceive*(a1: Mqd, a2: cstring, a3: int, a4: int, - a5: ptr Timespec): int {.importc, header: "<mqueue.h>".} -proc mq_timedsend*(a1: Mqd, a2: cstring, a3: int, a4: int, - a5: ptr Timespec): cint {.importc, header: "<mqueue.h>".} -proc mq_unlink*(a1: cstring): cint {.importc, header: "<mqueue.h>".} +when not defined(nintendoswitch): + proc mq_close*(a1: Mqd): cint {.importc, header: "<mqueue.h>".} + proc mq_getattr*(a1: Mqd, a2: ptr MqAttr): cint {. + importc, header: "<mqueue.h>".} + proc mq_notify*(a1: Mqd, a2: ptr SigEvent): cint {. + importc, header: "<mqueue.h>".} + proc mq_open*(a1: cstring, a2: cint): Mqd {. + varargs, importc, header: "<mqueue.h>".} + proc mq_receive*(a1: Mqd, a2: cstring, a3: int, a4: var int): int {. + importc, header: "<mqueue.h>".} + proc mq_send*(a1: Mqd, a2: cstring, a3: int, a4: int): cint {. + importc, header: "<mqueue.h>".} + proc mq_setattr*(a1: Mqd, a2, a3: ptr MqAttr): cint {. + importc, header: "<mqueue.h>".} + + proc mq_timedreceive*(a1: Mqd, a2: cstring, a3: int, a4: int, + a5: ptr Timespec): int {.importc, header: "<mqueue.h>".} + proc mq_timedsend*(a1: Mqd, a2: cstring, a3: int, a4: int, + a5: ptr Timespec): cint {.importc, header: "<mqueue.h>".} + proc mq_unlink*(a1: cstring): cint {.importc, header: "<mqueue.h>".} proc getpwnam*(a1: cstring): ptr Passwd {.importc, header: "<pwd.h>".} @@ -585,7 +606,7 @@ proc posix_madvise*(a1: pointer, a2: int, a3: cint): cint {. importc, header: "<sys/mman.h>".} proc posix_mem_offset*(a1: pointer, a2: int, a3: var Off, a4: var int, a5: var cint): cint {.importc, header: "<sys/mman.h>".} -when not (defined(linux) and defined(amd64)): +when not (defined(linux) and defined(amd64)) and not defined(nintendoswitch): proc posix_typed_mem_get_info*(a1: cint, a2: var Posix_typed_mem_info): cint {.importc, header: "<sys/mman.h>".} proc posix_typed_mem_open*(a1: cstring, a2, a3: cint): cint {. @@ -695,12 +716,12 @@ proc sigwait*(a1: var Sigset, a2: var cint): cint {. proc sigwaitinfo*(a1: var Sigset, a2: var SigInfo): cint {. importc, header: "<signal.h>".} - -proc catclose*(a1: Nl_catd): cint {.importc, header: "<nl_types.h>".} -proc catgets*(a1: Nl_catd, a2, a3: cint, a4: cstring): cstring {. - importc, header: "<nl_types.h>".} -proc catopen*(a1: cstring, a2: cint): Nl_catd {. - importc, header: "<nl_types.h>".} +when not defined(nintendoswitch): + proc catclose*(a1: Nl_catd): cint {.importc, header: "<nl_types.h>".} + proc catgets*(a1: Nl_catd, a2, a3: cint, a4: cstring): cstring {. + importc, header: "<nl_types.h>".} + proc catopen*(a1: cstring, a2: cint): Nl_catd {. + importc, header: "<nl_types.h>".} proc sched_get_priority_max*(a1: cint): cint {.importc, header: "<sched.h>".} proc sched_get_priority_min*(a1: cint): cint {.importc, header: "<sched.h>".} @@ -782,11 +803,12 @@ when hasSpawnH: a4: var Tposix_spawnattr, a5, a6: cstringArray): cint {.importc, header: "<spawn.h>".} -proc getcontext*(a1: var Ucontext): cint {.importc, header: "<ucontext.h>".} -proc makecontext*(a1: var Ucontext, a4: proc (){.noconv.}, a3: cint) {. - varargs, importc, header: "<ucontext.h>".} -proc setcontext*(a1: var Ucontext): cint {.importc, header: "<ucontext.h>".} -proc swapcontext*(a1, a2: var Ucontext): cint {.importc, header: "<ucontext.h>".} +when not defined(nintendoswitch): + proc getcontext*(a1: var Ucontext): cint {.importc, header: "<ucontext.h>".} + proc makecontext*(a1: var Ucontext, a4: proc (){.noconv.}, a3: cint) {. + varargs, importc, header: "<ucontext.h>".} + proc setcontext*(a1: var Ucontext): cint {.importc, header: "<ucontext.h>".} + proc swapcontext*(a1, a2: var Ucontext): cint {.importc, header: "<ucontext.h>".} proc readv*(a1: cint, a2: ptr IOVec, a3: cint): int {. importc, header: "<sys/uio.h>".} @@ -973,3 +995,19 @@ template onSignal*(signals: varargs[cint], body: untyped) = proc (sig: cint) {.noconv.} = body ) + +type + RLimit* {.importc: "struct rlimit", + header: "<sys/resource.h>", pure, final.} = object + rlim_cur*: int + rlim_max*: int + ## The getrlimit() and setrlimit() system calls get and set resource limits respectively. + ## Each resource has an associated soft and hard limit, as defined by the RLimit structure + +proc setrlimit*(resource: cint, rlp: var RLimit): cint + {.importc: "setrlimit",header: "<sys/resource.h>".} + ## The setrlimit() system calls sets resource limits. + +proc getrlimit*(resource: cint, rlp: var RLimit): cint + {.importc: "getrlimit",header: "<sys/resource.h>".} + ## The getrlimit() system call gets resource limits. diff --git a/lib/posix/posix_linux_amd64.nim b/lib/posix/posix_linux_amd64.nim index 9e6211b63..4f114d394 100644 --- a/lib/posix/posix_linux_amd64.nim +++ b/lib/posix/posix_linux_amd64.nim @@ -351,8 +351,8 @@ type Timeval* {.importc: "struct timeval", header: "<sys/select.h>", final, pure.} = object ## struct timeval - tv_sec*: clong ## Seconds. - tv_usec*: clong ## Microseconds. + tv_sec*: Time ## Seconds. + tv_usec*: Suseconds ## Microseconds. TFdSet* {.importc: "fd_set", header: "<sys/select.h>", final, pure.} = object abi: array[1024 div (8 * sizeof(clong)), clong] diff --git a/lib/posix/posix_linux_amd64_consts.nim b/lib/posix/posix_linux_amd64_consts.nim index 4b693960e..50b227635 100644 --- a/lib/posix/posix_linux_amd64_consts.nim +++ b/lib/posix/posix_linux_amd64_consts.nim @@ -300,7 +300,7 @@ const IPPROTO_TCP* = cint(6) const IPPROTO_UDP* = cint(17) const INADDR_ANY* = InAddrScalar(0) const INADDR_LOOPBACK* = InAddrScalar(2130706433) -const INADDR_BROADCAST* = InAddrScalar(-1) +const INADDR_BROADCAST* = InAddrScalar(4294967295) const INET_ADDRSTRLEN* = cint(16) const INET6_ADDRSTRLEN* = cint(46) const IPV6_JOIN_GROUP* = cint(20) @@ -433,6 +433,9 @@ const POSIX_MADV_WILLNEED* = cint(3) const POSIX_MADV_DONTNEED* = cint(4) const MAP_POPULATE* = cint(32768) +# <sys/resource.h> +const RLIMIT_NOFILE* = cint(7) + # <sys/select.h> const FD_SETSIZE* = cint(1024) diff --git a/lib/posix/posix_nintendoswitch.nim b/lib/posix/posix_nintendoswitch.nim new file mode 100644 index 000000000..892ea3370 --- /dev/null +++ b/lib/posix/posix_nintendoswitch.nim @@ -0,0 +1,506 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2018 Joey Yakimowich-Payne +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# To be included from posix.nim! + +const + hasSpawnH = true + hasAioH = false + +type + DIR* {.importc: "DIR", header: "<dirent.h>", + incompleteStruct.} = object + +const SIG_HOLD* = cast[SigHandler](2) + +type + SocketHandle* = distinct cint # The type used to represent socket descriptors + +type + Time* {.importc: "time_t", header: "<time.h>".} = distinct clong + + Timespec* {.importc: "struct timespec", + header: "<time.h>", final, pure.} = object ## struct timespec + tv_sec*: Time ## Seconds. + tv_nsec*: clong ## Nanoseconds. + + Dirent* {.importc: "struct dirent", + header: "<dirent.h>", final, pure.} = object ## dirent_t struct + d_ino*: Ino + d_type*: int8 # cuchar really! + d_name*: array[256, cchar] + + Tflock* {.importc: "struct flock", final, pure, + header: "<fcntl.h>".} = object ## flock type + l_type*: cshort ## Type of lock; F_RDLCK, F_WRLCK, F_UNLCK. + l_whence*: cshort ## Flag for starting offset. + l_start*: Off ## Relative offset in bytes. + l_len*: Off ## Size; if 0 then until EOF. + l_pid*: Pid ## Process ID of the process holding the lock; + ## returned with F_GETLK. + + # no struct FTW on linux + + Glob* {.importc: "glob_t", header: "<glob.h>", + final, pure.} = object ## glob_t + gl_pathc*: cint ## Count of paths matched by pattern. + gl_matchc*: cint ## Count of paths matching pattern + gl_offs*: cint ## Slots to reserve at the beginning of gl_pathv. + gl_flags*: cint + gl_pathv*: cstringArray ## Pointer to a list of matched pathnames. + gl_errfunc*: pointer + gl_closedir*: pointer + gl_readdir*: pointer + gl_opendir*: pointer + gl_lstat*: pointer + gl_stat*: pointer + + Group* {.importc: "struct group", header: "<grp.h>", + final, pure.} = object ## struct group + gr_name*: cstring ## The name of the group. + gr_passwd*: cstring + gr_gid*: Gid ## Numerical group ID. + gr_mem*: cstringArray ## Pointer to a null-terminated array of character + ## pointers to member names. + + Iconv* {.importc: "iconv_t", header: "<iconv.h>".} = pointer + ## Identifies the conversion from one codeset to another. + + Lconv* {.importc: "struct lconv", header: "<locale.h>", final, + pure.} = object + decimal_point*: cstring + thousands_sep*: cstring + grouping*: cstring + int_curr_symbol*: cstring + currency_symbol*: cstring + mon_decimal_point*: cstring + mon_thousands_sep*: cstring + mon_grouping*: cstring + positive_sign*: cstring + negative_sign*: cstring + int_frac_digits*: char + frac_digits*: char + p_cs_precedes*: char + p_sep_by_space*: char + n_cs_precedes*: char + n_sep_by_space*: char + p_sign_posn*: char + n_sign_posn*: char + int_n_cs_precedes*: char + int_n_sep_by_space*: char + int_n_sign_posn*: char + int_p_cs_precedes*: char + int_p_sep_by_space*: char + int_p_sign_posn*: char + + Passwd* {.importc: "struct passwd", header: "<pwd.h>", + final, pure.} = object ## struct passwd + pw_name*: cstring ## User's login name. + pw_passwd*: cstring + pw_uid*: Uid ## Numerical user ID. + pw_gid*: Gid ## Numerical group ID. + pw_comment*: cstring + pw_gecos*: cstring + pw_dir*: cstring ## Initial working directory. + pw_shell*: cstring ## Program to use as shell. + + Blkcnt* {.importc: "blkcnt_t", header: "<sys/types.h>".} = clong + ## used for file block counts + Blksize* {.importc: "blksize_t", header: "<sys/types.h>".} = clong + ## used for block sizes + Clock* {.importc: "clock_t", header: "<sys/types.h>".} = clong + ClockId* {.importc: "clockid_t", header: "<sys/types.h>".} = cint + Dev* {.importc: "dev_t", header: "<sys/types.h>".} = culong + Fsblkcnt* {.importc: "fsblkcnt_t", header: "<sys/types.h>".} = culong + Fsfilcnt* {.importc: "fsfilcnt_t", header: "<sys/types.h>".} = culong + Gid* {.importc: "gid_t", header: "<sys/types.h>".} = cuint + Id* {.importc: "id_t", header: "<sys/types.h>".} = cuint + Ino* {.importc: "ino_t", header: "<sys/types.h>".} = culong + Key* {.importc: "key_t", header: "<sys/types.h>".} = cint + Mode* {.importc: "mode_t", header: "<sys/types.h>".} = cint # cuint really! + Nlink* {.importc: "nlink_t", header: "<sys/types.h>".} = culong + Off* {.importc: "off_t", header: "<sys/types.h>".} = clong + Pid* {.importc: "pid_t", header: "<sys/types.h>".} = cint + Pthread_attr* {.importc: "pthread_attr_t", header: "<sys/types.h>", + pure, final.} = object + abi: array[56 div sizeof(clong), clong] + + Pthread_barrier* {.importc: "pthread_barrier_t", + header: "<sys/types.h>", pure, final.} = object + abi: array[32 div sizeof(clong), clong] + Pthread_barrierattr* {.importc: "pthread_barrierattr_t", + header: "<sys/types.h>", pure, final.} = object + abi: array[4 div sizeof(cint), cint] + + Pthread_cond* {.importc: "pthread_cond_t", header: "<sys/types.h>", + pure, final.} = object + abi: array[48 div sizeof(clonglong), clonglong] + Pthread_condattr* {.importc: "pthread_condattr_t", + header: "<sys/types.h>", pure, final.} = object + abi: array[4 div sizeof(cint), cint] + Pthread_key* {.importc: "pthread_key_t", header: "<sys/types.h>".} = cuint + Pthread_mutex* {.importc: "pthread_mutex_t", header: "<sys/types.h>", + pure, final.} = object + abi: array[48 div sizeof(clong), clong] + Pthread_mutexattr* {.importc: "pthread_mutexattr_t", + header: "<sys/types.h>", pure, final.} = object + abi: array[4 div sizeof(cint), cint] + Pthread_once* {.importc: "pthread_once_t", header: "<sys/types.h>".} = cint + Pthread_rwlock* {.importc: "pthread_rwlock_t", + header: "<sys/types.h>", pure, final.} = object + abi: array[56 div sizeof(clong), clong] + Pthread_rwlockattr* {.importc: "pthread_rwlockattr_t", + header: "<sys/types.h>".} = object + abi: array[8 div sizeof(clong), clong] + Pthread_spinlock* {.importc: "pthread_spinlock_t", + header: "<sys/types.h>".} = cint + Pthread* {.importc: "pthread_t", header: "<sys/types.h>".} = culong + Suseconds* {.importc: "suseconds_t", header: "<sys/types.h>".} = clong + #Ttime* {.importc: "time_t", header: "<sys/types.h>".} = int + Timer* {.importc: "timer_t", header: "<sys/types.h>".} = pointer + Uid* {.importc: "uid_t", header: "<sys/types.h>".} = cuint + Useconds* {.importc: "useconds_t", header: "<sys/types.h>".} = cuint + + Utsname* {.importc: "struct utsname", + header: "<sys/utsname.h>", + final, pure.} = object ## struct utsname + sysname*, ## Name of this implementation of the operating system. + nodename*, ## Name of this node within the communications + ## network to which this node is attached, if any. + release*, ## Current release level of this implementation. + version*, ## Current version level of this release. + machine*, ## Name of the hardware type on which the + ## system is running. + domainname*: array[65, char] + + Sem* {.importc: "sem_t", header: "<semaphore.h>", final, pure.} = object + abi: array[32 div sizeof(clong), clong] + + Stat* {.importc: "struct stat", + header: "<sys/stat.h>", final, pure.} = object ## struct stat + st_dev*: Dev ## Device ID of device containing file. + st_ino*: Ino ## File serial number. + st_mode*: Mode ## Mode of file (see below). + st_nlink*: Nlink ## Number of hard links to the file. + st_uid*: Uid ## User ID of file. + st_gid*: Gid ## Group ID of file. + st_rdev*: Dev ## Device ID (if file is character or block special). + st_size*: Off ## For regular files, the file size in bytes. + ## For symbolic links, the length in bytes of the + ## pathname contained in the symbolic link. + ## For a shared memory object, the length in bytes. + ## For a typed memory object, the length in bytes. + ## For other file types, the use of this field is + ## unspecified. + when StatHasNanoseconds: + st_atim*: Timespec ## Time of last access. + pad1: clong + st_mtim*: Timespec ## Time of last data modification. + pad2: clong + st_ctim*: Timespec ## Time of last status change. + pad3: clong + else: + st_atime*: Time ## Time of last access. + pad1: clong + st_mtime*: Time ## Time of last data modification. + pad2: clong + st_ctime*: Time ## Time of last status change. + pad3: clong + st_blksize*: Blksize ## A file system-specific preferred I/O block size + ## for this object. In some file system types, this + ## may vary from file to file. + st_blocks*: Blkcnt ## Number of blocks allocated for this object. + reserved: array[2, clong] + + + + Statvfs* {.importc: "struct statvfs", header: "<sys/statvfs.h>", + final, pure.} = object ## struct statvfs + f_bsize*: culong ## File system block size. + f_frsize*: culong ## Fundamental file system block size. + f_blocks*: Fsblkcnt ## Total number of blocks on file system + ## in units of f_frsize. + f_bfree*: Fsblkcnt ## Total number of free blocks. + f_bavail*: Fsblkcnt ## Number of free blocks available to + ## non-privileged process. + f_files*: Fsfilcnt ## Total number of file serial numbers. + f_ffree*: Fsfilcnt ## Total number of free file serial numbers. + f_favail*: Fsfilcnt ## Number of file serial numbers available to + ## non-privileged process. + f_fsid*: culong ## File system ID. + f_flag*: culong ## Bit mask of f_flag values. + f_namemax*: culong ## Maximum filename length. + + # No Posix_typed_mem_info + + Tm* {.importc: "struct tm", header: "<time.h>", + final, pure.} = object ## struct tm + tm_sec*: cint ## Seconds [0,60]. + tm_min*: cint ## Minutes [0,59]. + tm_hour*: cint ## Hour [0,23]. + tm_mday*: cint ## Day of month [1,31]. + tm_mon*: cint ## Month of year [0,11]. + tm_year*: cint ## Years since 1900. + tm_wday*: cint ## Day of week [0,6] (Sunday =0). + tm_yday*: cint ## Day of year [0,365]. + tm_isdst*: cint ## Daylight Savings flag. + + Itimerspec* {.importc: "struct itimerspec", header: "<time.h>", + final, pure.} = object ## struct itimerspec + it_interval*: Timespec ## Timer period. + it_value*: Timespec ## Timer expiration. + + Sig_atomic* {.importc: "sig_atomic_t", header: "<signal.h>".} = cint + ## Possibly volatile-qualified integer type of an object that can be + ## accessed as an atomic entity, even in the presence of asynchronous + ## interrupts. + Sigset* {.importc: "sigset_t", header: "<signal.h>", final.} = culong + + SigEvent* {.importc: "struct sigevent", + header: "<signal.h>", final, pure.} = object ## struct sigevent + sigev_notify*: cint ## Notification type. + sigev_signo*: cint ## Signal number. + sigev_value*: SigVal ## Signal value. + + SigVal* {.importc: "union sigval", + header: "<signal.h>", final, pure.} = object ## struct sigval + sival_int*: cint ## integer signal value + sival_ptr*: pointer ## pointer signal value; + + Sigaction* {.importc: "struct sigaction", + header: "<signal.h>", final, pure.} = object ## struct sigaction + sa_handler*: proc (x: cint) {.noconv.} ## Pointer to a signal-catching + ## function or one of the macros + ## SIG_IGN or SIG_DFL. + sa_mask*: Sigset ## Set of signals to be blocked during execution of + ## the signal handling function. + sa_flags*: cint ## Special flags. + + Stack* {.importc: "stack_t", + header: "<signal.h>", final, pure.} = object ## stack_t + ss_sp*: pointer ## Stack base or pointer. + ss_flags*: cint ## Flags. + ss_size*: csize ## Stack size. + + SigInfo* {.importc: "siginfo_t", + header: "<signal.h>", final, pure.} = object ## siginfo_t + si_signo*: cint ## Signal number. + si_code*: cint ## Signal code. + si_value*: SigVal ## Signal value. + + Nl_item* {.importc: "nl_item", header: "<langinfo.h>".} = cint + + Sched_param* {.importc: "struct sched_param", + header: "<sched.h>", + final, pure.} = object ## struct sched_param + sched_priority*: cint + + Timeval* {.importc: "struct timeval", header: "<sys/select.h>", + final, pure.} = object ## struct timeval + tv_sec*: Time ## Seconds. + tv_usec*: Suseconds ## Microseconds. + TFdSet* {.importc: "fd_set", header: "<sys/select.h>", + final, pure.} = object + abi: array[((64+(sizeof(clong) * 8)-1) div (sizeof(clong) * 8)), clong] + +proc si_pid*(info: SigInfo): Pid = + ## This might not be correct behavior. si_pid doesn't exist in Switch's + ## devkitpro headers + raise newException(OSError, "Nintendo switch cannot get si_pid!") + +type + Taiocb* {.importc: "struct aiocb", header: "<aio.h>", + final, pure.} = object ## struct aiocb + aio_fildes*: cint ## File descriptor. + aio_lio_opcode*: cint ## Operation to be performed. + aio_reqprio*: cint ## Request priority offset. + aio_buf*: pointer ## Location of buffer. + aio_nbytes*: csize ## Length of transfer. + aio_sigevent*: SigEvent ## Signal number and value. + next_prio: pointer + abs_prio: cint + policy: cint + error_Code: cint + return_value: clong + aio_offset*: Off ## File offset. + reserved: array[32, uint8] + +type + Tposix_spawnattr* {.importc: "posix_spawnattr_t", + header: "<spawn.h>", final, pure.} = object + Tposix_spawn_file_actions* {.importc: "posix_spawn_file_actions_t", + header: "<spawn.h>", final, pure.} = object + +# from sys/un.h +const Sockaddr_un_path_length* = 108 + +type + Socklen* {.importc: "socklen_t", header: "<sys/socket.h>".} = cuint + # cushort really + TSa_Family* {.importc: "sa_family_t", header: "<sys/socket.h>".} = cshort + + SockAddr* {.importc: "struct sockaddr", header: "<sys/socket.h>", + pure, final.} = object ## struct sockaddr + sa_len: cuchar + sa_family*: TSa_Family ## Address family. + sa_data*: array[14, char] ## Socket address (variable-length data). + + Sockaddr_storage* {.importc: "struct sockaddr_storage", + header: "<sys/socket.h>", + pure, final.} = object ## struct sockaddr_storage + ss_len: cuchar + ss_family*: TSa_Family ## Address family. + ss_padding1: array[64 - sizeof(cuchar) - sizeof(cshort), char] + ss_align: clonglong + ss_padding2: array[ + 128 - sizeof(cuchar) - sizeof(cshort) - + (64 - sizeof(cuchar) - sizeof(cshort)) - 64, char] + + Tif_nameindex* {.importc: "struct if_nameindex", final, + pure, header: "<net/if.h>".} = object ## struct if_nameindex + if_index*: cuint ## Numeric index of the interface. + if_name*: cstring ## Null-terminated name of the interface. + + + IOVec* {.importc: "struct iovec", pure, final, + header: "<sys/socket.h>".} = object ## struct iovec + iov_base*: pointer ## Base address of a memory region for input or output. + iov_len*: csize ## The size of the memory pointed to by iov_base. + + Tmsghdr* {.importc: "struct msghdr", pure, final, + header: "<sys/socket.h>".} = object ## struct msghdr + msg_name*: pointer ## Optional address. + msg_namelen*: Socklen ## Size of address. + msg_iov*: ptr IOVec ## Scatter/gather array. + msg_iovlen*: csize ## Members in msg_iov. + msg_control*: pointer ## Ancillary data; see below. + msg_controllen*: csize ## Ancillary data buffer len. + msg_flags*: cint ## Flags on received message. + + + Tcmsghdr* {.importc: "struct cmsghdr", pure, final, + header: "<sys/socket.h>".} = object ## struct cmsghdr + cmsg_len*: csize ## Data byte count, including the cmsghdr. + cmsg_level*: cint ## Originating protocol. + cmsg_type*: cint ## Protocol-specific type. + + TLinger* {.importc: "struct linger", pure, final, + header: "<sys/socket.h>".} = object ## struct linger + l_onoff*: cint ## Indicates whether linger option is enabled. + l_linger*: cint ## Linger time, in seconds. + # data follows... + + InPort* = uint16 + InAddrScalar* = uint32 + + InAddrT* {.importc: "in_addr_t", pure, final, + header: "<netinet/in.h>".} = uint32 + + InAddr* {.importc: "struct in_addr", pure, final, + header: "<netinet/in.h>".} = object ## struct in_addr + s_addr*: InAddrScalar + + Sockaddr_in* {.importc: "struct sockaddr_in", pure, final, + header: "<netinet/in.h>".} = object ## struct sockaddr_in + sin_len*: cushort + sin_family*: TSa_Family ## AF_INET. + sin_port*: InPort ## Port number. + sin_addr*: InAddr ## IP address. + sin_zero: array[8, uint8] + + In6Addr* {.importc: "struct in6_addr", pure, final, + header: "<netinet/in.h>".} = object ## struct in6_addr + s6_addr*: array[0..15, char] + + Sockaddr_in6* {.importc: "struct sockaddr_in6", pure, final, + header: "<netinet/in.h>".} = object ## struct sockaddr_in6 + sin6_family*: TSa_Family ## AF_INET6. + sin6_port*: InPort ## Port number. + sin6_flowinfo*: uint32 ## IPv6 traffic class and flow information. + sin6_addr*: In6Addr ## IPv6 address. + sin6_scope_id*: uint32 ## Set of interfaces for a scope. + + Hostent* {.importc: "struct hostent", pure, final, + header: "<netdb.h>".} = object ## struct hostent + h_name*: cstring ## Official name of the host. + h_aliases*: cstringArray ## A pointer to an array of pointers to + ## alternative host names, terminated by a + ## null pointer. + h_addrtype*: cint ## Address type. + h_length*: cint ## The length, in bytes, of the address. + h_addr_list*: cstringArray ## A pointer to an array of pointers to network + ## addresses (in network byte order) for the + ## host, terminated by a null pointer. + + Tnetent* {.importc: "struct netent", pure, final, + header: "<netdb.h>".} = object ## struct netent + n_name*: cstring ## Official, fully-qualified (including the + ## domain) name of the host. + n_aliases*: cstringArray ## A pointer to an array of pointers to + ## alternative network names, terminated by a + ## null pointer. + n_addrtype*: cint ## The address type of the network. + n_net*: uint32 ## The network number, in host byte order. + + Protoent* {.importc: "struct protoent", pure, final, + header: "<netdb.h>".} = object ## struct protoent + p_name*: cstring ## Official name of the protocol. + p_aliases*: cstringArray ## A pointer to an array of pointers to + ## alternative protocol names, terminated by + ## a null pointer. + p_proto*: cint ## The protocol number. + + Servent* {.importc: "struct servent", pure, final, + header: "<netdb.h>".} = object ## struct servent + s_name*: cstring ## Official name of the service. + s_aliases*: cstringArray ## A pointer to an array of pointers to + ## alternative service names, terminated by + ## a null pointer. + s_port*: cint ## The port number at which the service + ## resides, in network byte order. + s_proto*: cstring ## The name of the protocol to use when + ## contacting the service. + + AddrInfo* {.importc: "struct addrinfo", pure, final, + header: "<netdb.h>".} = object ## struct addrinfo + ai_flags*: cint ## Input flags. + ai_family*: cint ## Address family of socket. + ai_socktype*: cint ## Socket type. + ai_protocol*: cint ## Protocol of socket. + ai_addrlen*: Socklen ## Length of socket address. + ai_canonname*: cstring ## Canonical name of service location. + ai_addr*: ptr SockAddr ## Socket address of socket. + ai_next*: ptr AddrInfo ## Pointer to next in list. + + TPollfd* {.importc: "struct pollfd", pure, final, + header: "<poll.h>".} = object ## struct pollfd + fd*: cint ## The following descriptor being polled. + events*: cshort ## The input event flags (see below). + revents*: cshort ## The output event flags (see below). + + Tnfds* {.importc: "nfds_t", header: "<poll.h>".} = culong + +var + errno* {.importc, header: "<errno.h>".}: cint ## error variable + h_errno* {.importc, header: "<netdb.h>".}: cint + daylight* {.importc: "_daylight", header: "<time.h>".}: cint + timezone* {.importc: "_timezone", header: "<time.h>".}: clong + +# Regenerate using detect.nim! +include posix_nintendoswitch_consts + +const POSIX_SPAWN_USEVFORK* = cint(0x40) # needs _GNU_SOURCE! + +# <sys/wait.h> +proc WEXITSTATUS*(s: cint): cint = (s shr 8) and 0xff +proc WIFEXITED*(s:cint) : bool = (s and 0xff) == 0 +proc WTERMSIG*(s:cint): cint = s and 0x7f +proc WSTOPSIG*(s:cint): cint = WEXITSTATUS(s) +proc WIFSIGNALED*(s:cint) : bool = ((s and 0x7f) > 0) and ((s and 0x7f) < 0x7f) +proc WIFSTOPPED*(s:cint) : bool = (s and 0xff) == 0x7f diff --git a/lib/posix/posix_nintendoswitch_consts.nim b/lib/posix/posix_nintendoswitch_consts.nim new file mode 100644 index 000000000..33470d22b --- /dev/null +++ b/lib/posix/posix_nintendoswitch_consts.nim @@ -0,0 +1,586 @@ +# Generated by detect.nim + + +# <aio.h> + +# <dlfcn.h> + +# <errno.h> +const E2BIG* = cint(7) +const EACCES* = cint(13) +const EADDRINUSE* = cint(112) +const EADDRNOTAVAIL* = cint(125) +const EAFNOSUPPORT* = cint(106) +const EAGAIN* = cint(11) +const EALREADY* = cint(120) +const EBADF* = cint(9) +const EBADMSG* = cint(77) +const EBUSY* = cint(16) +const ECANCELED* = cint(140) +const ECHILD* = cint(10) +const ECONNABORTED* = cint(113) +const ECONNREFUSED* = cint(111) +const ECONNRESET* = cint(104) +const EDEADLK* = cint(45) +const EDESTADDRREQ* = cint(121) +const EDOM* = cint(33) +const EDQUOT* = cint(132) +const EEXIST* = cint(17) +const EFAULT* = cint(14) +const EFBIG* = cint(27) +const EHOSTUNREACH* = cint(118) +const EIDRM* = cint(36) +const EILSEQ* = cint(138) +const EINPROGRESS* = cint(119) +const EINTR* = cint(4) +const EINVAL* = cint(22) +const EIO* = cint(5) +const EISCONN* = cint(127) +const EISDIR* = cint(21) +const ELOOP* = cint(92) +const EMFILE* = cint(24) +const EMLINK* = cint(31) +const EMSGSIZE* = cint(122) +const EMULTIHOP* = cint(74) +const ENAMETOOLONG* = cint(91) +const ENETDOWN* = cint(115) +const ENETRESET* = cint(126) +const ENETUNREACH* = cint(114) +const ENFILE* = cint(23) +const ENOBUFS* = cint(105) +const ENODATA* = cint(61) +const ENODEV* = cint(19) +const ENOENT* = cint(2) +const ENOEXEC* = cint(8) +const ENOLCK* = cint(46) +const ENOLINK* = cint(67) +const ENOMEM* = cint(12) +const ENOMSG* = cint(35) +const ENOPROTOOPT* = cint(109) +const ENOSPC* = cint(28) +const ENOSR* = cint(63) +const ENOSTR* = cint(60) +const ENOSYS* = cint(88) +const ENOTCONN* = cint(128) +const ENOTDIR* = cint(20) +const ENOTEMPTY* = cint(90) +const ENOTSOCK* = cint(108) +const ENOTSUP* = cint(134) +const ENOTTY* = cint(25) +const ENXIO* = cint(6) +const EOPNOTSUPP* = cint(95) +const EOVERFLOW* = cint(139) +const EPERM* = cint(1) +const EPIPE* = cint(32) +const EPROTO* = cint(71) +const EPROTONOSUPPORT* = cint(123) +const EPROTOTYPE* = cint(107) +const ERANGE* = cint(34) +const EROFS* = cint(30) +const ESPIPE* = cint(29) +const ESRCH* = cint(3) +const ESTALE* = cint(133) +const ETIME* = cint(62) +const ETIMEDOUT* = cint(116) +const ETXTBSY* = cint(26) +const EWOULDBLOCK* = cint(11) +const EXDEV* = cint(18) + +# <fcntl.h> +const F_DUPFD* = cint(0) +const F_GETFD* = cint(1) +const F_SETFD* = cint(2) +const F_GETFL* = cint(3) +const F_SETFL* = cint(4) +const F_GETLK* = cint(7) +const F_SETLK* = cint(8) +const F_SETLKW* = cint(9) +const F_GETOWN* = cint(5) +const F_SETOWN* = cint(6) +const FD_CLOEXEC* = cint(1) +const F_RDLCK* = cint(1) +const F_UNLCK* = cint(3) +const F_WRLCK* = cint(2) +const O_CREAT* = cint(512) +const O_EXCL* = cint(2048) +const O_NOCTTY* = cint(32768) +const O_TRUNC* = cint(1024) +const O_APPEND* = cint(8) +const O_NONBLOCK* = cint(16384) +const O_SYNC* = cint(8192) +const O_ACCMODE* = cint(3) +const O_RDONLY* = cint(0) +const O_RDWR* = cint(2) +const O_WRONLY* = cint(1) + +# <fenv.h> + +# <fmtmsg.h> + +# <fnmatch.h> +const FNM_NOMATCH* = cint(1) +const FNM_PATHNAME* = cint(2) +const FNM_PERIOD* = cint(4) +const FNM_NOESCAPE* = cint(1) + +# <ftw.h> + +# <glob.h> +const GLOB_APPEND* = cint(1) +const GLOB_DOOFFS* = cint(2) +const GLOB_ERR* = cint(4) +const GLOB_MARK* = cint(8) +const GLOB_NOCHECK* = cint(16) +const GLOB_NOSORT* = cint(32) +const GLOB_NOSPACE* = cint(-1) + +# <langinfo.h> +const CODESET* = cint(0) +const D_T_FMT* = cint(1) +const D_FMT* = cint(2) +const T_FMT* = cint(3) +const T_FMT_AMPM* = cint(4) +const AM_STR* = cint(5) +const PM_STR* = cint(6) +const DAY_1* = cint(7) +const DAY_2* = cint(8) +const DAY_3* = cint(9) +const DAY_4* = cint(10) +const DAY_5* = cint(11) +const DAY_6* = cint(12) +const DAY_7* = cint(13) +const ABDAY_1* = cint(14) +const ABDAY_2* = cint(15) +const ABDAY_3* = cint(16) +const ABDAY_4* = cint(17) +const ABDAY_5* = cint(18) +const ABDAY_6* = cint(19) +const ABDAY_7* = cint(20) +const MON_1* = cint(21) +const MON_2* = cint(22) +const MON_3* = cint(23) +const MON_4* = cint(24) +const MON_5* = cint(25) +const MON_6* = cint(26) +const MON_7* = cint(27) +const MON_8* = cint(28) +const MON_9* = cint(29) +const MON_10* = cint(30) +const MON_11* = cint(31) +const MON_12* = cint(32) +const ABMON_1* = cint(33) +const ABMON_2* = cint(34) +const ABMON_3* = cint(35) +const ABMON_4* = cint(36) +const ABMON_5* = cint(37) +const ABMON_6* = cint(38) +const ABMON_7* = cint(39) +const ABMON_8* = cint(40) +const ABMON_9* = cint(41) +const ABMON_10* = cint(42) +const ABMON_11* = cint(43) +const ABMON_12* = cint(44) +const ERA* = cint(45) +const ERA_D_FMT* = cint(46) +const ERA_D_T_FMT* = cint(47) +const ERA_T_FMT* = cint(48) +const ALT_DIGITS* = cint(49) +const RADIXCHAR* = cint(50) +const THOUSEP* = cint(51) +const YESEXPR* = cint(52) +const NOEXPR* = cint(53) +const CRNCYSTR* = cint(56) + +# <locale.h> +const LC_ALL* = cint(0) +const LC_COLLATE* = cint(1) +const LC_CTYPE* = cint(2) +const LC_MESSAGES* = cint(6) +const LC_MONETARY* = cint(3) +const LC_NUMERIC* = cint(4) +const LC_TIME* = cint(5) + +# <netdb.h> +const IPPORT_RESERVED* = cint(1024) +const HOST_NOT_FOUND* = cint(1) +const NO_DATA* = cint(4) +const NO_RECOVERY* = cint(3) +const TRY_AGAIN* = cint(2) +const AI_PASSIVE* = cint(1) +const AI_CANONNAME* = cint(2) +const AI_NUMERICHOST* = cint(4) +const AI_NUMERICSERV* = cint(8) +const AI_V4MAPPED* = cint(2048) +const AI_ALL* = cint(256) +const AI_ADDRCONFIG* = cint(1024) +const NI_NOFQDN* = cint(1) +const NI_NUMERICHOST* = cint(2) +const NI_NAMEREQD* = cint(4) +const NI_NUMERICSERV* = cint(8) +const NI_NUMERICSCOPE* = cint(32) +const NI_DGRAM* = cint(16) +const EAI_AGAIN* = cint(2) +const EAI_BADFLAGS* = cint(3) +const EAI_FAIL* = cint(4) +const EAI_FAMILY* = cint(5) +const EAI_MEMORY* = cint(6) +const EAI_NONAME* = cint(8) +const EAI_SERVICE* = cint(9) +const EAI_SOCKTYPE* = cint(10) +const EAI_SYSTEM* = cint(11) +const EAI_OVERFLOW* = cint(14) + +# <net/if.h> +const IF_NAMESIZE* = cint(16) + +# <netinet/in.h> +const IPPROTO_IP* = cint(0) +const IPPROTO_IPV6* = cint(41) +const IPPROTO_ICMP* = cint(1) +const IPPROTO_RAW* = cint(255) +const IPPROTO_TCP* = cint(6) +const IPPROTO_UDP* = cint(17) +const INADDR_ANY* = InAddrScalar(0) +const INADDR_LOOPBACK* = InAddrScalar(2130706433) +const INADDR_BROADCAST* = InAddrScalar(-1) +const INET_ADDRSTRLEN* = cint(16) +const INET6_ADDRSTRLEN* = cint(46) +const IPV6_JOIN_GROUP* = cint(12) +const IPV6_LEAVE_GROUP* = cint(13) +const IPV6_MULTICAST_HOPS* = cint(10) +const IPV6_MULTICAST_IF* = cint(9) +const IPV6_MULTICAST_LOOP* = cint(11) +const IPV6_UNICAST_HOPS* = cint(4) +const IPV6_V6ONLY* = cint(27) + +# <netinet/tcp.h> +const TCP_NODELAY* = cint(1) + +# <nl_types.h> + +# <poll.h> +const POLLIN* = cshort(1) +const POLLRDNORM* = cshort(64) +const POLLRDBAND* = cshort(128) +const POLLPRI* = cshort(2) +const POLLOUT* = cshort(4) +const POLLWRNORM* = cshort(4) +const POLLWRBAND* = cshort(256) +const POLLERR* = cshort(8) +const POLLHUP* = cshort(16) +const POLLNVAL* = cshort(32) + +# <pthread.h> +const PTHREAD_CREATE_DETACHED* = cint(0) +const PTHREAD_CREATE_JOINABLE* = cint(1) +const PTHREAD_EXPLICIT_SCHED* = cint(2) +const PTHREAD_INHERIT_SCHED* = cint(1) +const PTHREAD_SCOPE_PROCESS* = cint(0) +const PTHREAD_SCOPE_SYSTEM* = cint(1) + +# <sched.h> +const SCHED_FIFO* = cint(1) +const SCHED_RR* = cint(2) +const SCHED_OTHER* = cint(0) + +# <semaphore.h> + +# <signal.h> +const SIGEV_NONE* = cint(1) +const SIGEV_SIGNAL* = cint(2) +const SIGEV_THREAD* = cint(3) +const SIGABRT* = cint(6) +const SIGALRM* = cint(14) +const SIGBUS* = cint(10) +const SIGCHLD* = cint(20) +const SIGCONT* = cint(19) +const SIGFPE* = cint(8) +const SIGHUP* = cint(1) +const SIGILL* = cint(4) +const SIGINT* = cint(2) +const SIGKILL* = cint(9) +const SIGPIPE* = cint(13) +const SIGQUIT* = cint(3) +const SIGSEGV* = cint(11) +const SIGSTOP* = cint(17) +const SIGTERM* = cint(15) +const SIGTSTP* = cint(18) +const SIGTTIN* = cint(21) +const SIGTTOU* = cint(22) +const SIGUSR1* = cint(30) +const SIGUSR2* = cint(31) +const SIGPOLL* = cint(23) +const SIGPROF* = cint(27) +const SIGSYS* = cint(12) +const SIGTRAP* = cint(5) +const SIGURG* = cint(16) +const SIGVTALRM* = cint(26) +const SIGXCPU* = cint(24) +const SIGXFSZ* = cint(25) +const SA_NOCLDSTOP* = cint(1) +const SIG_BLOCK* = cint(1) +const SIG_UNBLOCK* = cint(2) +const SIG_SETMASK* = cint(0) +const SS_ONSTACK* = cint(1) +const SS_DISABLE* = cint(2) +const MINSIGSTKSZ* = cint(2048) +const SIGSTKSZ* = cint(8192) +const SIG_DFL* = cast[Sighandler](0) +const SIG_ERR* = cast[Sighandler](-1) +const SIG_IGN* = cast[Sighandler](1) + +# <sys/ipc.h> + +# <sys/mman.h> + +# <sys/resource.h> + +# <sys/select.h> +const FD_SETSIZE* = cint(64) + +# <sys/socket.h> +const MSG_CTRUNC* = cint(32) +const MSG_DONTROUTE* = cint(4) +const MSG_EOR* = cint(8) +const MSG_OOB* = cint(1) +const SCM_RIGHTS* = cint(1) +const SO_ACCEPTCONN* = cint(2) +const SO_BROADCAST* = cint(32) +const SO_DEBUG* = cint(1) +const SO_DONTROUTE* = cint(16) +const SO_ERROR* = cint(4103) +const SO_KEEPALIVE* = cint(8) +const SO_LINGER* = cint(128) +const SO_OOBINLINE* = cint(256) +const SO_RCVBUF* = cint(4098) +const SO_RCVLOWAT* = cint(4100) +const SO_RCVTIMEO* = cint(4102) +const SO_REUSEADDR* = cint(4) +const SO_SNDBUF* = cint(4097) +const SO_SNDLOWAT* = cint(4099) +const SO_SNDTIMEO* = cint(4101) +const SO_TYPE* = cint(4104) +const SOCK_DGRAM* = cint(2) +const SOCK_RAW* = cint(3) +const SOCK_SEQPACKET* = cint(5) +const SOCK_STREAM* = cint(1) +const SOL_SOCKET* = cint(65535) +const SOMAXCONN* = cint(128) +const SO_REUSEPORT* = cint(512) +const MSG_NOSIGNAL* = cint(131072) +const MSG_PEEK* = cint(2) +const MSG_TRUNC* = cint(16) +const MSG_WAITALL* = cint(64) +const AF_INET* = TSa_Family(2) +const AF_INET6* = TSa_Family(28) +const AF_UNIX* = TSa_Family(1) +const AF_UNSPEC* = TSa_Family(0) +const SHUT_RD* = cint(0) +const SHUT_RDWR* = cint(2) +const SHUT_WR* = cint(1) + +# <sys/stat.h> +const S_IFBLK* = cint(24576) +const S_IFCHR* = cint(8192) +const S_IFDIR* = cint(16384) +const S_IFIFO* = cint(4096) +const S_IFLNK* = cint(40960) +const S_IFMT* = cint(61440) +const S_IFREG* = cint(32768) +const S_IFSOCK* = cint(49152) +const S_IRGRP* = cint(32) +const S_IROTH* = cint(4) +const S_IRUSR* = cint(256) +const S_IRWXG* = cint(56) +const S_IRWXO* = cint(7) +const S_IRWXU* = cint(448) +const S_ISGID* = cint(1024) +const S_ISUID* = cint(2048) +const S_ISVTX* = cint(512) +const S_IWGRP* = cint(16) +const S_IWOTH* = cint(2) +const S_IWUSR* = cint(128) +const S_IXGRP* = cint(8) +const S_IXOTH* = cint(1) +const S_IXUSR* = cint(64) + +# <sys/statvfs.h> +const ST_RDONLY* = cint(1) +const ST_NOSUID* = cint(2) + +# <sys/wait.h> +const WNOHANG* = cint(1) +const WUNTRACED* = cint(2) + +# <spawn.h> +const POSIX_SPAWN_RESETIDS* = cint(1) +const POSIX_SPAWN_SETPGROUP* = cint(2) +const POSIX_SPAWN_SETSCHEDPARAM* = cint(4) +const POSIX_SPAWN_SETSCHEDULER* = cint(8) +const POSIX_SPAWN_SETSIGDEF* = cint(16) +const POSIX_SPAWN_SETSIGMASK* = cint(32) + +# <stdio.h> +const IOFBF* = cint(0) +const IONBF* = cint(2) + +# <time.h> +const CLOCKS_PER_SEC* = clong(100) +const CLOCK_REALTIME* = cint(1) +const TIMER_ABSTIME* = cint(4) +const CLOCK_MONOTONIC* = cint(4) + +# <unistd.h> +const F_OK* = cint(0) +const R_OK* = cint(4) +const W_OK* = cint(2) +const X_OK* = cint(1) +const F_LOCK* = cint(1) +const F_TEST* = cint(3) +const F_TLOCK* = cint(2) +const F_ULOCK* = cint(0) +const PC_2_SYMLINKS* = cint(13) +const PC_ALLOC_SIZE_MIN* = cint(15) +const PC_ASYNC_IO* = cint(9) +const PC_CHOWN_RESTRICTED* = cint(6) +const PC_FILESIZEBITS* = cint(12) +const PC_LINK_MAX* = cint(0) +const PC_MAX_CANON* = cint(1) +const PC_MAX_INPUT* = cint(2) +const PC_NAME_MAX* = cint(3) +const PC_NO_TRUNC* = cint(7) +const PC_PATH_MAX* = cint(4) +const PC_PIPE_BUF* = cint(5) +const PC_PRIO_IO* = cint(10) +const PC_REC_INCR_XFER_SIZE* = cint(16) +const PC_REC_MIN_XFER_SIZE* = cint(18) +const PC_REC_XFER_ALIGN* = cint(19) +const PC_SYMLINK_MAX* = cint(14) +const PC_SYNC_IO* = cint(11) +const PC_VDISABLE* = cint(8) +const SC_2_C_BIND* = cint(108) +const SC_2_C_DEV* = cint(109) +const SC_2_CHAR_TERM* = cint(107) +const SC_2_FORT_DEV* = cint(110) +const SC_2_FORT_RUN* = cint(111) +const SC_2_LOCALEDEF* = cint(112) +const SC_2_PBS* = cint(113) +const SC_2_PBS_ACCOUNTING* = cint(114) +const SC_2_PBS_CHECKPOINT* = cint(115) +const SC_2_PBS_LOCATE* = cint(116) +const SC_2_PBS_MESSAGE* = cint(117) +const SC_2_PBS_TRACK* = cint(118) +const SC_2_SW_DEV* = cint(119) +const SC_2_UPE* = cint(120) +const SC_2_VERSION* = cint(121) +const SC_ADVISORY_INFO* = cint(54) +const SC_AIO_LISTIO_MAX* = cint(34) +const SC_AIO_MAX* = cint(35) +const SC_AIO_PRIO_DELTA_MAX* = cint(36) +const SC_ARG_MAX* = cint(0) +const SC_ASYNCHRONOUS_IO* = cint(21) +const SC_ATEXIT_MAX* = cint(55) +const SC_BARRIERS* = cint(56) +const SC_BC_BASE_MAX* = cint(57) +const SC_BC_DIM_MAX* = cint(58) +const SC_BC_SCALE_MAX* = cint(59) +const SC_BC_STRING_MAX* = cint(60) +const SC_CHILD_MAX* = cint(1) +const SC_CLK_TCK* = cint(2) +const SC_CLOCK_SELECTION* = cint(61) +const SC_COLL_WEIGHTS_MAX* = cint(62) +const SC_CPUTIME* = cint(63) +const SC_DELAYTIMER_MAX* = cint(37) +const SC_EXPR_NEST_MAX* = cint(64) +const SC_FSYNC* = cint(22) +const SC_GETGR_R_SIZE_MAX* = cint(50) +const SC_GETPW_R_SIZE_MAX* = cint(51) +const SC_HOST_NAME_MAX* = cint(65) +const SC_IOV_MAX* = cint(66) +const SC_IPV6* = cint(67) +const SC_JOB_CONTROL* = cint(5) +const SC_LINE_MAX* = cint(68) +const SC_LOGIN_NAME_MAX* = cint(52) +const SC_MAPPED_FILES* = cint(23) +const SC_MEMLOCK* = cint(24) +const SC_MEMLOCK_RANGE* = cint(25) +const SC_MEMORY_PROTECTION* = cint(26) +const SC_MESSAGE_PASSING* = cint(27) +const SC_MONOTONIC_CLOCK* = cint(69) +const SC_MQ_OPEN_MAX* = cint(13) +const SC_MQ_PRIO_MAX* = cint(14) +const SC_NGROUPS_MAX* = cint(3) +const SC_OPEN_MAX* = cint(4) +const SC_PAGE_SIZE* = cint(8) +const SC_PRIORITIZED_IO* = cint(28) +const SC_PRIORITY_SCHEDULING* = cint(101) +const SC_RAW_SOCKETS* = cint(70) +const SC_RE_DUP_MAX* = cint(73) +const SC_READER_WRITER_LOCKS* = cint(71) +const SC_REALTIME_SIGNALS* = cint(29) +const SC_REGEXP* = cint(72) +const SC_RTSIG_MAX* = cint(15) +const SC_SAVED_IDS* = cint(6) +const SC_SEM_NSEMS_MAX* = cint(16) +const SC_SEM_VALUE_MAX* = cint(17) +const SC_SEMAPHORES* = cint(30) +const SC_SHARED_MEMORY_OBJECTS* = cint(31) +const SC_SHELL* = cint(74) +const SC_SIGQUEUE_MAX* = cint(18) +const SC_SPAWN* = cint(75) +const SC_SPIN_LOCKS* = cint(76) +const SC_SPORADIC_SERVER* = cint(77) +const SC_SS_REPL_MAX* = cint(78) +const SC_STREAM_MAX* = cint(100) +const SC_SYMLOOP_MAX* = cint(79) +const SC_SYNCHRONIZED_IO* = cint(32) +const SC_THREAD_ATTR_STACKADDR* = cint(43) +const SC_THREAD_ATTR_STACKSIZE* = cint(44) +const SC_THREAD_CPUTIME* = cint(80) +const SC_THREAD_DESTRUCTOR_ITERATIONS* = cint(53) +const SC_THREAD_KEYS_MAX* = cint(38) +const SC_THREAD_PRIO_INHERIT* = cint(46) +const SC_THREAD_PRIO_PROTECT* = cint(47) +const SC_THREAD_PRIORITY_SCHEDULING* = cint(45) +const SC_THREAD_PROCESS_SHARED* = cint(48) +const SC_THREAD_SAFE_FUNCTIONS* = cint(49) +const SC_THREAD_SPORADIC_SERVER* = cint(81) +const SC_THREAD_STACK_MIN* = cint(39) +const SC_THREAD_THREADS_MAX* = cint(40) +const SC_THREADS* = cint(42) +const SC_TIMEOUTS* = cint(82) +const SC_TIMER_MAX* = cint(19) +const SC_TIMERS* = cint(33) +const SC_TRACE* = cint(83) +const SC_TRACE_EVENT_FILTER* = cint(84) +const SC_TRACE_EVENT_NAME_MAX* = cint(85) +const SC_TRACE_INHERIT* = cint(86) +const SC_TRACE_LOG* = cint(87) +const SC_TRACE_NAME_MAX* = cint(88) +const SC_TRACE_SYS_MAX* = cint(89) +const SC_TRACE_USER_EVENT_MAX* = cint(90) +const SC_TTY_NAME_MAX* = cint(41) +const SC_TYPED_MEMORY_OBJECTS* = cint(91) +const SC_TZNAME_MAX* = cint(20) +const SC_V6_ILP32_OFF32* = cint(92) +const SC_V6_ILP32_OFFBIG* = cint(93) +const SC_V6_LP64_OFF64* = cint(94) +const SC_V6_LPBIG_OFFBIG* = cint(95) +const SC_VERSION* = cint(7) +const SC_XBS5_ILP32_OFF32* = cint(92) +const SC_XBS5_ILP32_OFFBIG* = cint(93) +const SC_XBS5_LP64_OFF64* = cint(94) +const SC_XBS5_LPBIG_OFFBIG* = cint(95) +const SC_XOPEN_CRYPT* = cint(96) +const SC_XOPEN_ENH_I18N* = cint(97) +const SC_XOPEN_LEGACY* = cint(98) +const SC_XOPEN_REALTIME* = cint(99) +const SC_XOPEN_REALTIME_THREADS* = cint(102) +const SC_XOPEN_SHM* = cint(103) +const SC_XOPEN_STREAMS* = cint(104) +const SC_XOPEN_UNIX* = cint(105) +const SC_XOPEN_VERSION* = cint(106) +const SC_NPROCESSORS_ONLN* = cint(10) +const SEEK_SET* = cint(0) +const SEEK_CUR* = cint(1) +const SEEK_END* = cint(2) diff --git a/lib/posix/posix_other.nim b/lib/posix/posix_other.nim index 01bc1c1e5..99d67824e 100644 --- a/lib/posix/posix_other.nim +++ b/lib/posix/posix_other.nim @@ -7,10 +7,10 @@ # distribution, for details about the copyright. # -{.deadCodeElim:on.} +{.deadCodeElim: on.} # dce option deprecated const - hasSpawnH = not defined(haiku) # should exist for every Posix system nowadays + hasSpawnH = true # should exist for every Posix system nowadays hasAioH = defined(linux) when defined(linux) and not defined(android): @@ -43,6 +43,9 @@ type Dirent* {.importc: "struct dirent", header: "<dirent.h>", final, pure.} = object ## dirent_t struct + when defined(haiku): + d_dev*: Dev ## Device (not POSIX) + d_pdev*: Dev ## Parent device (only for queries) (not POSIX) d_ino*: Ino ## File serial number. when defined(dragonfly): # DragonflyBSD doesn't have `d_reclen` field. @@ -54,6 +57,9 @@ type ## (not POSIX) when defined(linux) or defined(openbsd): d_off*: Off ## Not an offset. Value that ``telldir()`` would return. + elif defined(haiku): + d_pino*: Ino ## Parent inode (only for queries) (not POSIX) + d_reclen*: cushort ## Length of this record. (not POSIX) d_name*: array[0..255, char] ## Name of entry. @@ -215,14 +221,14 @@ type ## For a typed memory object, the length in bytes. ## For other file types, the use of this field is ## unspecified. - when defined(macosx) or defined(android): - st_atime*: Time ## Time of last access. - st_mtime*: Time ## Time of last data modification. - st_ctime*: Time ## Time of last status change. - else: + when StatHasNanoseconds: st_atim*: Timespec ## Time of last access. st_mtim*: Timespec ## Time of last data modification. st_ctim*: Timespec ## Time of last status change. + else: + st_atime*: Time ## Time of last access. + st_mtime*: Time ## Time of last data modification. + st_ctime*: Time ## Time of last status change. st_blksize*: Blksize ## A file system-specific preferred I/O block size ## for this object. In some file system types, this ## may vary from file to file. @@ -335,8 +341,8 @@ type Timeval* {.importc: "struct timeval", header: "<sys/select.h>", final, pure.} = object ## struct timeval - tv_sec*: int ## Seconds. - tv_usec*: int ## Microseconds. + tv_sec*: Time ## Seconds. + tv_usec*: Suseconds ## Microseconds. TFdSet* {.importc: "fd_set", header: "<sys/select.h>", final, pure.} = object Mcontext* {.importc: "mcontext_t", header: "<ucontext.h>", @@ -599,6 +605,10 @@ else: MSG_NOSIGNAL* {.importc, header: "<sys/socket.h>".}: cint ## No SIGPIPE generated when an attempt to send is made on a stream-oriented socket that is no longer connected. +when defined(haiku): + const + SIGKILLTHR* = 21 ## BeOS specific: Kill just the thread, not team + when hasSpawnH: when defined(linux): # better be safe than sorry; Linux has this flag, macosx doesn't, don't diff --git a/lib/posix/posix_other_consts.nim b/lib/posix/posix_other_consts.nim index 003414a6a..1b27fc5f6 100644 --- a/lib/posix/posix_other_consts.nim +++ b/lib/posix/posix_other_consts.nim @@ -451,6 +451,9 @@ var POSIX_TYPED_MEM_ALLOCATE* {.importc: "POSIX_TYPED_MEM_ALLOCATE", header: "<s var POSIX_TYPED_MEM_ALLOCATE_CONTIG* {.importc: "POSIX_TYPED_MEM_ALLOCATE_CONTIG", header: "<sys/mman.h>".}: cint var POSIX_TYPED_MEM_MAP_ALLOCATABLE* {.importc: "POSIX_TYPED_MEM_MAP_ALLOCATABLE", header: "<sys/mman.h>".}: cint +# <sys/resource.h> +var RLIMIT_NOFILE* {.importc: "RLIMIT_NOFILE", header: "<sys/resource.h>".}: cint + # <sys/select.h> var FD_SETSIZE* {.importc: "FD_SETSIZE", header: "<sys/select.h>".}: cint diff --git a/lib/posix/termios.nim b/lib/posix/termios.nim index f86e408fb..c08de7342 100644 --- a/lib/posix/termios.nim +++ b/lib/posix/termios.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated import posix type @@ -122,6 +122,21 @@ var B9600* {.importc, header: "<termios.h>".}: Speed B19200* {.importc, header: "<termios.h>".}: Speed B38400* {.importc, header: "<termios.h>".}: Speed + B57600* {.importc, header: "<termios.h>".}: Speed + B115200* {.importc, header: "<termios.h>".}: Speed + B230400* {.importc, header: "<termios.h>".}: Speed + B460800* {.importc, header: "<termios.h>".}: Speed + B500000* {.importc, header: "<termios.h>".}: Speed + B576000* {.importc, header: "<termios.h>".}: Speed + B921600* {.importc, header: "<termios.h>".}: Speed + B1000000* {.importc, header: "<termios.h>".}: Speed + B1152000* {.importc, header: "<termios.h>".}: Speed + B1500000* {.importc, header: "<termios.h>".}: Speed + B2000000* {.importc, header: "<termios.h>".}: Speed + B2500000* {.importc, header: "<termios.h>".}: Speed + B3000000* {.importc, header: "<termios.h>".}: Speed + B3500000* {.importc, header: "<termios.h>".}: Speed + B4000000* {.importc, header: "<termios.h>".}: Speed EXTA* {.importc, header: "<termios.h>".}: Speed EXTB* {.importc, header: "<termios.h>".}: Speed CSIZE* {.importc, header: "<termios.h>".}: Cflag diff --git a/lib/pure/algorithm.nim b/lib/pure/algorithm.nim index fdf2d7cbb..81badfae6 100644 --- a/lib/pure/algorithm.nim +++ b/lib/pure/algorithm.nim @@ -13,9 +13,6 @@ type SortOrder* = enum ## sort order Descending, Ascending -{.deprecated: [TSortOrder: SortOrder].} - - proc `*`*(x: int, order: SortOrder): int {.inline.} = ## flips `x` if ``order == Descending``; ## if ``order == Ascending`` then `x` is returned. @@ -24,16 +21,20 @@ proc `*`*(x: int, order: SortOrder): int {.inline.} = var y = order.ord - 1 result = (x xor y) - y -proc fill*[T](a: var openArray[T], first, last: Natural, value: T) = - ## fills the array ``a[first..last]`` with `value`. +template fillImpl[T](a: var openArray[T], first, last: int, value: T) = var x = first while x <= last: a[x] = value inc(x) +proc fill*[T](a: var openArray[T], first, last: Natural, value: T) = + ## fills the array ``a[first..last]`` with `value`. + fillImpl(a, first, last, value) + proc fill*[T](a: var openArray[T], value: T) = ## fills the array `a` with `value`. - fill(a, 0, a.high, value) + fillImpl(a, 0, a.high, value) + proc reverse*[T](a: var openArray[T], first, last: Natural) = ## reverses the array ``a[first..last]``. @@ -63,36 +64,72 @@ proc reversed*[T](a: openArray[T]): seq[T] = ## returns the reverse of the array `a`. reversed(a, 0, a.high) +proc binarySearch*[T, K](a: openArray[T], key: K, + cmp: proc (x: T, y: K): int {.closure.}): int = + ## binary search for `key` in `a`. Returns -1 if not found. + ## + ## `cmp` is the comparator function to use, the expected return values are + ## the same as that of system.cmp. + if a.len == 0: + return -1 + + let len = a.len + + if len == 1: + if cmp(a[0], key) == 0: + return 0 + else: + return -1 + + if (len and (len - 1)) == 0: + # when `len` is a power of 2, a faster shr can be used. + var step = len shr 1 + var cmpRes: int + while step > 0: + let i = result or step + cmpRes = cmp(a[i], key) + if cmpRes == 0: + return i + + if cmpRes < 1: + result = i + step = step shr 1 + if cmp(a[result], key) != 0: result = -1 + else: + var b = len + var cmpRes: int + while result < b: + var mid = (result + b) shr 1 + cmpRes = cmp(a[mid], key) + if cmpRes == 0: + return mid + + if cmpRes < 0: + result = mid + 1 + else: + b = mid + if result >= len or cmp(a[result], key) != 0: result = -1 + proc binarySearch*[T](a: openArray[T], key: T): int = ## binary search for `key` in `a`. Returns -1 if not found. - var b = len(a) - while result < b: - var mid = (result + b) div 2 - if a[mid] < key: result = mid + 1 - else: b = mid - if result >= len(a) or a[result] != key: result = -1 - -proc smartBinarySearch*[T](a: openArray[T], key: T): int = - ## ``a.len`` must be a power of 2 for this to work. - var step = a.len div 2 - while step > 0: - if a[result or step] <= key: - result = result or step - step = step shr 1 - if a[result] != key: result = -1 + binarySearch(a, key, cmp[T]) + +proc smartBinarySearch*[T](a: openArray[T], key: T): int {.deprecated.} = + ## **Deprecated since version 0.18.1**; Use ``binarySearch`` instead. + binarySearch(a, key, cmp[T]) const onlySafeCode = true -proc lowerBound*[T](a: openArray[T], key: T, cmp: proc(x,y: T): int {.closure.}): int = - ## same as binarySearch except that if key is not in `a` then this - ## returns the location where `key` would be if it were. In other - ## words if you have a sorted sequence and you call +proc lowerBound*[T, K](a: openArray[T], key: K, cmp: proc(x: T, k: K): int {.closure.}): int = + ## Returns a position to the first element in the `a` that is greater than `key`, or last + ## if no such element is found. In other words if you have a sorted sequence and you call ## insert(thing, elm, lowerBound(thing, elm)) ## the sequence will still be sorted. ## - ## `cmp` is the comparator function to use, the expected return values are + ## The first version uses `cmp` to compare the elements. The expected return values are ## the same as that of system.cmp. + ## The second version uses the default comparison function `cmp`. ## ## example:: ## @@ -103,7 +140,7 @@ proc lowerBound*[T](a: openArray[T], key: T, cmp: proc(x,y: T): int {.closure.}) var count = a.high - a.low + 1 var step, pos: int while count != 0: - step = count div 2 + step = count shr 1 pos = result + step if cmp(a[pos], key) < 0: result = pos + 1 @@ -113,6 +150,36 @@ proc lowerBound*[T](a: openArray[T], key: T, cmp: proc(x,y: T): int {.closure.}) proc lowerBound*[T](a: openArray[T], key: T): int = lowerBound(a, key, cmp[T]) +proc upperBound*[T, K](a: openArray[T], key: K, cmp: proc(x: T, k: K): int {.closure.}): int = + ## Returns a position to the first element in the `a` that is not less + ## (i.e. greater or equal to) than `key`, or last if no such element is found. + ## In other words if you have a sorted sequence and you call + ## insert(thing, elm, upperBound(thing, elm)) + ## the sequence will still be sorted. + ## + ## The first version uses `cmp` to compare the elements. The expected return values are + ## the same as that of system.cmp. + ## The second version uses the default comparison function `cmp`. + ## + ## example:: + ## + ## var arr = @[1,2,3,4,6,7,8,9] + ## arr.insert(5, arr.upperBound(4)) + ## # after running the above arr is `[1,2,3,4,5,6,7,8,9]` + result = a.low + var count = a.high - a.low + 1 + var step, pos: int + while count != 0: + step = count shr 1 + pos = result + step + if cmp(a[pos], key) <= 0: + result = pos + 1 + count -= step + 1 + else: + count = step + +proc upperBound*[T](a: openArray[T], key: T): int = upperBound(a, key, cmp[T]) + template `<-` (a, b) = when false: a = b @@ -359,10 +426,11 @@ when isMainModule: var srt1 = [1,2,3,4,4,4,4,5] var srt2 = ["iello","hello"] var srt3 = [1.0,1.0,1.0] - var srt4: seq[int] = @[] + var srt4: seq[int] assert srt1.isSorted(cmp) == true assert srt2.isSorted(cmp) == false assert srt3.isSorted(cmp) == true + assert srt4.isSorted(cmp) == true var srtseq = newSeq[int]() assert srtseq.isSorted(cmp) == true # Tests for reversed @@ -391,7 +459,7 @@ proc rotateInternal[T](arg: var openarray[T]; first, middle, last: int): int = swap(arg[mFirst], arg[next]) mFirst += 1 - next += 1 + next += 1 if mFirst == mMiddle: mMiddle = next @@ -443,7 +511,7 @@ proc rotateLeft*[T](arg: var openarray[T]; slice: HSlice[int, int]; dist: int): ## the distance in amount of elements that the data should be rotated. Can be negative, can be any number. ## ## .. code-block:: nim - ## var list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + ## var list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ## list.rotateLeft(1 .. 8, 3) ## doAssert list == [0, 4, 5, 6, 7, 8, 1, 2, 3, 9, 10] let sliceLen = slice.b + 1 - slice.a @@ -472,12 +540,12 @@ proc rotatedLeft*[T](arg: openarray[T]; dist: int): seq[T] = arg.rotatedInternal(0, distLeft, arg.len) when isMainModule: - var list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - let list2 = list.rotatedLeft(1 ..< 9, 3) + var list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + let list2 = list.rotatedLeft(1 ..< 9, 3) let expected = [0, 4, 5, 6, 7, 8, 1, 2, 3, 9, 10] doAssert list.rotateLeft(1 ..< 9, 3) == 6 - doAssert list == expected + doAssert list == expected doAssert list2 == @expected var s0,s1,s2,s3,s4,s5 = "xxxabcdefgxxx" @@ -494,3 +562,45 @@ when isMainModule: doAssert s4 == "xxxefgabcdxxx" doAssert s5.rotateLeft(3 ..< 10, 11) == 6 doAssert s5 == "xxxefgabcdxxx" + + block product: + doAssert product(newSeq[seq[int]]()) == newSeq[seq[int]](), "empty input" + doAssert product(@[newSeq[int](), @[], @[]]) == newSeq[seq[int]](), "bit more empty input" + doAssert product(@[@[1,2]]) == @[@[1,2]], "a simple case of one element" + doAssert product(@[@[1,2], @[3,4]]) == @[@[2,4],@[1,4],@[2,3],@[1,3]], "two elements" + doAssert product(@[@[1,2], @[3,4], @[5,6]]) == @[@[2,4,6],@[1,4,6],@[2,3,6],@[1,3,6], @[2,4,5],@[1,4,5],@[2,3,5],@[1,3,5]], "three elements" + doAssert product(@[@[1,2], @[]]) == newSeq[seq[int]](), "two elements, but one empty" + + block lowerBound: + doAssert lowerBound([1,2,4], 3, system.cmp[int]) == 2 + doAssert lowerBound([1,2,2,3], 4, system.cmp[int]) == 4 + doAssert lowerBound([1,2,3,10], 11) == 4 + + block upperBound: + doAssert upperBound([1,2,4], 3, system.cmp[int]) == 2 + doAssert upperBound([1,2,2,3], 3, system.cmp[int]) == 4 + doAssert upperBound([1,2,3,5], 3) == 3 + + block fillEmptySeq: + var s = newSeq[int]() + s.fill(0) + + block testBinarySearch: + var noData: seq[int] + doAssert binarySearch(noData, 7) == -1 + let oneData = @[1] + doAssert binarySearch(oneData, 1) == 0 + doAssert binarySearch(onedata, 7) == -1 + let someData = @[1,3,4,7] + doAssert binarySearch(someData, 1) == 0 + doAssert binarySearch(somedata, 7) == 3 + doAssert binarySearch(someData, -1) == -1 + doAssert binarySearch(someData, 5) == -1 + doAssert binarySearch(someData, 13) == -1 + let moreData = @[1,3,5,7,4711] + doAssert binarySearch(moreData, -1) == -1 + doAssert binarySearch(moreData, 1) == 0 + doAssert binarySearch(moreData, 5) == 2 + doAssert binarySearch(moreData, 6) == -1 + doAssert binarySearch(moreData, 4711) == 4 + doAssert binarySearch(moreData, 4712) == -1 diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index a52c667fc..820f34703 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -10,6 +10,7 @@ include "system/inclrtl" import os, tables, strutils, times, heapqueue, lists, options, asyncstreams +import options, math import asyncfutures except callSoon import nativesockets, net, deques @@ -157,9 +158,6 @@ export asyncfutures, asyncstreams ## ---------------- ## ## * The effect system (``raises: []``) does not work with async procedures. -## * Can't await in a ``except`` body -## * Forward declarations for async procs are broken, -## link includes workaround: https://github.com/nim-lang/Nim/issues/3182. # TODO: Check if yielded future is nil and throw a more meaningful exception @@ -168,8 +166,10 @@ type timers*: HeapQueue[tuple[finishAt: float, fut: Future[void]]] callbacks*: Deque[proc ()] -proc processTimers(p: PDispatcherBase; didSomeWork: var bool) {.inline.} = - #Process just part if timers at a step +proc processTimers( + p: PDispatcherBase, didSomeWork: var bool +): Option[int] {.inline.} = + # Pop the timers in the order in which they will expire (smaller `finishAt`). var count = p.timers.len let t = epochTime() while count > 0 and t >= p.timers[0].finishAt: @@ -177,22 +177,25 @@ proc processTimers(p: PDispatcherBase; didSomeWork: var bool) {.inline.} = dec count didSomeWork = true + # Return the number of miliseconds in which the next timer will expire. + if p.timers.len == 0: return + + let milisecs = (p.timers[0].finishAt - epochTime()) * 1000 + return some(ceil(milisecs).int) + proc processPendingCallbacks(p: PDispatcherBase; didSomeWork: var bool) = while p.callbacks.len > 0: var cb = p.callbacks.popFirst() cb() didSomeWork = true -proc adjustedTimeout(p: PDispatcherBase, timeout: int): int {.inline.} = - # If dispatcher has active timers this proc returns the timeout - # of the nearest timer. Returns `timeout` otherwise. - result = timeout - if p.timers.len > 0: - let timerTimeout = p.timers[0].finishAt - let curTime = epochTime() - if timeout == -1 or (curTime + (timeout / 1000)) > timerTimeout: - result = int((timerTimeout - curTime) * 1000) - if result < 0: result = 0 +proc adjustTimeout(pollTimeout: int, nextTimer: Option[int]): int {.inline.} = + if nextTimer.isNone(): + return pollTimeout + + result = nextTimer.get() + if pollTimeout == -1: return + result = min(pollTimeout, result) proc callSoon(cbproc: proc ()) {.gcsafe.} @@ -299,53 +302,53 @@ when defined(windows) or defined(nimdoc): "No handles or timers registered in dispatcher.") result = false - if p.handles.len != 0: - let at = p.adjustedTimeout(timeout) - var llTimeout = - if at == -1: winlean.INFINITE - else: at.int32 - - var lpNumberOfBytesTransferred: Dword - var lpCompletionKey: ULONG_PTR - var customOverlapped: PCustomOverlapped - let res = getQueuedCompletionStatus(p.ioPort, - addr lpNumberOfBytesTransferred, addr lpCompletionKey, - cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool - result = true - - # http://stackoverflow.com/a/12277264/492186 - # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html - if res: - # This is useful for ensuring the reliability of the overlapped struct. + let nextTimer = processTimers(p, result) + let at = adjustTimeout(timeout, nextTimer) + var llTimeout = + if at == -1: winlean.INFINITE + else: at.int32 + + var lpNumberOfBytesTransferred: Dword + var lpCompletionKey: ULONG_PTR + var customOverlapped: PCustomOverlapped + let res = getQueuedCompletionStatus(p.ioPort, + addr lpNumberOfBytesTransferred, addr lpCompletionKey, + cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool + result = true + + # http://stackoverflow.com/a/12277264/492186 + # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html + if res: + # This is useful for ensuring the reliability of the overlapped struct. + assert customOverlapped.data.fd == lpCompletionKey.AsyncFD + + customOverlapped.data.cb(customOverlapped.data.fd, + lpNumberOfBytesTransferred, OSErrorCode(-1)) + + # If cell.data != nil, then system.protect(rawEnv(cb)) was called, + # so we need to dispose our `cb` environment, because it is not needed + # anymore. + if customOverlapped.data.cell.data != nil: + system.dispose(customOverlapped.data.cell) + + GC_unref(customOverlapped) + else: + let errCode = osLastError() + if customOverlapped != nil: assert customOverlapped.data.fd == lpCompletionKey.AsyncFD - customOverlapped.data.cb(customOverlapped.data.fd, - lpNumberOfBytesTransferred, OSErrorCode(-1)) - - # If cell.data != nil, then system.protect(rawEnv(cb)) was called, - # so we need to dispose our `cb` environment, because it is not needed - # anymore. + lpNumberOfBytesTransferred, errCode) if customOverlapped.data.cell.data != nil: system.dispose(customOverlapped.data.cell) - GC_unref(customOverlapped) else: - let errCode = osLastError() - if customOverlapped != nil: - assert customOverlapped.data.fd == lpCompletionKey.AsyncFD - customOverlapped.data.cb(customOverlapped.data.fd, - lpNumberOfBytesTransferred, errCode) - if customOverlapped.data.cell.data != nil: - system.dispose(customOverlapped.data.cell) - GC_unref(customOverlapped) - else: - if errCode.int32 == WAIT_TIMEOUT: - # Timed out - result = false - else: raiseOSError(errCode) + if errCode.int32 == WAIT_TIMEOUT: + # Timed out + result = false + else: raiseOSError(errCode) # Timer processing. - processTimers(p, result) + discard processTimers(p, result) # Callback queue processing processPendingCallbacks(p, result) @@ -1231,48 +1234,48 @@ else: "No handles or timers registered in dispatcher.") result = false - if not p.selector.isEmpty(): - var keys: array[64, ReadyKey] - var count = p.selector.selectInto(p.adjustedTimeout(timeout), keys) - for i in 0..<count: - var custom = false - let fd = keys[i].fd - let events = keys[i].events - var rLength = 0 # len(data.readList) after callback - var wLength = 0 # len(data.writeList) after callback - - if Event.Read in events or events == {Event.Error}: - processBasicCallbacks(fd, readList) - result = true - - if Event.Write in events or events == {Event.Error}: - processBasicCallbacks(fd, writeList) - result = true - - if Event.User in events: - processBasicCallbacks(fd, readList) + var keys: array[64, ReadyKey] + let nextTimer = processTimers(p, result) + var count = p.selector.selectInto(adjustTimeout(timeout, nextTimer), keys) + for i in 0..<count: + var custom = false + let fd = keys[i].fd + let events = keys[i].events + var rLength = 0 # len(data.readList) after callback + var wLength = 0 # len(data.writeList) after callback + + if Event.Read in events or events == {Event.Error}: + processBasicCallbacks(fd, readList) + result = true + + if Event.Write in events or events == {Event.Error}: + processBasicCallbacks(fd, writeList) + result = true + + if Event.User in events: + processBasicCallbacks(fd, readList) + custom = true + if rLength == 0: + p.selector.unregister(fd) + result = true + + when ioselSupportedPlatform: + if (customSet * events) != {}: custom = true - if rLength == 0: - p.selector.unregister(fd) + processCustomCallbacks(fd) result = true - when ioselSupportedPlatform: - if (customSet * events) != {}: - custom = true - processCustomCallbacks(fd) - result = true - - # because state `data` can be modified in callback we need to update - # descriptor events with currently registered callbacks. - if not custom: - var newEvents: set[Event] = {} - if rLength != -1 and wLength != -1: - if rLength > 0: incl(newEvents, Event.Read) - if wLength > 0: incl(newEvents, Event.Write) - p.selector.updateHandle(SocketHandle(fd), newEvents) + # because state `data` can be modified in callback we need to update + # descriptor events with currently registered callbacks. + if not custom: + var newEvents: set[Event] = {} + if rLength != -1 and wLength != -1: + if rLength > 0: incl(newEvents, Event.Read) + if wLength > 0: incl(newEvents, Event.Write) + p.selector.updateHandle(SocketHandle(fd), newEvents) # Timer processing. - processTimers(p, result) + discard processTimers(p, result) # Callback queue processing processPendingCallbacks(p, result) @@ -1513,7 +1516,7 @@ proc poll*(timeout = 500) = # Common procedures between current and upcoming asyncdispatch include includes.asynccommon -proc sleepAsync*(ms: int): Future[void] = +proc sleepAsync*(ms: int | float): Future[void] = ## Suspends the execution of the current async procedure for the next ## ``ms`` milliseconds. var retFuture = newFuture[void]("sleepAsync") @@ -1533,7 +1536,11 @@ proc withTimeout*[T](fut: Future[T], timeout: int): Future[bool] = var timeoutFuture = sleepAsync(timeout) fut.callback = proc () = - if not retFuture.finished: retFuture.complete(true) + if not retFuture.finished: + if fut.failed: + retFuture.fail(fut.error) + else: + retFuture.complete(true) timeoutFuture.callback = proc () = if not retFuture.finished: retFuture.complete(false) diff --git a/lib/pure/asyncfile.nim b/lib/pure/asyncfile.nim index 97bec2815..37339d3d1 100644 --- a/lib/pure/asyncfile.nim +++ b/lib/pure/asyncfile.nim @@ -78,7 +78,10 @@ proc getFileSize*(f: AsyncFile): int64 = raiseOSError(osLastError()) result = (high shl 32) or low else: + let curPos = lseek(f.fd.cint, 0, SEEK_CUR) result = lseek(f.fd.cint, 0, SEEK_END) + f.offset = lseek(f.fd.cint, curPos, SEEK_SET) + assert(f.offset == curPos) proc newAsyncFile*(fd: AsyncFd): AsyncFile = ## Creates `AsyncFile` with a previously opened file descriptor `fd`. @@ -88,7 +91,7 @@ proc newAsyncFile*(fd: AsyncFd): AsyncFile = proc openAsync*(filename: string, mode = fmRead): AsyncFile = ## Opens a file specified by the path in ``filename`` using - ## the specified ``mode`` asynchronously. + ## the specified FileMode ``mode`` asynchronously. when defined(windows) or defined(nimdoc): let flags = FILE_FLAG_OVERLAPPED or FILE_ATTRIBUTE_NORMAL let desiredAccess = getDesiredAccess(mode) @@ -281,6 +284,7 @@ proc read*(f: AsyncFile, size: int): Future[string] = result = false # We still want this callback to be called. elif res == 0: # EOF + f.offset = lseek(fd.cint, 0, SEEK_CUR) retFuture.complete("") else: readBuffer.setLen(res) diff --git a/lib/pure/asyncftpclient.nim b/lib/pure/asyncftpclient.nim index 019a18f55..3d6a9a015 100644 --- a/lib/pure/asyncftpclient.nim +++ b/lib/pure/asyncftpclient.nim @@ -304,6 +304,7 @@ proc retrFile*(ftp: AsyncFtpClient, file, dest: string, raise newException(ReplyError, "Reply has no file size.") await getFile(ftp, destFile, fileSize, onProgressChanged) + destFile.close() proc doUpload(ftp: AsyncFtpClient, file: File, onProgressChanged: ProgressChangedProc) {.async.} = diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim index 6df6527d5..5bf9183ed 100644 --- a/lib/pure/asyncfutures.nim +++ b/lib/pure/asyncfutures.nim @@ -27,8 +27,6 @@ type FutureError* = object of Exception cause*: FutureBase -{.deprecated: [PFutureBase: FutureBase, PFuture: Future].} - when not defined(release): var currentID = 0 @@ -177,7 +175,7 @@ proc fail*[T](future: Future[T], error: ref Exception) = if getStackTrace(error) == "": getStackTrace() else: getStackTrace(error) future.callbacks.call() -proc clearCallbacks(future: FutureBase) = +proc clearCallbacks*(future: FutureBase) = future.callbacks.function = nil future.callbacks.next = nil @@ -221,10 +219,10 @@ proc getHint(entry: StackTraceEntry): string = ## We try to provide some hints about stack trace entries that the user ## may not be familiar with, in particular calls inside the stdlib. result = "" - if entry.procname == "processPendingCallbacks": + if entry.procname == cstring"processPendingCallbacks": if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0: return "Executes pending callbacks" - elif entry.procname == "poll": + elif entry.procname == cstring"poll": if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0: return "Processes asynchronous completion events" diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index ba1615651..d27c2fb9c 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -60,9 +60,6 @@ type reusePort: bool maxBody: int ## The maximum content-length that will be read for the body. -{.deprecated: [TRequest: Request, PAsyncHttpServer: AsyncHttpServer, - THttpCode: HttpCode, THttpVersion: HttpVersion].} - proc newAsyncHttpServer*(reuseAddr = true, reusePort = false, maxBody = 8388608): AsyncHttpServer = ## Creates a new ``AsyncHttpServer`` instance. @@ -132,6 +129,20 @@ proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] = proc sendStatus(client: AsyncSocket, status: string): Future[void] = client.send("HTTP/1.1 " & status & "\c\L\c\L") +proc parseUppercaseMethod(name: string): HttpMethod = + result = + case name + of "GET": HttpGet + of "POST": HttpPost + of "HEAD": HttpHead + of "PUT": HttpPut + of "DELETE": HttpDelete + of "PATCH": HttpPatch + of "OPTIONS": HttpOptions + of "CONNECT": HttpConnect + of "TRACE": HttpTrace + else: raise newException(ValueError, "Invalid HTTP method " & name) + proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], client: AsyncSocket, address: string, lineFut: FutureVar[string], @@ -175,8 +186,7 @@ proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], case i of 0: try: - # TODO: this is likely slow. - request.reqMethod = parseEnum[HttpMethod]("http" & linePart) + request.reqMethod = parseUppercaseMethod(linePart) except ValueError: asyncCheck request.respondError(Http400) return diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index f70714309..9e0893a4d 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -11,7 +11,7 @@ ## ************* ## `asyncdispatch` module depends on the `asyncmacro` module to work properly. -import macros, strutils +import macros, strutils, asyncfutures proc skipUntilStmtList(node: NimNode): NimNode {.compileTime.} = # Skips a nest of StmtList's. @@ -26,6 +26,8 @@ proc skipStmtList(node: NimNode): NimNode {.compileTime.} = template createCb(retFutureSym, iteratorNameSym, strName, identName, futureVarCompletions: untyped) = + bind finished + var nameIterVar = iteratorNameSym #{.push stackTrace: off.} proc identName {.closure.} = @@ -60,52 +62,6 @@ template createCb(retFutureSym, iteratorNameSym, identName() #{.pop.} -proc generateExceptionCheck(futSym, - tryStmt, rootReceiver, fromNode: NimNode): NimNode {.compileTime.} = - if tryStmt.kind == nnkNilLit: - result = rootReceiver - else: - var exceptionChecks: seq[tuple[cond, body: NimNode]] = @[] - let errorNode = newDotExpr(futSym, newIdentNode("error")) - for i in 1 ..< tryStmt.len: - let exceptBranch = tryStmt[i] - if exceptBranch[0].kind == nnkStmtList: - exceptionChecks.add((newIdentNode("true"), exceptBranch[0])) - else: - var exceptIdentCount = 0 - var ifCond: NimNode - for i in 0 ..< exceptBranch.len: - let child = exceptBranch[i] - if child.kind == nnkIdent: - let cond = infix(errorNode, "of", child) - if exceptIdentCount == 0: - ifCond = cond - else: - ifCond = infix(ifCond, "or", cond) - else: - break - exceptIdentCount.inc - - expectKind(exceptBranch[exceptIdentCount], nnkStmtList) - exceptionChecks.add((ifCond, exceptBranch[exceptIdentCount])) - # -> -> else: raise futSym.error - exceptionChecks.add((newIdentNode("true"), - newNimNode(nnkRaiseStmt).add(errorNode))) - # Read the future if there is no error. - # -> else: futSym.read - let elseNode = newNimNode(nnkElse, fromNode) - elseNode.add newNimNode(nnkStmtList, fromNode) - elseNode[0].add rootReceiver - - let ifBody = newStmtList() - ifBody.add newCall(newIdentNode("setCurrentException"), errorNode) - ifBody.add newIfStmt(exceptionChecks) - ifBody.add newCall(newIdentNode("setCurrentException"), newNilLit()) - - result = newIfStmt( - (newDotExpr(futSym, newIdentNode("failed")), ifBody) - ) - result.add elseNode template useVar(result: var NimNode, futureVarNode: NimNode, valueReceiver, rootReceiver: untyped, fromNode: NimNode) = @@ -121,8 +77,7 @@ template useVar(result: var NimNode, futureVarNode: NimNode, valueReceiver, result.add newNimNode(nnkYieldStmt, fromNode).add(futureVarNode) # -> future<x>.read valueReceiver = newDotExpr(futureVarNode, newIdentNode("read")) - result.add generateExceptionCheck(futureVarNode, tryStmt, rootReceiver, - fromNode) + result.add rootReceiver template createVar(result: var NimNode, futSymName: string, asyncProc: NimNode, @@ -152,8 +107,8 @@ proc createFutureVarCompletions(futureVarIdents: seq[NimNode], ) proc processBody(node, retFutureSym: NimNode, - subTypeIsVoid: bool, futureVarIdents: seq[NimNode], - tryStmt: NimNode): NimNode {.compileTime.} = + subTypeIsVoid: bool, + futureVarIdents: seq[NimNode]): NimNode {.compileTime.} = #echo(node.treeRepr) result = node case node.kind @@ -171,7 +126,7 @@ proc processBody(node, retFutureSym: NimNode, result.add newCall(newIdentNode("complete"), retFutureSym) else: let x = node[0].processBody(retFutureSym, subTypeIsVoid, - futureVarIdents, tryStmt) + futureVarIdents) if x.kind == nnkYieldStmt: result.add x else: result.add newCall(newIdentNode("complete"), retFutureSym, x) @@ -179,7 +134,7 @@ proc processBody(node, retFutureSym: NimNode, result.add newNimNode(nnkReturnStmt, node).add(newNilLit()) return # Don't process the children of this return stmt of nnkCommand, nnkCall: - if node[0].kind == nnkIdent and node[0].ident == !"await": + if node[0].kind == nnkIdent and node[0].eqIdent("await"): case node[1].kind of nnkIdent, nnkInfix, nnkDotExpr, nnkCall, nnkCommand: # await x @@ -192,7 +147,7 @@ proc processBody(node, retFutureSym: NimNode, else: error("Invalid node kind in 'await', got: " & $node[1].kind) elif node.len > 1 and node[1].kind == nnkCommand and - node[1][0].kind == nnkIdent and node[1][0].ident == !"await": + node[1][0].kind == nnkIdent and node[1][0].eqIdent("await"): # foo await x var newCommand = node result.createVar("future" & $node[0].toStrLit, node[1][1], newCommand[1], @@ -201,16 +156,16 @@ proc processBody(node, retFutureSym: NimNode, of nnkVarSection, nnkLetSection: case node[0][2].kind of nnkCommand: - if node[0][2][0].kind == nnkIdent and node[0][2][0].ident == !"await": + if node[0][2][0].kind == nnkIdent and node[0][2][0].eqIdent("await"): # var x = await y var newVarSection = node # TODO: Should this use copyNimNode? - result.createVar("future" & $node[0][0].ident, node[0][2][1], + result.createVar("future" & node[0][0].strVal, node[0][2][1], newVarSection[0][2], newVarSection, node) else: discard of nnkAsgn: case node[1].kind of nnkCommand: - if node[1][0].ident == !"await": + if node[1][0].eqIdent("await"): # x = await y var newAsgn = node result.createVar("future" & $node[0].toStrLit, node[1][1], newAsgn[1], newAsgn, node) @@ -218,74 +173,22 @@ proc processBody(node, retFutureSym: NimNode, of nnkDiscardStmt: # discard await x if node[0].kind == nnkCommand and node[0][0].kind == nnkIdent and - node[0][0].ident == !"await": + node[0][0].eqIdent("await"): var newDiscard = node result.createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1], newDiscard[0], newDiscard, node) - of nnkTryStmt: - # try: await x; except: ... - result = newNimNode(nnkStmtList, node) - template wrapInTry(n, tryBody: untyped) = - var temp = n - n[0] = tryBody - tryBody = temp - - # Transform ``except`` body. - # TODO: Could we perform some ``await`` transformation here to get it - # working in ``except``? - tryBody[1] = processBody(n[1], retFutureSym, subTypeIsVoid, - futureVarIdents, nil) - - proc processForTry(n: NimNode, i: var int, - res: NimNode): bool {.compileTime.} = - ## Transforms the body of the tryStmt. Does not transform the - ## body in ``except``. - ## Returns true if the tryStmt node was transformed into an ifStmt. - result = false - var skipped = n.skipStmtList() - while i < skipped.len: - var processed = processBody(skipped[i], retFutureSym, - subTypeIsVoid, futureVarIdents, n) - - # Check if we transformed the node into an exception check. - # This suggests skipped[i] contains ``await``. - if processed.kind != skipped[i].kind or processed.len != skipped[i].len: - processed = processed.skipUntilStmtList() - expectKind(processed, nnkStmtList) - expectKind(processed[2][1], nnkElse) - i.inc - - if not processForTry(n, i, processed[2][1][0]): - # We need to wrap the nnkElse nodes back into a tryStmt. - # As they are executed if an exception does not happen - # inside the awaited future. - # The following code will wrap the nodes inside the - # original tryStmt. - wrapInTry(n, processed[2][1][0]) - - res.add processed - result = true - else: - res.add skipped[i] - i.inc - var i = 0 - if not processForTry(node, i, result): - # If the tryStmt hasn't been transformed we can just put the body - # back into it. - wrapInTry(node, result) - return else: discard for i in 0 ..< result.len: result[i] = processBody(result[i], retFutureSym, subTypeIsVoid, - futureVarIdents, nil) + futureVarIdents) proc getName(node: NimNode): string {.compileTime.} = case node.kind of nnkPostfix: - return $node[1].ident + return node[1].strVal of nnkIdent: - return $node.ident + return node.strVal of nnkEmpty: return "anonymous" else: @@ -296,7 +199,7 @@ proc getFutureVarIdents(params: NimNode): seq[NimNode] {.compileTime.} = for i in 1 ..< len(params): expectKind(params[i], nnkIdentDefs) if params[i][1].kind == nnkBracketExpr and - ($params[i][1][0].ident).normalize == "futurevar": + params[i][1][0].eqIdent("futurevar"): result.add(params[i][0]) proc isInvalidReturnType(typeName: string): bool = @@ -323,7 +226,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = let fut = repr(returnType[0]) verifyReturnType(fut) baseType = returnType[1] - elif returnType.kind in nnkCallKinds and $returnType[0] == "[]": + elif returnType.kind in nnkCallKinds and returnType[0].eqIdent("[]"): let fut = repr(returnType[1]) verifyReturnType(fut) baseType = returnType[2] @@ -333,7 +236,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = verifyReturnType(repr(returnType)) let subtypeIsVoid = returnType.kind == nnkEmpty or - (baseType.kind == nnkIdent and returnType[1].ident == !"void") + (baseType.kind == nnkIdent and returnType[1].eqIdent("void")) let futureVarIdents = getFutureVarIdents(prc.params) @@ -348,7 +251,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = newVarStmt(retFutureSym, newCall( newNimNode(nnkBracketExpr, prc.body).add( - newIdentNode(!"newFuture"), # TODO: Strange bug here? Remove the `!`. + newIdentNode("newFuture"), subRetType), newLit(prcName)))) # Get type from return type of this proc @@ -360,7 +263,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = # -> complete(retFuture, result) var iteratorNameSym = genSym(nskIterator, $prcName & "Iter") var procBody = prc.body.processBody(retFutureSym, subtypeIsVoid, - futureVarIdents, nil) + futureVarIdents) # don't do anything with forward bodies (empty) if procBody.kind != nnkEmpty: procBody.add(createFutureVarCompletions(futureVarIdents, nil)) @@ -424,8 +327,8 @@ macro async*(prc: untyped): untyped = ## Macro which processes async procedures into the appropriate ## iterators and yield statements. if prc.kind == nnkStmtList: + result = newStmtList() for oneProc in prc: - result = newStmtList() result.add asyncSingleProc(oneProc) else: result = asyncSingleProc(prc) @@ -448,30 +351,30 @@ proc stripAwait(node: NimNode): NimNode = case node.kind of nnkCommand, nnkCall: - if node[0].kind == nnkIdent and node[0].ident == !"await": + if node[0].kind == nnkIdent and node[0].eqIdent("await"): node[0] = emptyNoopSym elif node.len > 1 and node[1].kind == nnkCommand and - node[1][0].kind == nnkIdent and node[1][0].ident == !"await": + node[1][0].kind == nnkIdent and node[1][0].eqIdent("await"): # foo await x node[1][0] = emptyNoopSym of nnkVarSection, nnkLetSection: case node[0][2].kind of nnkCommand: - if node[0][2][0].kind == nnkIdent and node[0][2][0].ident == !"await": + if node[0][2][0].kind == nnkIdent and node[0][2][0].eqIdent("await"): # var x = await y node[0][2][0] = emptyNoopSym else: discard of nnkAsgn: case node[1].kind of nnkCommand: - if node[1][0].ident == !"await": + if node[1][0].eqIdent("await"): # x = await y node[1][0] = emptyNoopSym else: discard of nnkDiscardStmt: # discard await x if node[0].kind == nnkCommand and node[0][0].kind == nnkIdent and - node[0][0].ident == !"await": + node[0][0].eqIdent("await"): node[0][0] = emptyNoopSym else: discard @@ -480,9 +383,9 @@ proc stripAwait(node: NimNode): NimNode = proc splitParamType(paramType: NimNode, async: bool): NimNode = result = paramType - if paramType.kind == nnkInfix and $paramType[0].ident in ["|", "or"]: - let firstAsync = "async" in ($paramType[1].ident).normalize - let secondAsync = "async" in ($paramType[2].ident).normalize + if paramType.kind == nnkInfix and paramType[0].strVal in ["|", "or"]: + let firstAsync = "async" in paramType[1].strVal.normalize + let secondAsync = "async" in paramType[2].strVal.normalize if firstAsync: result = paramType[if async: 1 else: 2] diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index a75f9daac..71a1600dc 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -134,8 +134,6 @@ type protocol: Protocol AsyncSocket* = ref AsyncSocketDesc -{.deprecated: [PAsyncSocket: AsyncSocket].} - proc newAsyncSocket*(fd: AsyncFD, domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM, protocol: Protocol = IPPROTO_TCP, buffered = true): AsyncSocket = @@ -201,7 +199,7 @@ when defineSsl: flags: set[SocketFlag]) {.async.} = let len = bioCtrlPending(socket.bioOut) if len > 0: - var data = newStringOfCap(len) + var data = newString(len) let read = bioRead(socket.bioOut, addr data[0], len) assert read != 0 if read < 0: @@ -495,8 +493,6 @@ proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string], ## **Warning**: ``recvLineInto`` on unbuffered sockets assumes that the ## protocol uses ``\r\L`` to delimit a new line. assert SocketFlag.Peek notin flags ## TODO: - assert(not resString.mget.isNil(), - "String inside resString future needs to be initialised") result = newFuture[void]("asyncnet.recvLineInto") # TODO: Make the async transformation check for FutureVar params and complete @@ -659,7 +655,7 @@ when defineSsl: proc wrapConnectedSocket*(ctx: SslContext, socket: AsyncSocket, handshake: SslHandshakeType, - hostname: string = nil) = + hostname: string = "") = ## Wraps a connected socket in an SSL context. This function effectively ## turns ``socket`` into an SSL socket. ## ``hostname`` should be specified so that the client knows which hostname @@ -674,7 +670,7 @@ when defineSsl: case handshake of handshakeAsClient: - if not hostname.isNil and not isIpAddress(hostname): + if hostname.len > 0 and not isIpAddress(hostname): # Set the SNI address for this connection. This call can fail if # we're not using TLSv1+. discard SSL_set_tlsext_host_name(socket.sslHandle, hostname) diff --git a/lib/pure/base64.nim b/lib/pure/base64.nim index 4b0d08292..bfb8a1666 100644 --- a/lib/pure/base64.nim +++ b/lib/pure/base64.nim @@ -52,7 +52,7 @@ template encodeInternal(s: typed, lineLen: int, newLine: string): untyped = if numLines > 0: inc(total, (numLines - 1) * newLine.len) result = newString(total) - var + var i = 0 r = 0 currLine = 0 @@ -76,7 +76,7 @@ template encodeInternal(s: typed, lineLen: int, newLine: string): untyped = currLine = 0 if i < s.len-1: - let + let a = ord(s[i]) b = ord(s[i+1]) result[r] = cb64[a shr 2] @@ -130,11 +130,11 @@ proc decode*(s: string): string = # total is an upper bound, as we will skip arbitrary whitespace: result = newString(total) - var + var i = 0 r = 0 while true: - while s[i] in Whitespace: inc(i) + while i < s.len and s[i] in Whitespace: inc(i) if i < s.len-3: let a = s[i].decodeByte diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim index 5de6aa487..101146ace 100644 --- a/lib/pure/cgi.nim +++ b/lib/pure/cgi.nim @@ -64,8 +64,6 @@ type methodPost, ## query uses the POST method methodGet ## query uses the GET method -{.deprecated: [TRequestMethod: RequestMethod, ECgi: CgiError].} - proc cgiError*(msg: string) {.noreturn.} = ## raises an ECgi exception with message `msg`. var e: ref CgiError @@ -97,11 +95,10 @@ iterator decodeData*(data: string): tuple[key, value: TaintedString] = var name = "" var value = "" # decode everything in one pass: - while data[i] != '\0': + while i < data.len: setLen(name, 0) # reuse memory - while true: + while i < data.len: case data[i] - of '\0': break of '%': var x = 0 handleHexChar(data[i+1], x) @@ -112,15 +109,16 @@ iterator decodeData*(data: string): tuple[key, value: TaintedString] = of '=', '&': break else: add(name, data[i]) inc(i) - if data[i] != '=': cgiError("'=' expected") + if i >= data.len or data[i] != '=': cgiError("'=' expected") inc(i) # skip '=' setLen(value, 0) # reuse memory - while true: + while i < data.len: case data[i] of '%': var x = 0 - handleHexChar(data[i+1], x) - handleHexChar(data[i+2], x) + if i+2 < data.len: + handleHexChar(data[i+1], x) + handleHexChar(data[i+2], x) inc(i, 2) add(value, chr(x)) of '+': add(value, ' ') @@ -128,19 +126,18 @@ iterator decodeData*(data: string): tuple[key, value: TaintedString] = else: add(value, data[i]) inc(i) yield (name.TaintedString, value.TaintedString) - if data[i] == '&': inc(i) - elif data[i] == '\0': break - else: cgiError("'&' expected") + if i < data.len: + if data[i] == '&': inc(i) + else: cgiError("'&' expected") iterator decodeData*(allowedMethods: set[RequestMethod] = {methodNone, methodPost, methodGet}): tuple[key, value: TaintedString] = ## Reads and decodes CGI data and yields the (name, value) pairs the ## data consists of. If the client does not use a method listed in the ## `allowedMethods` set, an `ECgi` exception is raised. - var data = getEncodedData(allowedMethods) - if not isNil(data): - for key, value in decodeData(data): - yield (key, value) + let data = getEncodedData(allowedMethods) + for key, value in decodeData(data): + yield (key, value) proc readData*(allowedMethods: set[RequestMethod] = {methodNone, methodPost, methodGet}): StringTableRef = diff --git a/lib/pure/collections/critbits.nim b/lib/pure/collections/critbits.nim index 34f5c5470..c94e08098 100644 --- a/lib/pure/collections/critbits.nim +++ b/lib/pure/collections/critbits.nim @@ -74,18 +74,19 @@ proc rawInsert[T](c: var CritBitTree[T], key: string): Node[T] = var newByte = 0 block blockX: while newbyte < key.len: - if it.key[newbyte] != key[newbyte]: - newotherbits = it.key[newbyte].ord xor key[newbyte].ord + let ch = if newbyte < it.key.len: it.key[newbyte] else: '\0' + if ch != key[newbyte]: + newotherbits = ch.ord xor key[newbyte].ord break blockX inc newbyte - if it.key[newbyte] != '\0': + if newbyte < it.key.len: newotherbits = it.key[newbyte].ord else: return it while (newOtherBits and (newOtherBits-1)) != 0: newOtherBits = newOtherBits and (newOtherBits-1) newOtherBits = newOtherBits xor 255 - let ch = it.key[newByte] + let ch = if newByte < it.key.len: it.key[newByte] else: '\0' let dir = (1 + (ord(ch) or newOtherBits)) shr 8 var inner: Node[T] @@ -162,18 +163,20 @@ proc containsOrIncl*(c: var CritBitTree[void], key: string): bool = var n = rawInsert(c, key) result = c.count == oldCount -proc inc*(c: var CritBitTree[int]; key: string) = - ## counts the 'key'. - let oldCount = c.count +proc inc*(c: var CritBitTree[int]; key: string, val: int = 1) = + ## increments `c[key]` by `val`. var n = rawInsert(c, key) - if c.count == oldCount: - # not a new key: - inc n.val + inc n.val, val proc incl*(c: var CritBitTree[void], key: string) = ## includes `key` in `c`. discard rawInsert(c, key) +proc incl*[T](c: var CritBitTree[T], key: string, val: T) = + ## inserts `key` with value `val` into `c`. + var n = rawInsert(c, key) + n.val = val + proc `[]=`*[T](c: var CritBitTree[T], key: string, val: T) = ## puts a (key, value)-pair into `t`. var n = rawInsert(c, key) @@ -321,10 +324,14 @@ proc `$`*[T](c: CritBitTree[T]): string = const avgItemLen = 16 result = newStringOfCap(c.count * avgItemLen) result.add("{") - for key, val in pairs(c): - if result.len > 1: result.add(", ") - result.add($key) - when T isnot void: + when T is void: + for key in keys(c): + if result.len > 1: result.add(", ") + result.addQuoted(key) + else: + for key, val in pairs(c): + if result.len > 1: result.add(", ") + result.addQuoted(key) result.add(": ") result.addQuoted(val) result.add("}") @@ -351,3 +358,37 @@ when isMainModule: assert toSeq(r.items) == @["abc", "definition", "prefix", "xyz"] assert toSeq(r.itemsWithPrefix("de")) == @["definition"] + var c = CritBitTree[int]() + + c.inc("a") + assert c["a"] == 1 + + c.inc("a", 4) + assert c["a"] == 5 + + c.inc("a", -5) + assert c["a"] == 0 + + c.inc("b", 2) + assert c["b"] == 2 + + c.inc("c", 3) + assert c["c"] == 3 + + c.inc("a", 1) + assert c["a"] == 1 + + var cf = CritBitTree[float]() + + cf.incl("a", 1.0) + assert cf["a"] == 1.0 + + cf.incl("b", 2.0) + assert cf["b"] == 2.0 + + cf.incl("c", 3.0) + assert cf["c"] == 3.0 + + assert cf.len == 3 + cf.excl("c") + assert cf.len == 2 diff --git a/lib/pure/collections/deques.nim b/lib/pure/collections/deques.nim index 328308a9b..e8342e208 100644 --- a/lib/pure/collections/deques.nim +++ b/lib/pure/collections/deques.nim @@ -38,7 +38,7 @@ ## Note: For inter thread communication use ## a `Channel <channels.html>`_ instead. -import math +import math, typetraits type Deque*[T] = object @@ -160,16 +160,15 @@ proc peekLast*[T](deq: Deque[T]): T {.inline.} = emptyCheck(deq) result = deq.data[(deq.tail - 1) and deq.mask] -template default[T](t: typedesc[T]): T = - var v: T - v +template destroy(x: untyped) = + reset(x) proc popFirst*[T](deq: var Deque[T]): T {.inline, discardable.} = ## Remove and returns the first element of the `deq`. emptyCheck(deq) dec deq.count result = deq.data[deq.head] - deq.data[deq.head] = default(type(result)) + destroy(deq.data[deq.head]) deq.head = (deq.head + 1) and deq.mask proc popLast*[T](deq: var Deque[T]): T {.inline, discardable.} = @@ -178,7 +177,34 @@ proc popLast*[T](deq: var Deque[T]): T {.inline, discardable.} = dec deq.count deq.tail = (deq.tail - 1) and deq.mask result = deq.data[deq.tail] - deq.data[deq.tail] = default(type(result)) + destroy(deq.data[deq.tail]) + +proc clear*[T](deq: var Deque[T]) {.inline.} = + ## Resets the deque so that it is empty. + for el in mitems(deq): destroy(el) + deq.count = 0 + deq.tail = deq.head + +proc shrink*[T](deq: var Deque[T], fromFirst = 0, fromLast = 0) = + ## Remove `fromFirst` elements from the front of the deque and + ## `fromLast` elements from the back. If the supplied number of + ## elements exceeds the total number of elements in the deque, + ## the deque will remain empty. + ## + ## Any user defined destructors + if fromFirst + fromLast > deq.count: + clear(deq) + return + + for i in 0 ..< fromFirst: + destroy(deq.data[deq.head]) + deq.head = (deq.head + 1) and deq.mask + + for i in 0 ..< fromLast: + destroy(deq.data[deq.tail]) + deq.tail = (deq.tail - 1) and deq.mask + + dec deq.count, fromFirst + fromLast proc `$`*[T](deq: Deque[T]): string = ## Turn a deque into its string representation. @@ -215,6 +241,22 @@ when isMainModule: assert deq.find(6) >= 0 assert deq.find(789) < 0 + block: + var d = initDeque[int](1) + d.addLast 7 + d.addLast 8 + d.addLast 10 + d.addFirst 5 + d.addFirst 2 + d.addFirst 1 + d.addLast 20 + d.shrink(fromLast = 2) + doAssert($d == "[1, 2, 5, 7, 8]") + d.shrink(2, 1) + doAssert($d == "[5, 7]") + d.shrink(2, 2) + doAssert d.len == 0 + for i in -2 .. 10: if i in deq: assert deq.contains(i) and deq.find(i) >= 0 diff --git a/lib/pure/collections/intsets.nim b/lib/pure/collections/intsets.nim index bfecfe447..545958977 100644 --- a/lib/pure/collections/intsets.nim +++ b/lib/pure/collections/intsets.nim @@ -184,7 +184,7 @@ proc missingOrExcl*(s: var IntSet, key: int) : bool = ## `key` is removed from `s` and false is returned. var count = s.elems exclImpl(s, key) - result = count == s.elems + result = count == s.elems proc containsOrIncl*(s: var IntSet, key: int): bool = ## returns true if `s` contains `key`, otherwise `key` is included in `s` @@ -212,7 +212,10 @@ proc initIntSet*: IntSet = #newSeq(result.data, InitIntSetSize) #result.max = InitIntSetSize-1 - result.data = nil + when defined(nimNoNilSeqs): + result.data = @[] + else: + result.data = nil result.max = 0 result.counter = 0 result.head = nil @@ -222,7 +225,10 @@ proc clear*(result: var IntSet) = #setLen(result.data, InitIntSetSize) #for i in 0..InitIntSetSize-1: result.data[i] = nil #result.max = InitIntSetSize-1 - result.data = nil + when defined(nimNoNilSeqs): + result.data = @[] + else: + result.data = nil result.max = 0 result.counter = 0 result.head = nil @@ -234,7 +240,10 @@ proc assign*(dest: var IntSet, src: IntSet) = ## copies `src` to `dest`. `dest` does not need to be initialized by ## `initIntSet`. if src.elems <= src.a.len: - dest.data = nil + when defined(nimNoNilSeqs): + dest.data = @[] + else: + dest.data = nil dest.max = 0 dest.counter = src.counter dest.head = nil @@ -247,11 +256,9 @@ proc assign*(dest: var IntSet, src: IntSet) = var it = src.head while it != nil: - var h = it.key and dest.max while dest.data[h] != nil: h = nextTry(h, dest.max) assert(dest.data[h] == nil) - var n: PTrunk new(n) n.next = dest.head @@ -259,7 +266,6 @@ proc assign*(dest: var IntSet, src: IntSet) = n.bits = it.bits dest.head = n dest.data[h] = n - it = it.next proc union*(s1, s2: IntSet): IntSet = @@ -315,7 +321,7 @@ proc len*(s: IntSet): int {.inline.} = for _ in s: inc(result) -proc card*(s: IntSet): int {.inline.} = +proc card*(s: IntSet): int {.inline.} = ## alias for `len() <#len>` _. result = s.len() @@ -361,7 +367,7 @@ when isMainModule: x.incl(1056) x.incl(1044) - x.excl(1044) + x.excl(1044) assert x.containsOrIncl(888) == false assert 888 in x diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 511020228..63d910a8e 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -25,6 +25,28 @@ import macros when not defined(nimhygiene): {.pragma: dirty.} + +macro evalOnceAs(expAlias, exp: untyped, letAssigneable: static[bool]): untyped = + ## Injects ``expAlias`` in caller scope, to avoid bugs involving multiple + ## substitution in macro arguments such as + ## https://github.com/nim-lang/Nim/issues/7187 + ## ``evalOnceAs(myAlias, myExp)`` will behave as ``let myAlias = myExp`` + ## except when ``letAssigneable`` is false (eg to handle openArray) where + ## it just forwards ``exp`` unchanged + expectKind(expAlias, nnkIdent) + var val = exp + + result = newStmtList() + # If `exp` is not a symbol we evaluate it once here and then use the temporary + # symbol as alias + if exp.kind != nnkSym and letAssigneable: + val = genSym() + result.add(newLetStmt(val, exp)) + + result.add( + newProc(name = genSym(nskTemplate, $expAlias), params = [getType(untyped)], + body = val, procType = nnkTemplateDef)) + proc concat*[T](seqs: varargs[seq[T]]): seq[T] = ## Takes several sequences' items and returns them inside a new sequence. ## @@ -161,7 +183,6 @@ proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = ## assert numbers.distribute(3, false) == @[@[1, 2, 3], @[4, 5, 6], @[7]] ## assert numbers.distribute(6)[0] == @[1, 2] ## assert numbers.distribute(6)[5] == @[7] - assert(not s.isNil, "`s` can't be nil") if num < 2: result = @[s] return @@ -496,13 +517,17 @@ template toSeq*(iter: untyped): untyped = ## result = true) ## assert odd_numbers == @[1, 3, 5, 7, 9] + # Note: see also `mapIt` for explanation of some of the implementation + # subtleties. when compiles(iter.len): - var i = 0 - var result = newSeq[type(iter)](iter.len) - for x in iter: - result[i] = x - inc i - result + block: + evalOnceAs(iter2, iter, true) + var result = newSeq[type(iter)](iter2.len) + var i = 0 + for x in iter2: + result[i] = x + inc i + result else: var result: seq[type(iter)] = @[] for x in iter: @@ -635,8 +660,7 @@ template mapIt*(s, typ, op: untyped): untyped = result.add(op) result - -template mapIt*(s, op: untyped): untyped = +template mapIt*(s: typed, op: untyped): untyped = ## Convenience template around the ``map`` proc to reduce typing. ## ## The template injects the ``it`` variable which you can use directly in an @@ -653,19 +677,24 @@ template mapIt*(s, op: untyped): untyped = block: var it{.inject.}: type(items(s)); op)) - var result: seq[outType] when compiles(s.len): - let t = s - var i = 0 - result = newSeq[outType](s.len) - for it {.inject.} in t: - result[i] = op - i += 1 + block: # using a block avoids https://github.com/nim-lang/Nim/issues/8580 + + # BUG: `evalOnceAs(s2, s, false)` would lead to C compile errors + # (`error: use of undeclared identifier`) instead of Nim compile errors + evalOnceAs(s2, s, compiles((let _ = s))) + + var i = 0 + var result = newSeq[outType](s2.len) + for it {.inject.} in s2: + result[i] = op + i += 1 + result else: - result = @[] + var result: seq[outType] = @[] for it {.inject.} in s: result.add(op) - result + result template applyIt*(varSeq, op: untyped) = ## Convenience template around the mutable ``apply`` proc to reduce typing. @@ -752,6 +781,14 @@ macro mapLiterals*(constructor, op: untyped; when isMainModule: import strutils + + # helper for testing double substitution side effects which are handled + # by `evalOnceAs` + var counter = 0 + proc identity[T](a:T):auto= + counter.inc + a + block: # concat test let s1 = @[1, 2, 3] @@ -854,20 +891,20 @@ when isMainModule: doAssert numbers.distribute(6)[0] == @[1, 2] doAssert numbers.distribute(6)[5] == @[7] let a = @[1, 2, 3, 4, 5, 6, 7] - doAssert a.distribute(1, true) == @[@[1, 2, 3, 4, 5, 6, 7]] - doAssert a.distribute(1, false) == @[@[1, 2, 3, 4, 5, 6, 7]] - doAssert a.distribute(2, true) == @[@[1, 2, 3, 4], @[5, 6, 7]] - doAssert a.distribute(2, false) == @[@[1, 2, 3, 4], @[5, 6, 7]] - doAssert a.distribute(3, true) == @[@[1, 2, 3], @[4, 5], @[6, 7]] - doAssert a.distribute(3, false) == @[@[1, 2, 3], @[4, 5, 6], @[7]] - doAssert a.distribute(4, true) == @[@[1, 2], @[3, 4], @[5, 6], @[7]] - doAssert a.distribute(4, false) == @[@[1, 2], @[3, 4], @[5, 6], @[7]] - doAssert a.distribute(5, true) == @[@[1, 2], @[3, 4], @[5], @[6], @[7]] - doAssert a.distribute(5, false) == @[@[1, 2], @[3, 4], @[5, 6], @[7], @[]] - doAssert a.distribute(6, true) == @[@[1, 2], @[3], @[4], @[5], @[6], @[7]] - doAssert a.distribute(6, false) == @[ + doAssert a.distribute(1, true) == @[@[1, 2, 3, 4, 5, 6, 7]] + doAssert a.distribute(1, false) == @[@[1, 2, 3, 4, 5, 6, 7]] + doAssert a.distribute(2, true) == @[@[1, 2, 3, 4], @[5, 6, 7]] + doAssert a.distribute(2, false) == @[@[1, 2, 3, 4], @[5, 6, 7]] + doAssert a.distribute(3, true) == @[@[1, 2, 3], @[4, 5], @[6, 7]] + doAssert a.distribute(3, false) == @[@[1, 2, 3], @[4, 5, 6], @[7]] + doAssert a.distribute(4, true) == @[@[1, 2], @[3, 4], @[5, 6], @[7]] + doAssert a.distribute(4, false) == @[@[1, 2], @[3, 4], @[5, 6], @[7]] + doAssert a.distribute(5, true) == @[@[1, 2], @[3, 4], @[5], @[6], @[7]] + doAssert a.distribute(5, false) == @[@[1, 2], @[3, 4], @[5, 6], @[7], @[]] + doAssert a.distribute(6, true) == @[@[1, 2], @[3], @[4], @[5], @[6], @[7]] + doAssert a.distribute(6, false) == @[ @[1, 2], @[3, 4], @[5, 6], @[7], @[], @[]] - doAssert a.distribute(8, false) == a.distribute(8, true) + doAssert a.distribute(8, false) == a.distribute(8, true) doAssert a.distribute(90, false) == a.distribute(90, true) var b = @[0] for f in 1 .. 25: b.add(f) @@ -997,6 +1034,12 @@ when isMainModule: result = true) assert odd_numbers == @[1, 3, 5, 7, 9] + block: + # tests https://github.com/nim-lang/Nim/issues/7187 + counter = 0 + let ret = toSeq(@[1, 2, 3].identity().filter(proc (x: int): bool = x < 3)) + doAssert ret == @[1, 2] + doAssert counter == 1 block: # foldl tests let numbers = @[5, 9, 11] @@ -1023,10 +1066,12 @@ when isMainModule: assert multiplication == 495, "Multiplication is (5*(9*(11)))" assert concatenation == "nimiscool" - block: # mapIt tests + block: # mapIt + applyIt test + counter = 0 var nums = @[1, 2, 3, 4] - strings = nums.mapIt($(4 * it)) + strings = nums.identity.mapIt($(4 * it)) + doAssert counter == 1 nums.applyIt(it * 3) assert nums[0] + nums[3] == 15 assert strings[2] == "12" @@ -1044,5 +1089,51 @@ when isMainModule: doAssert mapLiterals((1, ("abc"), 2), float, nested=false) == (float(1), "abc", float(2)) doAssert mapLiterals(([1], ("abc"), 2), `$`, nested=true) == (["1"], "abc", "2") + block: # mapIt with openArray + counter = 0 + proc foo(x: openArray[int]): seq[int] = x.mapIt(it * 10) + doAssert foo([identity(1),identity(2)]) == @[10, 20] + doAssert counter == 2 + + block: # mapIt with direct openArray + proc foo1(x: openArray[int]): seq[int] = x.mapIt(it * 10) + counter = 0 + doAssert foo1(openArray[int]([identity(1),identity(2)])) == @[10,20] + doAssert counter == 2 + + # Corner cases (openArray litterals should not be common) + template foo2(x: openArray[int]): seq[int] = x.mapIt(it * 10) + counter = 0 + doAssert foo2(openArray[int]([identity(1),identity(2)])) == @[10,20] + # TODO: this fails; not sure how to fix this case + # doAssert counter == 2 + + counter = 0 + doAssert openArray[int]([identity(1), identity(2)]).mapIt(it) == @[1,2] + # ditto + # doAssert counter == 2 + + block: # mapIt empty test, see https://github.com/nim-lang/Nim/pull/8584#pullrequestreview-144723468 + # NOTE: `[].mapIt(it)` is illegal, just as `let a = @[]` is (lacks type + # of elements) + doAssert: not compiles(mapIt(@[], it)) + doAssert: not compiles(mapIt([], it)) + doAssert newSeq[int](0).mapIt(it) == @[] + + block: # mapIt redifinition check, see https://github.com/nim-lang/Nim/issues/8580 + let s2 = [1,2].mapIt(it) + doAssert s2 == @[1,2] + + block: + counter = 0 + doAssert [1,2].identity().mapIt(it*2).mapIt(it*10) == @[20, 40] + # https://github.com/nim-lang/Nim/issues/7187 test case + doAssert counter == 1 + + block: # mapIt with invalid RHS for `let` (#8566) + type X = enum + A, B + doAssert mapIt(X, $it) == @["A", "B"] + when not defined(testing): echo "Finished doc tests" diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index 9e9152fc8..31ca56963 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -11,7 +11,7 @@ ## ordered hash set. ## ## Hash sets are different from the `built in set type -## <manual.html#set-type>`_. Sets allow you to store any value that can be +## <manual.html#types-set-type>`_. Sets allow you to store any value that can be ## `hashed <hashes.html>`_ and they don't contain duplicate entries. ## ## **Note**: The data types declared here have *value semantics*: This means @@ -74,7 +74,7 @@ proc isValid*[A](s: HashSet[A]): bool = ## proc savePreferences(options: HashSet[string]) = ## assert options.isValid, "Pass an initialized set!" ## # Do stuff here, may crash in release builds! - result = not s.data.isNil + result = s.data.len > 0 proc len*[A](s: HashSet[A]): int = ## Returns the number of keys in `s`. @@ -120,6 +120,13 @@ iterator items*[A](s: HashSet[A]): A = for h in 0..high(s.data): if isFilled(s.data[h].hcode): yield s.data[h].key +proc hash*[A](s: HashSet[A]): Hash = + ## hashing of HashSet + assert s.isValid, "The set needs to be initialized." + for h in 0..high(s.data): + result = result xor s.data[h].hcode + result = !$result + const growthFactor = 2 @@ -340,6 +347,18 @@ proc excl*[A](s: var HashSet[A], other: HashSet[A]) = assert other.isValid, "The set `other` needs to be initialized." for item in other: discard exclImpl(s, item) +proc pop*[A](s: var HashSet[A]): A = + ## Remove and return an arbitrary element from the set `s`. + ## + ## Raises KeyError if the set `s` is empty. + ## + for h in 0..high(s.data): + if isFilled(s.data[h].hcode): + result = s.data[h].key + excl(s, result) + return result + raise newException(KeyError, "set is empty") + proc containsOrIncl*[A](s: var HashSet[A], key: A): bool = ## Includes `key` in the set `s` and tells if `key` was added to `s`. ## @@ -635,7 +654,7 @@ proc isValid*[A](s: OrderedSet[A]): bool = ## proc saveTarotCards(cards: OrderedSet[int]) = ## assert cards.isValid, "Pass an initialized set!" ## # Do stuff here, may crash in release builds! - result = not s.data.isNil + result = s.data.len > 0 proc len*[A](s: OrderedSet[A]): int {.inline.} = ## Returns the number of keys in `s`. @@ -690,6 +709,13 @@ iterator items*[A](s: OrderedSet[A]): A = forAllOrderedPairs: yield s.data[h].key +proc hash*[A](s: OrderedSet[A]): Hash = + ## hashing of OrderedSet + assert s.isValid, "The set needs to be initialized." + forAllOrderedPairs: + result = result !& s.data[h].hcode + result = !$result + iterator pairs*[A](s: OrderedSet[A]): tuple[a: int, b: A] = assert s.isValid, "The set needs to be initialized" forAllOrderedPairs: diff --git a/lib/pure/collections/sharedtables.nim b/lib/pure/collections/sharedtables.nim index 4f311af87..0292a27a2 100644 --- a/lib/pure/collections/sharedtables.nim +++ b/lib/pure/collections/sharedtables.nim @@ -136,11 +136,13 @@ proc withKey*[A, B](t: var SharedTable[A, B], key: A, ## procedure. ## ## The ``mapper`` takes 3 arguments: - ## #. ``key`` - the current key, if it exists, or the key passed to - ## ``withKey`` otherwise; - ## #. ``val`` - the current value, if the key exists, or default value - ## of the type otherwise; - ## #. ``pairExists`` - ``true`` if the key exists, ``false`` otherwise. + ## + ## 1. ``key`` - the current key, if it exists, or the key passed to + ## ``withKey`` otherwise; + ## 2. ``val`` - the current value, if the key exists, or default value + ## of the type otherwise; + ## 3. ``pairExists`` - ``true`` if the key exists, ``false`` otherwise. + ## ## The ``mapper`` can can modify ``val`` and ``pairExists`` values to change ## the mapping of the key or delete it from the table. ## When adding a value, make sure to set ``pairExists`` to ``true`` along diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index c97846f92..f85de7546 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -158,6 +158,12 @@ template getOrDefaultImpl(t, key): untyped = var index = rawGet(t, key, hc) if index >= 0: result = t.data[index].val +template getOrDefaultImpl(t, key, default: untyped): untyped = + mixin rawGet + var hc: Hash + var index = rawGet(t, key, hc) + result = if index >= 0: t.data[index].val else: default + proc `[]`*[A, B](t: Table[A, B], key: A): B {.deprecatedGet.} = ## retrieves the value at ``t[key]``. If `key` is not in `t`, the ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether @@ -175,10 +181,18 @@ proc mget*[A, B](t: var Table[A, B], key: A): var B {.deprecated.} = ## instead. get(t, key) -proc getOrDefault*[A, B](t: Table[A, B], key: A): B = getOrDefaultImpl(t, key) +proc getOrDefault*[A, B](t: Table[A, B], key: A): B = + ## retrieves the value at ``t[key]`` iff `key` is in `t`. Otherwise, the + ## default initialization value for type `B` is returned (e.g. 0 for any + ## integer type). + getOrDefaultImpl(t, key) -template withValue*[A, B](t: var Table[A, B], key: A, - value, body: untyped) = +proc getOrDefault*[A, B](t: Table[A, B], key: A, default: B): B = + ## retrieves the value at ``t[key]`` iff `key` is in `t`. Otherwise, `default` + ## is returned. + getOrDefaultImpl(t, key, default) + +template withValue*[A, B](t: var Table[A, B], key: A, value, body: untyped) = ## retrieves the value at ``t[key]``. ## `value` can be modified in the scope of the ``withValue`` call. ## @@ -266,7 +280,7 @@ iterator mvalues*[A, B](t: var Table[A, B]): var B = if isFilled(t.data[h].hcode): yield t.data[h].val proc del*[A, B](t: var Table[A, B], key: A) = - ## deletes `key` from hash table `t`. + ## deletes `key` from hash table `t`. Does nothing if the key does not exist. delImpl() proc take*[A, B](t: var Table[A, B], key: A, val: var B): bool = @@ -325,8 +339,7 @@ proc initTable*[A, B](initialSize=64): Table[A, B] = result.counter = 0 newSeq(result.data, initialSize) -proc toTable*[A, B](pairs: openArray[(A, - B)]): Table[A, B] = +proc toTable*[A, B](pairs: openArray[(A, B)]): Table[A, B] = ## creates a new hash table that contains the given `pairs`. result = initTable[A, B](rightSize(pairs.len)) for key, val in items(pairs): result[key] = val @@ -410,7 +423,16 @@ proc mget*[A, B](t: TableRef[A, B], key: A): var B {.deprecated.} = ## Use ```[]``` instead. t[][key] -proc getOrDefault*[A, B](t: TableRef[A, B], key: A): B = getOrDefault(t[], key) +proc getOrDefault*[A, B](t: TableRef[A, B], key: A): B = + ## retrieves the value at ``t[key]`` iff `key` is in `t`. Otherwise, the + ## default initialization value for type `B` is returned (e.g. 0 for any + ## integer type). + getOrDefault(t[], key) + +proc getOrDefault*[A, B](t: TableRef[A, B], key: A, default: B): B = + ## retrieves the value at ``t[key]`` iff `key` is in `t`. Otherwise, `default` + ## is returned. + getOrDefault(t[], key, default) proc mgetOrPut*[A, B](t: TableRef[A, B], key: A, val: B): var B = ## retrieves value at ``t[key]`` or puts ``val`` if not present, either way @@ -435,7 +457,7 @@ proc add*[A, B](t: TableRef[A, B], key: A, val: B) = t[].add(key, val) proc del*[A, B](t: TableRef[A, B], key: A) = - ## deletes `key` from hash table `t`. + ## deletes `key` from hash table `t`. Does nothing if the key does not exist. t[].del(key) proc take*[A, B](t: TableRef[A, B], key: A, val: var B): bool = @@ -562,8 +584,15 @@ proc mget*[A, B](t: var OrderedTable[A, B], key: A): var B {.deprecated.} = get(t, key) proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A): B = + ## retrieves the value at ``t[key]`` iff `key` is in `t`. Otherwise, the + ## default initialization value for type `B` is returned (e.g. 0 for any + ## integer type). getOrDefaultImpl(t, key) +proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A, default: B): B = + ## retrieves the value at ``t[key]`` iff `key` is in `t`. Otherwise, `default` + ## is returned. + getOrDefaultImpl(t, key, default) proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool = ## returns true iff `key` is in the table `t`. @@ -630,8 +659,7 @@ proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] = result.last = -1 newSeq(result.data, initialSize) -proc toOrderedTable*[A, B](pairs: openArray[(A, - B)]): OrderedTable[A, B] = +proc toOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTable[A, B] = ## creates a new ordered hash table that contains the given `pairs`. result = initOrderedTable[A, B](rightSize(pairs.len)) for key, val in items(pairs): result[key] = val @@ -657,8 +685,7 @@ proc `==`*[A, B](s, t: OrderedTable[A, B]): bool = hs = nxts return true -proc sort*[A, B](t: var OrderedTable[A, B], - cmp: proc (x,y: (A, B)): int) = +proc sort*[A, B](t: var OrderedTable[A, B], cmp: proc (x,y: (A, B)): int) = ## sorts `t` according to `cmp`. This modifies the internal list ## that kept the insertion order, so insertion order is lost after this ## call but key lookup and insertions remain possible after `sort` (in @@ -748,8 +775,16 @@ proc mget*[A, B](t: OrderedTableRef[A, B], key: A): var B {.deprecated.} = result = t[][key] proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A): B = + ## retrieves the value at ``t[key]`` iff `key` is in `t`. Otherwise, the + ## default initialization value for type `B` is returned (e.g. 0 for any + ## integer type). getOrDefault(t[], key) +proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A, default: B): B = + ## retrieves the value at ``t[key]`` iff `key` is in `t`. Otherwise, `default` + ## is returned. + getOrDefault(t[], key, default) + proc mgetOrPut*[A, B](t: OrderedTableRef[A, B], key: A, val: B): var B = ## retrieves value at ``t[key]`` or puts ``val`` if not present, either way ## returning a value which can be modified. @@ -802,8 +837,7 @@ proc `==`*[A, B](s, t: OrderedTableRef[A, B]): bool = elif isNil(t): result = false else: result = s[] == t[] -proc sort*[A, B](t: OrderedTableRef[A, B], - cmp: proc (x,y: (A, B)): int) = +proc sort*[A, B](t: OrderedTableRef[A, B], cmp: proc (x,y: (A, B)): int) = ## sorts `t` according to `cmp`. This modifies the internal list ## that kept the insertion order, so insertion order is lost after this ## call but key lookup and insertions remain possible after `sort` (in @@ -811,7 +845,8 @@ proc sort*[A, B](t: OrderedTableRef[A, B], t[].sort(cmp) proc del*[A, B](t: var OrderedTable[A, B], key: A) = - ## deletes `key` from ordered hash table `t`. O(n) complexity. + ## deletes `key` from ordered hash table `t`. O(n) complexity. Does nothing + ## if the key does not exist. var n: OrderedKeyValuePairSeq[A, B] newSeq(n, len(t.data)) var h = t.first @@ -830,7 +865,8 @@ proc del*[A, B](t: var OrderedTable[A, B], key: A) = h = nxt proc del*[A, B](t: var OrderedTableRef[A, B], key: A) = - ## deletes `key` from ordered hash table `t`. O(n) complexity. + ## deletes `key` from ordered hash table `t`. O(n) complexity. Does nothing + ## if the key does not exist. t[].del(key) # ------------------------------ count tables ------------------------------- @@ -916,9 +952,17 @@ proc mget*[A](t: var CountTable[A], key: A): var int {.deprecated.} = ctget(t, key) proc getOrDefault*[A](t: CountTable[A], key: A): int = + ## retrieves the value at ``t[key]`` iff `key` is in `t`. Otherwise, 0 (the + ## default initialization value of `int`), is returned. var index = rawGet(t, key) if index >= 0: result = t.data[index].val +proc getOrDefault*[A](t: CountTable[A], key: A, default: int): int = + ## retrieves the value at ``t[key]`` iff `key` is in `t`. Otherwise, the + ## integer value of `default` is returned. + var index = rawGet(t, key) + result = if index >= 0: t.data[index].val else: default + proc hasKey*[A](t: CountTable[A], key: A): bool = ## returns true iff `key` is in the table `t`. result = rawGet(t, key) >= 0 @@ -1073,8 +1117,15 @@ proc mget*[A](t: CountTableRef[A], key: A): var int {.deprecated.} = result = t[][key] proc getOrDefault*[A](t: CountTableRef[A], key: A): int = + ## retrieves the value at ``t[key]`` iff `key` is in `t`. Otherwise, 0 (the + ## default initialization value of `int`), is returned. result = t[].getOrDefault(key) +proc getOrDefault*[A](t: CountTableRef[A], key: A, default: int): int = + ## retrieves the value at ``t[key]`` iff `key` is in `t`. Otherwise, the + ## integer value of `default` is returned. + result = t[].getOrDefault(key, default) + proc hasKey*[A](t: CountTableRef[A], key: A): bool = ## returns true iff `key` is in the table `t`. result = t[].hasKey(key) @@ -1267,7 +1318,7 @@ when isMainModule: #lib/pure/collections/tables.nim(117, 21) template/generic instantiation from here #lib/pure/collections/tableimpl.nim(32, 27) Error: undeclared field: 'hcode doAssert 0 == t.getOrDefault(testKey) - t.inc(testKey,3) + t.inc(testKey, 3) doAssert 3 == t.getOrDefault(testKey) block: @@ -1278,7 +1329,7 @@ when isMainModule: doAssert clearTable[42] == "asd" clearTable.clear() doAssert(not clearTable.hasKey(123123)) - doAssert clearTable.getOrDefault(42) == nil + doAssert clearTable.getOrDefault(42) == "" block: #5482 var a = [("wrong?","foo"), ("wrong?", "foo2")].newOrderedTable() @@ -1334,3 +1385,21 @@ when isMainModule: block: # CountTable.smallest let t = toCountTable([0, 0, 5, 5, 5]) doAssert t.smallest == (0, 2) + + block: + var tp: Table[string, string] = initTable[string, string]() + doAssert "test1" == tp.getOrDefault("test1", "test1") + tp["test2"] = "test2" + doAssert "test2" == tp.getOrDefault("test2", "test1") + var tr: TableRef[string, string] = newTable[string, string]() + doAssert "test1" == tr.getOrDefault("test1", "test1") + tr["test2"] = "test2" + doAssert "test2" == tr.getOrDefault("test2", "test1") + var op: OrderedTable[string, string] = initOrderedTable[string, string]() + doAssert "test1" == op.getOrDefault("test1", "test1") + op["test2"] = "test2" + doAssert "test2" == op.getOrDefault("test2", "test1") + var orf: OrderedTableRef[string, string] = newOrderedTable[string, string]() + doAssert "test1" == orf.getOrDefault("test1", "test1") + orf["test2"] = "test2" + doAssert "test2" == orf.getOrDefault("test2", "test1") diff --git a/lib/pure/colors.nim b/lib/pure/colors.nim index 4ec76dee0..843f29a63 100644 --- a/lib/pure/colors.nim +++ b/lib/pure/colors.nim @@ -10,12 +10,11 @@ ## the ``graphics`` module. import strutils +from algorithm import binarySearch type Color* = distinct int ## a color stored as RGB -{.deprecated: [TColor: Color].} - proc `==` *(a, b: Color): bool {.borrow.} ## compares two colors. @@ -373,37 +372,28 @@ proc `$`*(c: Color): string = ## converts a color into its textual representation. Example: ``#00FF00``. result = '#' & toHex(int(c), 6) -proc binaryStrSearch(x: openArray[tuple[name: string, col: Color]], - y: string): int = - var a = 0 - var b = len(x) - 1 - while a <= b: - var mid = (a + b) div 2 - var c = cmp(x[mid].name, y) - if c < 0: a = mid + 1 - elif c > 0: b = mid - 1 - else: return mid - result = - 1 +proc colorNameCmp(x: tuple[name: string, col: Color], y: string): int = + result = cmpIgnoreCase(x.name, y) proc parseColor*(name: string): Color = ## parses `name` to a color value. If no valid color could be - ## parsed ``EInvalidValue`` is raised. + ## parsed ``EInvalidValue`` is raised. Case insensitive. if name[0] == '#': result = Color(parseHexInt(name)) else: - var idx = binaryStrSearch(colorNames, name) + var idx = binarySearch(colorNames, name, colorNameCmp) if idx < 0: raise newException(ValueError, "unknown color: " & name) result = colorNames[idx][1] proc isColor*(name: string): bool = ## returns true if `name` is a known color name or a hexadecimal color - ## prefixed with ``#``. + ## prefixed with ``#``. Case insensitive. if name[0] == '#': for i in 1 .. name.len-1: if name[i] notin {'0'..'9', 'a'..'f', 'A'..'F'}: return false result = true else: - result = binaryStrSearch(colorNames, name) >= 0 + result = binarySearch(colorNames, name, colorNameCmp) >= 0 proc rgb*(r, g, b: range[0..255]): Color = ## constructs a color from RGB values. diff --git a/lib/pure/complex.nim b/lib/pure/complex.nim index ccde5ee0a..ba5c571ce 100644 --- a/lib/pure/complex.nim +++ b/lib/pure/complex.nim @@ -25,7 +25,9 @@ type Complex* = tuple[re, im: float] ## a complex number, consisting of a real and an imaginary part -{.deprecated: [TComplex: Complex].} +const + im*: Complex = (re: 0.0, im: 1.0) + ## The imaginary unit. √-1. proc toComplex*(x: SomeInteger): Complex = ## Convert some integer ``x`` to a complex number. diff --git a/lib/pure/concurrency/cpuinfo.nim b/lib/pure/concurrency/cpuinfo.nim index f01488811..541265da9 100644 --- a/lib/pure/concurrency/cpuinfo.nim +++ b/lib/pure/concurrency/cpuinfo.nim @@ -38,8 +38,18 @@ when defined(macosx) or defined(bsd): importc: "sysctl", nodecl.} when defined(genode): - proc affinitySpaceTotal(): cuint {. - importcpp: "genodeEnv->cpu().affinity_space().total()".} + include genode/env + + proc affinitySpaceTotal(env: GenodeEnvPtr): cuint {. + importcpp: "@->cpu().affinity_space().total()".} + +when defined(haiku): + {.emit: "#include <OS.h>".} + type + SystemInfo {.importc: "system_info", bycopy.} = object + cpuCount {.importc: "cpu_count".}: uint32 + + proc getSystemInfo(info: ptr SystemInfo): int32 {.importc: "get_system_info".} proc countProcessors*(): int {.rtl, extern: "ncpi$1".} = ## returns the numer of the processors/cores the machine has. @@ -83,7 +93,11 @@ proc countProcessors*(): int {.rtl, extern: "ncpi$1".} = var SC_NPROC_ONLN {.importc: "_SC_NPROC_ONLN", header: "<unistd.h>".}: cint result = sysconf(SC_NPROC_ONLN) elif defined(genode): - result = affinitySpaceTotal().int + result = runtimeEnv.affinitySpaceTotal().int + elif defined(haiku): + var sysinfo: SystemInfo + if getSystemInfo(addr sysinfo) == 0: + result = sysinfo.cpuCount.int else: result = sysconf(SC_NPROCESSORS_ONLN) if result <= 0: result = 0 diff --git a/lib/pure/concurrency/threadpool.nim b/lib/pure/concurrency/threadpool.nim index a5eaec86e..f3b13fac5 100644 --- a/lib/pure/concurrency/threadpool.nim +++ b/lib/pure/concurrency/threadpool.nim @@ -30,7 +30,7 @@ proc destroySemaphore(cv: var Semaphore) {.inline.} = deinitCond(cv.c) deinitLock(cv.L) -proc await(cv: var Semaphore) = +proc blockUntil(cv: var Semaphore) = acquire(cv.L) while cv.counter <= 0: wait(cv.c, cv.L) @@ -81,7 +81,7 @@ proc closeBarrier(b: ptr Barrier) {.compilerProc.} = fence() b.interest = true fence() - while b.left != b.entered: await(b.cv) + while b.left != b.entered: blockUntil(b.cv) destroySemaphore(b.cv) {.pop.} @@ -89,8 +89,6 @@ proc closeBarrier(b: ptr Barrier) {.compilerProc.} = # ---------------------------------------------------------------------------- type - foreign* = object ## a region that indicates the pointer comes from a - ## foreign thread heap. AwaitInfo = object cv: Semaphore idx: int @@ -99,7 +97,7 @@ type FlowVarBaseObj = object of RootObj ready, usesSemaphore, awaited: bool cv: Semaphore #\ - # for 'awaitAny' support + # for 'blockUntilAny' support ai: ptr AwaitInfo idx: int data: pointer # we incRef and unref it to keep it alive; note this MUST NOT @@ -130,12 +128,12 @@ type q: ToFreeQueue readyForTask: Semaphore -proc await*(fv: FlowVarBase) = +proc blockUntil*(fv: FlowVarBase) = ## waits until the value for the flowVar arrives. Usually it is not necessary ## to call this explicitly. if fv.usesSemaphore and not fv.awaited: fv.awaited = true - await(fv.cv) + blockUntil(fv.cv) destroySemaphore(fv.cv) proc selectWorker(w: ptr Worker; fn: WorkerProc; data: pointer): bool = @@ -143,7 +141,7 @@ proc selectWorker(w: ptr Worker; fn: WorkerProc; data: pointer): bool = w.data = data w.f = fn signal(w.taskArrived) - await(w.taskStarted) + blockUntil(w.taskStarted) result = true proc cleanFlowVars(w: ptr Worker) = @@ -168,12 +166,21 @@ proc wakeupWorkerToProcessQueue(w: ptr Worker) = signal(w.q.empty) signal(w.taskArrived) +proc attach(fv: FlowVarBase; i: int): bool = + acquire(fv.cv.L) + if fv.cv.counter <= 0: + fv.idx = i + result = true + else: + result = false + release(fv.cv.L) + proc finished(fv: FlowVarBase) = - doAssert fv.ai.isNil, "flowVar is still attached to an 'awaitAny'" + doAssert fv.ai.isNil, "flowVar is still attached to an 'blockUntilAny'" # we have to protect against the rare cases where the owner of the flowVar # simply disregards the flowVar and yet the "flowVar" has not yet written # anything to it: - await(fv) + blockUntil(fv) if fv.data.isNil: return let owner = cast[ptr Worker](fv.owner) let q = addr(owner.q) @@ -182,7 +189,7 @@ proc finished(fv: FlowVarBase) = #echo "EXHAUSTED!" release(q.lock) wakeupWorkerToProcessQueue(owner) - await(q.empty) + blockUntil(q.empty) acquire(q.lock) q.data[q.len] = cast[pointer](fv.data) inc q.len @@ -213,7 +220,7 @@ proc awaitAndThen*[T](fv: FlowVar[T]; action: proc (x: T) {.closure.}) = ## to ``action``. Note that due to Nim's parameter passing semantics this ## means that ``T`` doesn't need to be copied and so ``awaitAndThen`` can ## sometimes be more efficient than ``^``. - await(fv) + blockUntil(fv) when T is string or T is seq: action(cast[T](fv.data)) elif T is ref: @@ -222,49 +229,50 @@ proc awaitAndThen*[T](fv: FlowVar[T]; action: proc (x: T) {.closure.}) = action(fv.blob) finished(fv) -proc unsafeRead*[T](fv: FlowVar[ref T]): foreign ptr T = +proc unsafeRead*[T](fv: FlowVar[ref T]): ptr T = ## blocks until the value is available and then returns this value. - await(fv) - result = cast[foreign ptr T](fv.data) + blockUntil(fv) + result = cast[ptr T](fv.data) proc `^`*[T](fv: FlowVar[ref T]): ref T = ## blocks until the value is available and then returns this value. - await(fv) + blockUntil(fv) let src = cast[ref T](fv.data) deepCopy result, src proc `^`*[T](fv: FlowVar[T]): T = ## blocks until the value is available and then returns this value. - await(fv) + blockUntil(fv) when T is string or T is seq: # XXX closures? deepCopy? result = cast[T](fv.data) else: result = fv.blob -proc awaitAny*(flowVars: openArray[FlowVarBase]): int = +proc blockUntilAny*(flowVars: openArray[FlowVarBase]): int = ## awaits any of the given flowVars. Returns the index of one flowVar for - ## which a value arrived. A flowVar only supports one call to 'awaitAny' at - ## the same time. That means if you await([a,b]) and await([b,c]) the second - ## call will only await 'c'. If there is no flowVar left to be able to wait + ## which a value arrived. A flowVar only supports one call to 'blockUntilAny' at + ## the same time. That means if you blockUntilAny([a,b]) and blockUntilAny([b,c]) the second + ## call will only blockUntil 'c'. If there is no flowVar left to be able to wait ## on, -1 is returned. - ## **Note**: This results in non-deterministic behaviour and so should be - ## avoided. + ## **Note**: This results in non-deterministic behaviour and should be avoided. var ai: AwaitInfo ai.cv.initSemaphore() var conflicts = 0 + result = -1 for i in 0 .. flowVars.high: if cas(addr flowVars[i].ai, nil, addr ai): - flowVars[i].idx = i + if not attach(flowVars[i], i): + result = i + break else: inc conflicts if conflicts < flowVars.len: - await(ai.cv) - result = ai.idx + if result < 0: + blockUntil(ai.cv) + result = ai.idx for i in 0 .. flowVars.high: discard cas(addr flowVars[i].ai, addr ai, nil) - else: - result = -1 destroySemaphore(ai.cv) proc isReady*(fv: FlowVarBase): bool = @@ -318,10 +326,10 @@ proc slave(w: ptr Worker) {.thread.} = w.ready = true readyWorker = w signal(gSomeReady) - await(w.taskArrived) + blockUntil(w.taskArrived) # XXX Somebody needs to look into this (why does this assertion fail # in Visual Studio?) - when not defined(vcc): assert(not w.ready) + when not defined(vcc) and not defined(tcc): assert(not w.ready) withLock numSlavesLock: inc numSlavesRunning @@ -343,7 +351,7 @@ proc distinguishedSlave(w: ptr Worker) {.thread.} = else: w.ready = true signal(w.readyForTask) - await(w.taskArrived) + blockUntil(w.taskArrived) assert(not w.ready) w.f(w, w.data) if w.q.len != 0: w.cleanFlowVars @@ -491,7 +499,7 @@ proc nimSpawn3(fn: WorkerProc; data: pointer) {.compilerProc.} = # on the current thread instead. var self = addr(workersData[localThreadId-1]) fn(self, data) - await(self.taskStarted) + blockUntil(self.taskStarted) return if isSlave: @@ -516,7 +524,7 @@ proc nimSpawn3(fn: WorkerProc; data: pointer) {.compilerProc.} = inc numSlavesWaiting - await(gSomeReady) + blockUntil(gSomeReady) if isSlave: withLock numSlavesLock: @@ -534,7 +542,7 @@ proc nimSpawn4(fn: WorkerProc; data: pointer; id: ThreadId) {.compilerProc.} = release(distinguishedLock) while true: if selectWorker(addr(distinguishedData[id]), fn, data): break - await(distinguishedData[id].readyForTask) + blockUntil(distinguishedData[id].readyForTask) proc sync*() = @@ -547,7 +555,7 @@ proc sync*() = if not allReady: break allReady = allReady and workersData[i].ready if allReady: break - await(gSomeReady) + blockUntil(gSomeReady) inc toRelease for i in 0 ..< toRelease: diff --git a/lib/pure/cookies.nim b/lib/pure/cookies.nim index 8f16717ac..eaff86ae6 100644 --- a/lib/pure/cookies.nim +++ b/lib/pure/cookies.nim @@ -25,16 +25,16 @@ proc parseCookies*(s: string): StringTableRef = result = newStringTable(modeCaseInsensitive) var i = 0 while true: - while s[i] == ' ' or s[i] == '\t': inc(i) + while i < s.len and (s[i] == ' ' or s[i] == '\t'): inc(i) var keystart = i - while s[i] != '=' and s[i] != '\0': inc(i) + while i < s.len and s[i] != '=': inc(i) var keyend = i-1 - if s[i] == '\0': break + if i >= s.len: break inc(i) # skip '=' var valstart = i - while s[i] != ';' and s[i] != '\0': inc(i) + while i < s.len and s[i] != ';': inc(i) result[substr(s, keystart, keyend)] = substr(s, valstart, i-1) - if s[i] == '\0': break + if i >= s.len: break inc(i) # skip ';' proc setCookie*(key, value: string, domain = "", path = "", @@ -51,26 +51,26 @@ proc setCookie*(key, value: string, domain = "", path = "", if secure: result.add("; Secure") if httpOnly: result.add("; HttpOnly") -proc setCookie*(key, value: string, expires: DateTime, +proc setCookie*(key, value: string, expires: DateTime|Time, domain = "", path = "", noName = false, secure = false, httpOnly = false): string = ## Creates a command in the format of ## ``Set-Cookie: key=value; Domain=...; ...`` - ## - ## **Note:** UTC is assumed as the timezone for ``expires``. return setCookie(key, value, domain, path, - format(expires, "ddd',' dd MMM yyyy HH:mm:ss 'GMT'"), + format(expires.utc, "ddd',' dd MMM yyyy HH:mm:ss 'GMT'"), noname, secure, httpOnly) when isMainModule: - var tim = fromUnix(getTime().toUnix + 76 * (60 * 60 * 24)) + let expire = fromUnix(0) + 1.seconds - let cookie = setCookie("test", "value", tim.utc) - when not defined(testing): - echo cookie - let start = "Set-Cookie: test=value; Expires=" - assert cookie[0..start.high] == start + let cookies = [ + setCookie("test", "value", expire), + setCookie("test", "value", expire.local), + setCookie("test", "value", expire.utc) + ] + let expected = "Set-Cookie: test=value; Expires=Thu, 01 Jan 1970 00:00:01 GMT" + doAssert cookies == [expected, expected, expected] let table = parseCookies("uid=1; kp=2") - assert table["uid"] == "1" - assert table["kp"] == "2" + doAssert table["uid"] == "1" + doAssert table["kp"] == "2" diff --git a/lib/pure/coro.nim b/lib/pure/coro.nim index b6ef30e7c..2fe34ed40 100644 --- a/lib/pure/coro.nim +++ b/lib/pure/coro.nim @@ -43,6 +43,10 @@ when defined(windows): {.warning: "ucontext coroutine backend is not available on windows, defaulting to fibers.".} when defined(nimCoroutinesSetjmp): {.warning: "setjmp coroutine backend is not available on windows, defaulting to fibers.".} +elif defined(haiku): + const coroBackend = CORO_BACKEND_SETJMP + when defined(nimCoroutinesUcontext): + {.warning: "ucontext coroutine backend is not available on haiku, defaulting to setjmp".} elif defined(nimCoroutinesSetjmp) or defined(nimCoroutinesSetjmpBundled): const coroBackend = CORO_BACKEND_SETJMP else: @@ -55,21 +59,21 @@ when coroBackend == CORO_BACKEND_FIBERS: elif coroBackend == CORO_BACKEND_UCONTEXT: type - stack_t {.importc, header: "<sys/ucontext.h>".} = object + stack_t {.importc, header: "<ucontext.h>".} = object ss_sp: pointer ss_flags: int ss_size: int - ucontext_t {.importc, header: "<sys/ucontext.h>".} = object + ucontext_t {.importc, header: "<ucontext.h>".} = object uc_link: ptr ucontext_t uc_stack: stack_t Context = ucontext_t - proc getcontext(context: var ucontext_t): int32 {.importc, header: "<sys/ucontext.h>".} - proc setcontext(context: var ucontext_t): int32 {.importc, header: "<sys/ucontext.h>".} - proc swapcontext(fromCtx, toCtx: var ucontext_t): int32 {.importc, header: "<sys/ucontext.h>".} - proc makecontext(context: var ucontext_t, fn: pointer, argc: int32) {.importc, header: "<sys/ucontext.h>", varargs.} + proc getcontext(context: var ucontext_t): int32 {.importc, header: "<ucontext.h>".} + proc setcontext(context: var ucontext_t): int32 {.importc, header: "<ucontext.h>".} + proc swapcontext(fromCtx, toCtx: var ucontext_t): int32 {.importc, header: "<ucontext.h>".} + proc makecontext(context: var ucontext_t, fn: pointer, argc: int32) {.importc, header: "<ucontext.h>", varargs.} elif coroBackend == CORO_BACKEND_SETJMP: proc coroExecWithStack*(fn: pointer, stack: pointer) {.noreturn, importc: "narch_$1", fastcall.} @@ -111,7 +115,12 @@ elif coroBackend == CORO_BACKEND_SETJMP: when defined(unix): # GLibc fails with "*** longjmp causes uninitialized stack frame ***" because # our custom stacks are not initialized to a magic value. - {.passC: "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0"} + when defined(osx): + # workaround: error: The deprecated ucontext routines require _XOPEN_SOURCE to be defined + const extra = " -D_XOPEN_SOURCE" + else: + const extra = "" + {.passC: "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0" & extra.} const CORO_CREATED = 0 @@ -190,7 +199,7 @@ proc switchTo(current, to: CoroutinePtr) = elif to.state == CORO_CREATED: # Coroutine is started. coroExecWithStack(runCurrentTask, to.stack.bottom) - doAssert false + #doAssert false else: {.error: "Invalid coroutine backend set.".} # Execution was just resumed. Restore frame information and set active stack. diff --git a/lib/pure/cstrutils.nim b/lib/pure/cstrutils.nim index 437140892..fe9ceb68b 100644 --- a/lib/pure/cstrutils.nim +++ b/lib/pure/cstrutils.nim @@ -45,8 +45,10 @@ proc endsWith*(s, suffix: cstring): bool {.noSideEffect, proc cmpIgnoreStyle*(a, b: cstring): int {.noSideEffect, rtl, extern: "csuCmpIgnoreStyle".} = - ## Compares two strings normalized (i.e. case and - ## underscores do not matter). Returns: + ## Semantically the same as ``cmp(normalize($a), normalize($b))``. It + ## is just optimized to not allocate temporary strings. This should + ## NOT be used to compare Nim identifier names. use `macros.eqIdent` + ## for that. Returns: ## ## | 0 iff a == b ## | < 0 iff a < b diff --git a/lib/pure/db_common.nim b/lib/pure/db_common.nim index 957389605..f8689024b 100644 --- a/lib/pure/db_common.nim +++ b/lib/pure/db_common.nim @@ -83,9 +83,6 @@ type foreignKey*: bool ## is this a foreign key? DbColumns* = seq[DbColumn] -{.deprecated: [EDb: DbError, TSqlQuery: SqlQuery, FDb: DbEffect, - FReadDb: ReadDbEffect, FWriteDb: WriteDbEffect].} - template sql*(query: string): SqlQuery = ## constructs a SqlQuery from the string `query`. This is supposed to be ## used as a raw-string-literal modifier: diff --git a/lib/pure/distros.nim b/lib/pure/distros.nim index 0adba5b1e..5847cfadb 100644 --- a/lib/pure/distros.nim +++ b/lib/pure/distros.nim @@ -126,6 +126,8 @@ type OpenBSD DragonFlyBSD + Haiku + const LacksDevPackages* = {Distribution.Gentoo, Distribution.Slackware, @@ -166,6 +168,8 @@ proc detectOsImpl(d: Distribution): bool = of Distribution.Solaris: let uname = toLowerAscii(uname()) result = ("sun" in uname) or ("solaris" in uname) + of Distribution.Haiku: + result = defined(haiku) else: let dd = toLowerAscii($d) result = dd in toLowerAscii(uname()) or dd in toLowerAscii(release()) @@ -224,6 +228,8 @@ proc foreignDepInstallCmd*(foreignPackageName: string): (string, bool) = result = ("pacman -S " & p, true) else: result = ("<your package manager here> install " & p, true) + elif defined(haiku): + result = ("pkgman install " & p, true) else: result = ("brew install " & p, false) diff --git a/lib/pure/dynlib.nim b/lib/pure/dynlib.nim index fda41dadb..d030ad532 100644 --- a/lib/pure/dynlib.nim +++ b/lib/pure/dynlib.nim @@ -10,14 +10,55 @@ ## This module implements the ability to access symbols from shared ## libraries. On POSIX this uses the ``dlsym`` mechanism, on ## Windows ``LoadLibrary``. +## +## Examples +## -------- +## +## Loading a simple C function +## ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +## +## The following example demonstrates loading a function called 'greet' +## from a library that is determined at runtime based upon a language choice. +## If the library fails to load or the function 'greet' is not found, +## it quits with a failure error code. +## +## .. code-block::nim +## +## import dynlib +## +## type +## greetFunction = proc(): cstring {.gcsafe, stdcall.} +## +## let lang = stdin.readLine() +## +## let lib = case lang +## of "french": +## loadLib("french.dll") +## else: +## loadLib("english.dll") +## +## if lib == nil: +## echo "Error loading library" +## quit(QuitFailure) +## +## let greet = cast[greetFunction](lib.symAddr("greet")) +## +## if greet == nil: +## echo "Error loading 'greet' function from library" +## quit(QuitFailure) +## +## let greeting = greet() +## +## echo greeting +## +## unloadLib(lib) +## import strutils type LibHandle* = pointer ## a handle to a dynamically loaded library -{.deprecated: [TLibHandle: LibHandle].} - proc loadLib*(path: string, global_symbols=false): LibHandle {.gcsafe.} ## loads a library from `path`. Returns nil if the library could not ## be loaded. @@ -96,6 +137,28 @@ when defined(posix): proc symAddr(lib: LibHandle, name: cstring): pointer = return dlsym(lib, name) +elif defined(nintendoswitch): + # + # ========================================================================= + # Nintendo switch DevkitPro sdk does not have these. Raise an error if called. + # ========================================================================= + # + + proc dlclose(lib: LibHandle) = + raise newException(OSError, "dlclose not implemented on Nintendo Switch!") + proc dlopen(path: cstring, mode: int): LibHandle = + raise newException(OSError, "dlopen not implemented on Nintendo Switch!") + proc dlsym(lib: LibHandle, name: cstring): pointer = + raise newException(OSError, "dlsym not implemented on Nintendo Switch!") + proc loadLib(path: string, global_symbols=false): LibHandle = + raise newException(OSError, "loadLib not implemented on Nintendo Switch!") + proc loadLib(): LibHandle = + raise newException(OSError, "loadLib not implemented on Nintendo Switch!") + proc unloadLib(lib: LibHandle) = + raise newException(OSError, "unloadLib not implemented on Nintendo Switch!") + proc symAddr(lib: LibHandle, name: cstring): pointer = + raise newException(OSError, "symAddr not implemented on Nintendo Switch!") + elif defined(windows) or defined(dos): # # ======================================================================= diff --git a/lib/pure/encodings.nim b/lib/pure/encodings.nim index 5840d443d..2039a31be 100644 --- a/lib/pure/encodings.nim +++ b/lib/pure/encodings.nim @@ -27,8 +27,6 @@ type EncodingError* = object of ValueError ## exception that is raised ## for encoding errors -{.deprecated: [EInvalidEncoding: EncodingError, PConverter: EncodingConverter].} - when defined(windows): proc eqEncodingNames(a, b: string): bool = var i = 0 @@ -36,7 +34,7 @@ when defined(windows): while i < a.len and j < b.len: if a[i] in {'-', '_'}: inc i if b[j] in {'-', '_'}: inc j - if a[i].toLower != b[j].toLower: return false + if i < a.len and j < b.len and a[i].toLowerAscii != b[j].toLowerAscii: return false inc i inc j result = i == a.len and j == b.len @@ -257,7 +255,7 @@ when defined(windows): else: when defined(haiku): - const iconvDll = "(libc.so.6|libiconv.so|libtextencoding.so)" + const iconvDll = "libiconv.so" elif defined(macosx): const iconvDll = "libiconv.dylib" else: @@ -274,6 +272,8 @@ else: const EILSEQ = 86.cint elif defined(solaris): const EILSEQ = 88.cint + elif defined(haiku): + const EILSEQ = -2147454938.cint var errno {.importc, header: "<errno.h>".}: cint @@ -334,7 +334,7 @@ when defined(windows): if s.len == 0: return "" # educated guess of capacity: var cap = s.len + s.len shr 2 - result = newStringOfCap(cap*2) + result = newString(cap*2) # convert to utf-16 LE var m = multiByteToWideChar(codePage = c.src, dwFlags = 0'i32, lpMultiByteStr = cstring(s), @@ -349,7 +349,7 @@ when defined(windows): lpWideCharStr = nil, cchWideChar = cint(0)) # and do the conversion properly: - result = newStringOfCap(cap*2) + result = newString(cap*2) m = multiByteToWideChar(codePage = c.src, dwFlags = 0'i32, lpMultiByteStr = cstring(s), cbMultiByte = cint(s.len), @@ -366,7 +366,7 @@ when defined(windows): if int(c.dest) == 1200: return # otherwise the fun starts again: cap = s.len + s.len shr 2 - var res = newStringOfCap(cap) + var res = newString(cap) m = wideCharToMultiByte( codePage = c.dest, dwFlags = 0'i32, @@ -384,7 +384,7 @@ when defined(windows): lpMultiByteStr = nil, cbMultiByte = cint(0)) # and do the conversion properly: - res = newStringOfCap(cap) + res = newString(cap) m = wideCharToMultiByte( codePage = c.dest, dwFlags = 0'i32, diff --git a/lib/pure/events.nim b/lib/pure/events.nim index 23a8a2c58..a39dbe3bf 100644 --- a/lib/pure/events.nim +++ b/lib/pure/events.nim @@ -43,9 +43,6 @@ type s: seq[EventHandler] EventError* = object of ValueError -{.deprecated: [TEventArgs: EventArgs, TEventHandler: EventHandler, - TEventEmitter: EventEmitter, EInvalidEvent: EventError].} - proc initEventHandler*(name: string): EventHandler = ## Initializes an EventHandler with the specified name and returns it. result.handlers = @[] diff --git a/lib/pure/fenv.nim b/lib/pure/fenv.nim index f8f115ecc..0725973ca 100644 --- a/lib/pure/fenv.nim +++ b/lib/pure/fenv.nim @@ -10,9 +10,9 @@ ## Floating-point environment. Handling of floating-point rounding and ## exceptions (overflow, division by zero, etc.). -{.deadCodeElim:on.} +{.deadCodeElim: on.} # dce option deprecated -when defined(Posix) and not defined(haiku): +when defined(Posix): {.passl: "-lm".} var @@ -102,32 +102,33 @@ proc feupdateenv*(envp: ptr Tfenv): cint {.importc, header: "<fenv.h>".} ## represented by object pointed to by `envp` and raise exceptions ## according to saved exceptions. -var FP_RADIX_INTERNAL {. importc: "FLT_RADIX" header: "<float.h>" .} : int - -template fpRadix* : int = FP_RADIX_INTERNAL +const + FLT_RADIX = 2 ## the radix of the exponent representation + + FLT_MANT_DIG = 24 ## the number of base FLT_RADIX digits in the mantissa part of a float + FLT_DIG = 6 ## the number of digits of precision of a float + FLT_MIN_EXP = -125 # the minimum value of base FLT_RADIX in the exponent part of a float + FLT_MAX_EXP = 128 # the maximum value of base FLT_RADIX in the exponent part of a float + FLT_MIN_10_EXP = -37 ## the minimum value in base 10 of the exponent part of a float + FLT_MAX_10_EXP = 38 ## the maximum value in base 10 of the exponent part of a float + FLT_MIN = 1.17549435e-38'f32 ## the minimum value of a float + FLT_MAX = 3.40282347e+38'f32 ## the maximum value of a float + FLT_EPSILON = 1.19209290e-07'f32 ## the difference between 1 and the least value greater than 1 of a float + + DBL_MANT_DIG = 53 ## the number of base FLT_RADIX digits in the mantissa part of a double + DBL_DIG = 15 ## the number of digits of precision of a double + DBL_MIN_EXP = -1021 ## the minimum value of base FLT_RADIX in the exponent part of a double + DBL_MAX_EXP = 1024 ## the maximum value of base FLT_RADIX in the exponent part of a double + DBL_MIN_10_EXP = -307 ## the minimum value in base 10 of the exponent part of a double + DBL_MAX_10_EXP = 308 ## the maximum value in base 10 of the exponent part of a double + DBL_MIN = 2.2250738585072014E-308 ## the minimal value of a double + DBL_MAX = 1.7976931348623157E+308 ## the minimal value of a double + DBL_EPSILON = 2.2204460492503131E-16 ## the difference between 1 and the least value greater than 1 of a double + +template fpRadix* : int = FLT_RADIX ## The (integer) value of the radix used to represent any floating ## point type on the architecture used to build the program. -var FLT_MANT_DIG {. importc: "FLT_MANT_DIG" header: "<float.h>" .} : int -var FLT_DIG {. importc: "FLT_DIG" header: "<float.h>" .} : int -var FLT_MIN_EXP {. importc: "FLT_MIN_EXP" header: "<float.h>" .} : int -var FLT_MAX_EXP {. importc: "FLT_MAX_EXP" header: "<float.h>" .} : int -var FLT_MIN_10_EXP {. importc: "FLT_MIN_10_EXP" header: "<float.h>" .} : int -var FLT_MAX_10_EXP {. importc: "FLT_MAX_10_EXP" header: "<float.h>" .} : int -var FLT_MIN {. importc: "FLT_MIN" header: "<float.h>" .} : cfloat -var FLT_MAX {. importc: "FLT_MAX" header: "<float.h>" .} : cfloat -var FLT_EPSILON {. importc: "FLT_EPSILON" header: "<float.h>" .} : cfloat - -var DBL_MANT_DIG {. importc: "DBL_MANT_DIG" header: "<float.h>" .} : int -var DBL_DIG {. importc: "DBL_DIG" header: "<float.h>" .} : int -var DBL_MIN_EXP {. importc: "DBL_MIN_EXP" header: "<float.h>" .} : int -var DBL_MAX_EXP {. importc: "DBL_MAX_EXP" header: "<float.h>" .} : int -var DBL_MIN_10_EXP {. importc: "DBL_MIN_10_EXP" header: "<float.h>" .} : int -var DBL_MAX_10_EXP {. importc: "DBL_MAX_10_EXP" header: "<float.h>" .} : int -var DBL_MIN {. importc: "DBL_MIN" header: "<float.h>" .} : cdouble -var DBL_MAX {. importc: "DBL_MAX" header: "<float.h>" .} : cdouble -var DBL_EPSILON {. importc: "DBL_EPSILON" header: "<float.h>" .} : cdouble - template mantissaDigits*(T : typedesc[float32]) : int = FLT_MANT_DIG ## Number of digits (in base ``floatingPointRadix``) in the mantissa ## of 32-bit floating-point numbers. @@ -179,3 +180,11 @@ template maximumPositiveValue*(T : typedesc[float64]) : float64 = DBL_MAX template epsilon*(T : typedesc[float64]): float64 = DBL_EPSILON ## The difference between 1.0 and the smallest number greater than ## 1.0 that can be represented in a 64-bit floating-point type. + + +when isMainModule: + func is_significant(x: float): bool = + if x > minimumPositiveValue(float) and x < maximumPositiveValue(float): true + else: false + + doAssert is_significant(10.0) diff --git a/lib/pure/future.nim b/lib/pure/future.nim index 1a3ab688d..40dba7846 100644 --- a/lib/pure/future.nim +++ b/lib/pure/future.nim @@ -1,200 +1,4 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2015 Dominik Picheta -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# -## This module implements experimental features which may soon be moved to -## the system module (or other more appropriate modules). +{.deprecated: "Use the new 'sugar' module instead".} -import macros - -proc createProcType(p, b: NimNode): NimNode {.compileTime.} = - #echo treeRepr(p) - #echo treeRepr(b) - result = newNimNode(nnkProcTy) - var formalParams = newNimNode(nnkFormalParams) - - formalParams.add b - - case p.kind - of nnkPar: - for i in 0 ..< p.len: - let ident = p[i] - var identDefs = newNimNode(nnkIdentDefs) - case ident.kind - of nnkExprColonExpr: - identDefs.add ident[0] - identDefs.add ident[1] - else: - identDefs.add newIdentNode("i" & $i) - identDefs.add(ident) - identDefs.add newEmptyNode() - formalParams.add identDefs - else: - var identDefs = newNimNode(nnkIdentDefs) - identDefs.add newIdentNode("i0") - identDefs.add(p) - identDefs.add newEmptyNode() - formalParams.add identDefs - - result.add formalParams - result.add newEmptyNode() - #echo(treeRepr(result)) - #echo(result.toStrLit()) - -macro `=>`*(p, b: untyped): untyped = - ## Syntax sugar for anonymous procedures. - ## - ## .. code-block:: nim - ## - ## proc passTwoAndTwo(f: (int, int) -> int): int = - ## f(2, 2) - ## - ## passTwoAndTwo((x, y) => x + y) # 4 - - #echo treeRepr(p) - #echo(treeRepr(b)) - var params: seq[NimNode] = @[newIdentNode("auto")] - - case p.kind - of nnkPar: - for c in children(p): - var identDefs = newNimNode(nnkIdentDefs) - case c.kind - of nnkExprColonExpr: - identDefs.add(c[0]) - identDefs.add(c[1]) - identDefs.add(newEmptyNode()) - of nnkIdent: - identDefs.add(c) - identDefs.add(newIdentNode("auto")) - identDefs.add(newEmptyNode()) - of nnkInfix: - if c[0].kind == nnkIdent and c[0].ident == !"->": - var procTy = createProcType(c[1], c[2]) - params[0] = procTy[0][0] - for i in 1 ..< procTy[0].len: - params.add(procTy[0][i]) - else: - error("Expected proc type (->) got (" & $c[0].ident & ").") - break - else: - echo treeRepr c - error("Incorrect procedure parameter list.") - params.add(identDefs) - of nnkIdent: - var identDefs = newNimNode(nnkIdentDefs) - identDefs.add(p) - identDefs.add(newIdentNode("auto")) - identDefs.add(newEmptyNode()) - params.add(identDefs) - of nnkInfix: - if p[0].kind == nnkIdent and p[0].ident == !"->": - var procTy = createProcType(p[1], p[2]) - params[0] = procTy[0][0] - for i in 1 ..< procTy[0].len: - params.add(procTy[0][i]) - else: - error("Expected proc type (->) got (" & $p[0].ident & ").") - else: - error("Incorrect procedure parameter list.") - result = newProc(params = params, body = b, procType = nnkLambda) - #echo(result.treeRepr) - #echo(result.toStrLit()) - #return result # TODO: Bug? - -macro `->`*(p, b: untyped): untyped = - ## Syntax sugar for procedure types. - ## - ## .. code-block:: nim - ## - ## proc pass2(f: (float, float) -> float): float = - ## f(2, 2) - ## - ## # is the same as: - ## - ## proc pass2(f: proc (x, y: float): float): float = - ## f(2, 2) - - result = createProcType(p, b) - -type ListComprehension = object -var lc*: ListComprehension - -macro `[]`*(lc: ListComprehension, comp, typ: untyped): untyped = - ## List comprehension, returns a sequence. `comp` is the actual list - ## comprehension, for example ``x | (x <- 1..10, x mod 2 == 0)``. `typ` is - ## the type that will be stored inside the result seq. - ## - ## .. code-block:: nim - ## - ## echo lc[x | (x <- 1..10, x mod 2 == 0), int] - ## - ## const n = 20 - ## echo lc[(x,y,z) | (x <- 1..n, y <- x..n, z <- y..n, x*x + y*y == z*z), - ## tuple[a,b,c: int]] - - expectLen(comp, 3) - expectKind(comp, nnkInfix) - expectKind(comp[0], nnkIdent) - assert($comp[0].ident == "|") - - result = newCall( - newDotExpr( - newIdentNode("result"), - newIdentNode("add")), - comp[1]) - - for i in countdown(comp[2].len-1, 0): - let x = comp[2][i] - expectMinLen(x, 1) - if x[0].kind == nnkIdent and $x[0].ident == "<-": - expectLen(x, 3) - result = newNimNode(nnkForStmt).add(x[1], x[2], result) - else: - result = newIfStmt((x, result)) - - result = newNimNode(nnkCall).add( - newNimNode(nnkPar).add( - newNimNode(nnkLambda).add( - newEmptyNode(), - newEmptyNode(), - newEmptyNode(), - newNimNode(nnkFormalParams).add( - newNimNode(nnkBracketExpr).add( - newIdentNode("seq"), - typ)), - newEmptyNode(), - newEmptyNode(), - newStmtList( - newAssignment( - newIdentNode("result"), - newNimNode(nnkPrefix).add( - newIdentNode("@"), - newNimNode(nnkBracket))), - result)))) - - -macro dump*(x: typed): untyped = - ## Dumps the content of an expression, useful for debugging. - ## It accepts any expression and prints a textual representation - ## of the tree representing the expression - as it would appear in - ## source code - together with the value of the expression. - ## - ## As an example, - ## - ## .. code-block:: nim - ## let - ## x = 10 - ## y = 20 - ## dump(x + y) - ## - ## will print ``x + y = 30``. - let s = x.toStrLit - let r = quote do: - debugEcho `s`, " = ", `x` - return r +include sugar diff --git a/lib/pure/hashes.nim b/lib/pure/hashes.nim index b015ed311..6535bb0d3 100644 --- a/lib/pure/hashes.nim +++ b/lib/pure/hashes.nim @@ -45,7 +45,6 @@ type Hash* = int ## a hash value; hash tables using these values should ## always have a size of a power of two and can use the ``and`` ## operator instead of ``mod`` for truncation of the hash value. -{.deprecated: [THash: Hash].} proc `!&`*(h: Hash, val: int): Hash {.inline.} = ## mixes a hash value `h` with `val` to produce a new hash value. This is diff --git a/lib/pure/htmlgen.nim b/lib/pure/htmlgen.nim index c0934a45b..a2e224f44 100644 --- a/lib/pure/htmlgen.nim +++ b/lib/pure/htmlgen.nim @@ -31,14 +31,23 @@ import macros, strutils const - coreAttr* = " id class title style " - eventAttr* = " onclick ondblclick onmousedown onmouseup " & - "onmouseover onmousemove onmouseout onkeypress onkeydown onkeyup onload " - commonAttr* = coreAttr & eventAttr + coreAttr* = " accesskey class contenteditable dir hidden id lang " & + "spellcheck style tabindex title translate " + eventAttr* = "onabort onblur oncancel oncanplay oncanplaythrough onchange " & + "onclick oncuechange ondblclick ondurationchange onemptied onended " & + "onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload " & + "onloadeddata onloadedmetadata onloadstart onmousedown onmouseenter " & + "onmouseleave onmousemove onmouseout onmouseover onmouseup onmousewheel " & + "onpause onplay onplaying onprogress onratechange onreset onresize " & + "onscroll onseeked onseeking onselect onshow onstalled onsubmit " & + "onsuspend ontimeupdate ontoggle onvolumechange onwaiting " + ariaAttr* = " role " + commonAttr* = coreAttr & eventAttr & ariaAttr proc getIdent(e: NimNode): string {.compileTime.} = case e.kind - of nnkIdent: result = normalize($e.ident) + of nnkIdent: + result = e.strVal.normalize of nnkAccQuoted: result = getIdent(e[0]) for i in 1 .. e.len-1: @@ -92,19 +101,21 @@ proc xmlCheckedTag*(e: NimNode, tag: string, optAttr = "", reqAttr = "", result.add(newStrLitNode("</")) result.add(newStrLitNode(tag)) result.add(newStrLitNode(">")) - result = nestList(!"&", result) - + when compiles(nestList(ident"&", result)): + result = nestList(ident"&", result) + else: + result = nestList(!"&", result) macro a*(e: varargs[untyped]): untyped = ## generates the HTML ``a`` element. let e = callsite() - result = xmlCheckedTag(e, "a", "href charset type hreflang rel rev " & - "accesskey tabindex" & commonAttr) + result = xmlCheckedTag(e, "a", "href target download rel hreflang type " & + commonAttr) -macro acronym*(e: varargs[untyped]): untyped = - ## generates the HTML ``acronym`` element. +macro abbr*(e: varargs[untyped]): untyped = + ## generates the HTML ``abbr`` element. let e = callsite() - result = xmlCheckedTag(e, "acronym", commonAttr) + result = xmlCheckedTag(e, "abbr", commonAttr) macro address*(e: varargs[untyped]): untyped = ## generates the HTML ``address`` element. @@ -114,8 +125,24 @@ macro address*(e: varargs[untyped]): untyped = macro area*(e: varargs[untyped]): untyped = ## generates the HTML ``area`` element. let e = callsite() - result = xmlCheckedTag(e, "area", "shape coords href nohref" & - " accesskey tabindex" & commonAttr, "alt", true) + result = xmlCheckedTag(e, "area", "coords download href hreflang rel " & + "shape target type" & commonAttr, "alt", true) + +macro article*(e: varargs[untyped]): untyped = + ## generates the HTML ``article`` element. + let e = callsite() + result = xmlCheckedTag(e, "article", commonAttr) + +macro aside*(e: varargs[untyped]): untyped = + ## generates the HTML ``aside`` element. + let e = callsite() + result = xmlCheckedTag(e, "aside", commonAttr) + +macro audio*(e: varargs[untyped]): untyped = + ## generates the HTML ``audio`` element. + let e = callsite() + result = xmlCheckedTag(e, "audio", "src crossorigin preload " & + "autoplay mediagroup loop muted controls" & commonAttr) macro b*(e: varargs[untyped]): untyped = ## generates the HTML ``b`` element. @@ -125,7 +152,17 @@ macro b*(e: varargs[untyped]): untyped = macro base*(e: varargs[untyped]): untyped = ## generates the HTML ``base`` element. let e = callsite() - result = xmlCheckedTag(e, "base", "", "href", true) + result = xmlCheckedTag(e, "base", "href target" & commonAttr, "", true) + +macro bdi*(e: varargs[untyped]): untyped = + ## generates the HTML ``bdi`` element. + let e = callsite() + result = xmlCheckedTag(e, "bdi", commonAttr) + +macro bdo*(e: varargs[untyped]): untyped = + ## generates the HTML ``bdo`` element. + let e = callsite() + result = xmlCheckedTag(e, "bdo", commonAttr) macro big*(e: varargs[untyped]): untyped = ## generates the HTML ``big`` element. @@ -140,18 +177,26 @@ macro blockquote*(e: varargs[untyped]): untyped = macro body*(e: varargs[untyped]): untyped = ## generates the HTML ``body`` element. let e = callsite() - result = xmlCheckedTag(e, "body", commonAttr) + result = xmlCheckedTag(e, "body", "onafterprint onbeforeprint " & + "onbeforeunload onhashchange onmessage onoffline ononline onpagehide " & + "onpageshow onpopstate onstorage onunload" & commonAttr) macro br*(e: varargs[untyped]): untyped = ## generates the HTML ``br`` element. let e = callsite() - result = xmlCheckedTag(e, "br", "", "", true) + result = xmlCheckedTag(e, "br", commonAttr, "", true) macro button*(e: varargs[untyped]): untyped = ## generates the HTML ``button`` element. let e = callsite() - result = xmlCheckedTag(e, "button", "accesskey tabindex " & - "disabled name type value" & commonAttr) + result = xmlCheckedTag(e, "button", "autofocus disabled form formaction " & + "formenctype formmethod formnovalidate formtarget menu name type value" & + commonAttr) + +macro canvas*(e: varargs[untyped]): untyped = + ## generates the HTML ``canvas`` element. + let e = callsite() + result = xmlCheckedTag(e, "canvas", "width height" & commonAttr) macro caption*(e: varargs[untyped]): untyped = ## generates the HTML ``caption`` element. @@ -171,12 +216,22 @@ macro code*(e: varargs[untyped]): untyped = macro col*(e: varargs[untyped]): untyped = ## generates the HTML ``col`` element. let e = callsite() - result = xmlCheckedTag(e, "col", "span align valign" & commonAttr, "", true) + result = xmlCheckedTag(e, "col", "span" & commonAttr, "", true) macro colgroup*(e: varargs[untyped]): untyped = ## generates the HTML ``colgroup`` element. let e = callsite() - result = xmlCheckedTag(e, "colgroup", "span align valign" & commonAttr) + result = xmlCheckedTag(e, "colgroup", "span" & commonAttr) + +macro data*(e: varargs[untyped]): untyped = + ## generates the HTML ``data`` element. + let e = callsite() + result = xmlCheckedTag(e, "data", "value" & commonAttr) + +macro datalist*(e: varargs[untyped]): untyped = + ## generates the HTML ``datalist`` element. + let e = callsite() + result = xmlCheckedTag(e, "datalist", commonAttr) macro dd*(e: varargs[untyped]): untyped = ## generates the HTML ``dd`` element. @@ -213,16 +268,37 @@ macro em*(e: varargs[untyped]): untyped = let e = callsite() result = xmlCheckedTag(e, "em", commonAttr) +macro embed*(e: varargs[untyped]): untyped = + ## generates the HTML ``embed`` element. + let e = callsite() + result = xmlCheckedTag(e, "embed", "src type height width" & + commonAttr, "", true) + macro fieldset*(e: varargs[untyped]): untyped = ## generates the HTML ``fieldset`` element. let e = callsite() - result = xmlCheckedTag(e, "fieldset", commonAttr) + result = xmlCheckedTag(e, "fieldset", "disabled form name" & commonAttr) + +macro figure*(e: varargs[untyped]): untyped = + ## generates the HTML ``figure`` element. + let e = callsite() + result = xmlCheckedTag(e, "figure", commonAttr) + +macro figcaption*(e: varargs[untyped]): untyped = + ## generates the HTML ``figcaption`` element. + let e = callsite() + result = xmlCheckedTag(e, "figcaption", commonAttr) + +macro footer*(e: varargs[untyped]): untyped = + ## generates the HTML ``footer`` element. + let e = callsite() + result = xmlCheckedTag(e, "footer", commonAttr) macro form*(e: varargs[untyped]): untyped = ## generates the HTML ``form`` element. let e = callsite() - result = xmlCheckedTag(e, "form", "method encype accept accept-charset" & - commonAttr, "action") + result = xmlCheckedTag(e, "form", "accept-charset action autocomplete " & + "enctype method name novalidate target" & commonAttr) macro h1*(e: varargs[untyped]): untyped = ## generates the HTML ``h1`` element. @@ -257,7 +333,12 @@ macro h6*(e: varargs[untyped]): untyped = macro head*(e: varargs[untyped]): untyped = ## generates the HTML ``head`` element. let e = callsite() - result = xmlCheckedTag(e, "head", "profile") + result = xmlCheckedTag(e, "head", commonAttr) + +macro header*(e: varargs[untyped]): untyped = + ## generates the HTML ``header`` element. + let e = callsite() + result = xmlCheckedTag(e, "header", commonAttr) macro html*(e: varargs[untyped]): untyped = ## generates the HTML ``html`` element. @@ -274,16 +355,26 @@ macro i*(e: varargs[untyped]): untyped = let e = callsite() result = xmlCheckedTag(e, "i", commonAttr) +macro iframe*(e: varargs[untyped]): untyped = + ## generates the HTML ``iframe`` element. + let e = callsite() + result = xmlCheckedTag(e, "iframe", "src srcdoc name sandbox width height" & + commonAttr) + macro img*(e: varargs[untyped]): untyped = ## generates the HTML ``img`` element. let e = callsite() - result = xmlCheckedTag(e, "img", "longdesc height width", "src alt", true) + result = xmlCheckedTag(e, "img", "crossorigin usemap ismap height width" & + commonAttr, "src alt", true) macro input*(e: varargs[untyped]): untyped = ## generates the HTML ``input`` element. let e = callsite() - result = xmlCheckedTag(e, "input", "name type value checked maxlength src" & - " alt accept disabled readonly accesskey tabindex" & commonAttr, "", true) + result = xmlCheckedTag(e, "input", "accept alt autocomplete autofocus " & + "checked dirname disabled form formaction formenctype formmethod " & + "formnovalidate formtarget height inputmode list max maxlength min " & + "minlength multiple name pattern placeholder readonly required size " & + "src step type value width" & commonAttr, "", true) macro ins*(e: varargs[untyped]): untyped = ## generates the HTML ``ins`` element. @@ -295,36 +386,64 @@ macro kbd*(e: varargs[untyped]): untyped = let e = callsite() result = xmlCheckedTag(e, "kbd", commonAttr) +macro keygen*(e: varargs[untyped]): untyped = + ## generates the HTML ``keygen`` element. + let e = callsite() + result = xmlCheckedTag(e, "keygen", "autofocus challenge disabled " & + "form keytype name" & commonAttr) + macro label*(e: varargs[untyped]): untyped = ## generates the HTML ``label`` element. let e = callsite() - result = xmlCheckedTag(e, "label", "for accesskey" & commonAttr) + result = xmlCheckedTag(e, "label", "form for" & commonAttr) macro legend*(e: varargs[untyped]): untyped = ## generates the HTML ``legend`` element. let e = callsite() - result = xmlCheckedTag(e, "legend", "accesskey" & commonAttr) + result = xmlCheckedTag(e, "legend", commonAttr) macro li*(e: varargs[untyped]): untyped = ## generates the HTML ``li`` element. let e = callsite() - result = xmlCheckedTag(e, "li", commonAttr) + result = xmlCheckedTag(e, "li", "value" & commonAttr) macro link*(e: varargs[untyped]): untyped = ## generates the HTML ``link`` element. let e = callsite() - result = xmlCheckedTag(e, "link", "href charset hreflang type rel rev media" & - commonAttr, "", true) + result = xmlCheckedTag(e, "link", "href crossorigin rel media hreflang " & + "type sizes" & commonAttr, "", true) + +macro main*(e: varargs[untyped]): untyped = + ## generates the HTML ``main`` element. + let e = callsite() + result = xmlCheckedTag(e, "main", commonAttr) macro map*(e: varargs[untyped]): untyped = ## generates the HTML ``map`` element. let e = callsite() - result = xmlCheckedTag(e, "map", "class title" & eventAttr, "id", false) + result = xmlCheckedTag(e, "map", "name" & commonAttr) + +macro mark*(e: varargs[untyped]): untyped = + ## generates the HTML ``mark`` element. + let e = callsite() + result = xmlCheckedTag(e, "mark", commonAttr) macro meta*(e: varargs[untyped]): untyped = ## generates the HTML ``meta`` element. let e = callsite() - result = xmlCheckedTag(e, "meta", "name http-equiv scheme", "content", true) + result = xmlCheckedTag(e, "meta", "name http-equiv content charset" & + commonAttr, "", true) + +macro meter*(e: varargs[untyped]): untyped = + ## generates the HTML ``meter`` element. + let e = callsite() + result = xmlCheckedTag(e, "meter", "value min max low high optimum" & + commonAttr) + +macro nav*(e: varargs[untyped]): untyped = + ## generates the HTML ``nav`` element. + let e = callsite() + result = xmlCheckedTag(e, "nav", commonAttr) macro noscript*(e: varargs[untyped]): untyped = ## generates the HTML ``noscript`` element. @@ -334,13 +453,13 @@ macro noscript*(e: varargs[untyped]): untyped = macro `object`*(e: varargs[untyped]): untyped = ## generates the HTML ``object`` element. let e = callsite() - result = xmlCheckedTag(e, "object", "classid data codebase declare type " & - "codetype archive standby width height name tabindex" & commonAttr) + result = xmlCheckedTag(e, "object", "data type typemustmatch name usemap " & + "form width height" & commonAttr) macro ol*(e: varargs[untyped]): untyped = ## generates the HTML ``ol`` element. let e = callsite() - result = xmlCheckedTag(e, "ol", commonAttr) + result = xmlCheckedTag(e, "ol", "reversed start type" & commonAttr) macro optgroup*(e: varargs[untyped]): untyped = ## generates the HTML ``optgroup`` element. @@ -350,7 +469,13 @@ macro optgroup*(e: varargs[untyped]): untyped = macro option*(e: varargs[untyped]): untyped = ## generates the HTML ``option`` element. let e = callsite() - result = xmlCheckedTag(e, "option", "selected value" & commonAttr) + result = xmlCheckedTag(e, "option", "disabled label selected value" & + commonAttr) + +macro output*(e: varargs[untyped]): untyped = + ## generates the HTML ``output`` element. + let e = callsite() + result = xmlCheckedTag(e, "output", "for form name" & commonAttr) macro p*(e: varargs[untyped]): untyped = ## generates the HTML ``p`` element. @@ -360,18 +485,53 @@ macro p*(e: varargs[untyped]): untyped = macro param*(e: varargs[untyped]): untyped = ## generates the HTML ``param`` element. let e = callsite() - result = xmlCheckedTag(e, "param", "value id type valuetype", "name", true) + result = xmlCheckedTag(e, "param", commonAttr, "name value", true) macro pre*(e: varargs[untyped]): untyped = ## generates the HTML ``pre`` element. let e = callsite() result = xmlCheckedTag(e, "pre", commonAttr) +macro progress*(e: varargs[untyped]): untyped = + ## generates the HTML ``progress`` element. + let e = callsite() + result = xmlCheckedTag(e, "progress", "value max" & commonAttr) + macro q*(e: varargs[untyped]): untyped = ## generates the HTML ``q`` element. let e = callsite() result = xmlCheckedTag(e, "q", "cite" & commonAttr) +macro rb*(e: varargs[untyped]): untyped = + ## generates the HTML ``rb`` element. + let e = callsite() + result = xmlCheckedTag(e, "rb", commonAttr) + +macro rp*(e: varargs[untyped]): untyped = + ## generates the HTML ``rp`` element. + let e = callsite() + result = xmlCheckedTag(e, "rp", commonAttr) + +macro rt*(e: varargs[untyped]): untyped = + ## generates the HTML ``rt`` element. + let e = callsite() + result = xmlCheckedTag(e, "rt", commonAttr) + +macro rtc*(e: varargs[untyped]): untyped = + ## generates the HTML ``rtc`` element. + let e = callsite() + result = xmlCheckedTag(e, "rtc", commonAttr) + +macro ruby*(e: varargs[untyped]): untyped = + ## generates the HTML ``ruby`` element. + let e = callsite() + result = xmlCheckedTag(e, "ruby", commonAttr) + +macro s*(e: varargs[untyped]): untyped = + ## generates the HTML ``s`` element. + let e = callsite() + result = xmlCheckedTag(e, "s", commonAttr) + macro samp*(e: varargs[untyped]): untyped = ## generates the HTML ``samp`` element. let e = callsite() @@ -380,19 +540,30 @@ macro samp*(e: varargs[untyped]): untyped = macro script*(e: varargs[untyped]): untyped = ## generates the HTML ``script`` element. let e = callsite() - result = xmlCheckedTag(e, "script", "src charset defer", "type", false) + result = xmlCheckedTag(e, "script", "src type charset async defer " & + "crossorigin" & commonAttr) + +macro section*(e: varargs[untyped]): untyped = + ## generates the HTML ``section`` element. + let e = callsite() + result = xmlCheckedTag(e, "section", commonAttr) macro select*(e: varargs[untyped]): untyped = ## generates the HTML ``select`` element. let e = callsite() - result = xmlCheckedTag(e, "select", "name size multiple disabled tabindex" & - commonAttr) + result = xmlCheckedTag(e, "select", "autofocus disabled form multiple " & + "name required size" & commonAttr) macro small*(e: varargs[untyped]): untyped = ## generates the HTML ``small`` element. let e = callsite() result = xmlCheckedTag(e, "small", commonAttr) +macro source*(e: varargs[untyped]): untyped = + ## generates the HTML ``source`` element. + let e = callsite() + result = xmlCheckedTag(e, "source", "type" & commonAttr, "src", true) + macro span*(e: varargs[untyped]): untyped = ## generates the HTML ``span`` element. let e = callsite() @@ -406,7 +577,7 @@ macro strong*(e: varargs[untyped]): untyped = macro style*(e: varargs[untyped]): untyped = ## generates the HTML ``style`` element. let e = callsite() - result = xmlCheckedTag(e, "style", "media title", "type") + result = xmlCheckedTag(e, "style", "media type" & commonAttr) macro sub*(e: varargs[untyped]): untyped = ## generates the HTML ``sub`` element. @@ -421,57 +592,77 @@ macro sup*(e: varargs[untyped]): untyped = macro table*(e: varargs[untyped]): untyped = ## generates the HTML ``table`` element. let e = callsite() - result = xmlCheckedTag(e, "table", "summary border cellpadding cellspacing" & - " frame rules width" & commonAttr) + result = xmlCheckedTag(e, "table", "border sortable" & commonAttr) macro tbody*(e: varargs[untyped]): untyped = ## generates the HTML ``tbody`` element. let e = callsite() - result = xmlCheckedTag(e, "tbody", "align valign" & commonAttr) + result = xmlCheckedTag(e, "tbody", commonAttr) macro td*(e: varargs[untyped]): untyped = ## generates the HTML ``td`` element. let e = callsite() - result = xmlCheckedTag(e, "td", "colspan rowspan abbr axis headers scope" & - " align valign" & commonAttr) + result = xmlCheckedTag(e, "td", "colspan rowspan headers" & commonAttr) + +macro `template`*(e: varargs[untyped]): untyped = + ## generates the HTML ``template`` element. + let e = callsite() + result = xmlCheckedTag(e, "template", commonAttr) macro textarea*(e: varargs[untyped]): untyped = ## generates the HTML ``textarea`` element. let e = callsite() - result = xmlCheckedTag(e, "textarea", " name disabled readonly accesskey" & - " tabindex" & commonAttr, "rows cols", false) + result = xmlCheckedTag(e, "textarea", "autocomplete autofocus cols " & + "dirname disabled form inputmode maxlength minlength name placeholder " & + "readonly required rows wrap" & commonAttr) macro tfoot*(e: varargs[untyped]): untyped = ## generates the HTML ``tfoot`` element. let e = callsite() - result = xmlCheckedTag(e, "tfoot", "align valign" & commonAttr) + result = xmlCheckedTag(e, "tfoot", commonAttr) macro th*(e: varargs[untyped]): untyped = ## generates the HTML ``th`` element. let e = callsite() - result = xmlCheckedTag(e, "th", "colspan rowspan abbr axis headers scope" & - " align valign" & commonAttr) + result = xmlCheckedTag(e, "th", "colspan rowspan headers abbr scope axis" & + " sorted" & commonAttr) macro thead*(e: varargs[untyped]): untyped = ## generates the HTML ``thead`` element. let e = callsite() - result = xmlCheckedTag(e, "thead", "align valign" & commonAttr) + result = xmlCheckedTag(e, "thead", commonAttr) + +macro time*(e: varargs[untyped]): untyped = + ## generates the HTML ``time`` element. + let e = callsite() + result = xmlCheckedTag(e, "time", "datetime" & commonAttr) macro title*(e: varargs[untyped]): untyped = ## generates the HTML ``title`` element. let e = callsite() - result = xmlCheckedTag(e, "title") + result = xmlCheckedTag(e, "title", commonAttr) macro tr*(e: varargs[untyped]): untyped = ## generates the HTML ``tr`` element. let e = callsite() - result = xmlCheckedTag(e, "tr", "align valign" & commonAttr) + result = xmlCheckedTag(e, "tr", commonAttr) + +macro track*(e: varargs[untyped]): untyped = + ## generates the HTML ``track`` element. + let e = callsite() + result = xmlCheckedTag(e, "track", "kind srclang label default" & + commonAttr, "src", true) macro tt*(e: varargs[untyped]): untyped = ## generates the HTML ``tt`` element. let e = callsite() result = xmlCheckedTag(e, "tt", commonAttr) +macro u*(e: varargs[untyped]): untyped = + ## generates the HTML ``u`` element. + let e = callsite() + result = xmlCheckedTag(e, "u", commonAttr) + macro ul*(e: varargs[untyped]): untyped = ## generates the HTML ``ul`` element. let e = callsite() @@ -482,6 +673,17 @@ macro `var`*(e: varargs[untyped]): untyped = let e = callsite() result = xmlCheckedTag(e, "var", commonAttr) +macro video*(e: varargs[untyped]): untyped = + ## generates the HTML ``video`` element. + let e = callsite() + result = xmlCheckedTag(e, "video", "src crossorigin poster preload " & + "autoplay mediagroup loop muted controls width height" & commonAttr) + +macro wbr*(e: varargs[untyped]): untyped = + ## generates the HTML ``wbr`` element. + let e = callsite() + result = xmlCheckedTag(e, "wbr", commonAttr, "", true) + when isMainModule: let nim = "Nim" assert h1(a(href="http://nim-lang.org", nim)) == diff --git a/lib/pure/htmlparser.nim b/lib/pure/htmlparser.nim index a718a10fd..fbf2b8e73 100644 --- a/lib/pure/htmlparser.nim +++ b/lib/pure/htmlparser.nim @@ -178,7 +178,6 @@ type tagVar, ## the HTML ``var`` element tagVideo, ## the HTML ``video`` element tagWbr ## the HTML ``wbr`` element -{.deprecated: [THtmlTag: HtmlTag].} const tagToStr* = [ @@ -218,79 +217,6 @@ const tagBr, tagCol, tagFrame, tagHr, tagImg, tagIsindex, tagLink, tagMeta, tagParam, tagWbr} - Entities = [ - ("nbsp", 0x00A0), ("iexcl", 0x00A1), ("cent", 0x00A2), ("pound", 0x00A3), - ("curren", 0x00A4), ("yen", 0x00A5), ("brvbar", 0x00A6), ("sect", 0x00A7), - ("uml", 0x00A8), ("copy", 0x00A9), ("ordf", 0x00AA), ("laquo", 0x00AB), - ("not", 0x00AC), ("shy", 0x00AD), ("reg", 0x00AE), ("macr", 0x00AF), - ("deg", 0x00B0), ("plusmn", 0x00B1), ("sup2", 0x00B2), ("sup3", 0x00B3), - ("acute", 0x00B4), ("micro", 0x00B5), ("para", 0x00B6), ("middot", 0x00B7), - ("cedil", 0x00B8), ("sup1", 0x00B9), ("ordm", 0x00BA), ("raquo", 0x00BB), - ("frac14", 0x00BC), ("frac12", 0x00BD), ("frac34", 0x00BE), - ("iquest", 0x00BF), ("Agrave", 0x00C0), ("Aacute", 0x00C1), - ("Acirc", 0x00C2), ("Atilde", 0x00C3), ("Auml", 0x00C4), ("Aring", 0x00C5), - ("AElig", 0x00C6), ("Ccedil", 0x00C7), ("Egrave", 0x00C8), - ("Eacute", 0x00C9), ("Ecirc", 0x00CA), ("Euml", 0x00CB), ("Igrave", 0x00CC), - ("Iacute", 0x00CD), ("Icirc", 0x00CE), ("Iuml", 0x00CF), ("ETH", 0x00D0), - ("Ntilde", 0x00D1), ("Ograve", 0x00D2), ("Oacute", 0x00D3), - ("Ocirc", 0x00D4), ("Otilde", 0x00D5), ("Ouml", 0x00D6), ("times", 0x00D7), - ("Oslash", 0x00D8), ("Ugrave", 0x00D9), ("Uacute", 0x00DA), - ("Ucirc", 0x00DB), ("Uuml", 0x00DC), ("Yacute", 0x00DD), ("THORN", 0x00DE), - ("szlig", 0x00DF), ("agrave", 0x00E0), ("aacute", 0x00E1), - ("acirc", 0x00E2), ("atilde", 0x00E3), ("auml", 0x00E4), ("aring", 0x00E5), - ("aelig", 0x00E6), ("ccedil", 0x00E7), ("egrave", 0x00E8), - ("eacute", 0x00E9), ("ecirc", 0x00EA), ("euml", 0x00EB), ("igrave", 0x00EC), - ("iacute", 0x00ED), ("icirc", 0x00EE), ("iuml", 0x00EF), ("eth", 0x00F0), - ("ntilde", 0x00F1), ("ograve", 0x00F2), ("oacute", 0x00F3), - ("ocirc", 0x00F4), ("otilde", 0x00F5), ("ouml", 0x00F6), ("divide", 0x00F7), - ("oslash", 0x00F8), ("ugrave", 0x00F9), ("uacute", 0x00FA), - ("ucirc", 0x00FB), ("uuml", 0x00FC), ("yacute", 0x00FD), ("thorn", 0x00FE), - ("yuml", 0x00FF), ("OElig", 0x0152), ("oelig", 0x0153), ("Scaron", 0x0160), - ("scaron", 0x0161), ("Yuml", 0x0178), ("fnof", 0x0192), ("circ", 0x02C6), - ("tilde", 0x02DC), ("Alpha", 0x0391), ("Beta", 0x0392), ("Gamma", 0x0393), - ("Delta", 0x0394), ("Epsilon", 0x0395), ("Zeta", 0x0396), ("Eta", 0x0397), - ("Theta", 0x0398), ("Iota", 0x0399), ("Kappa", 0x039A), ("Lambda", 0x039B), - ("Mu", 0x039C), ("Nu", 0x039D), ("Xi", 0x039E), ("Omicron", 0x039F), - ("Pi", 0x03A0), ("Rho", 0x03A1), ("Sigma", 0x03A3), ("Tau", 0x03A4), - ("Upsilon", 0x03A5), ("Phi", 0x03A6), ("Chi", 0x03A7), ("Psi", 0x03A8), - ("Omega", 0x03A9), ("alpha", 0x03B1), ("beta", 0x03B2), ("gamma", 0x03B3), - ("delta", 0x03B4), ("epsilon", 0x03B5), ("zeta", 0x03B6), ("eta", 0x03B7), - ("theta", 0x03B8), ("iota", 0x03B9), ("kappa", 0x03BA), ("lambda", 0x03BB), - ("mu", 0x03BC), ("nu", 0x03BD), ("xi", 0x03BE), ("omicron", 0x03BF), - ("pi", 0x03C0), ("rho", 0x03C1), ("sigmaf", 0x03C2), ("sigma", 0x03C3), - ("tau", 0x03C4), ("upsilon", 0x03C5), ("phi", 0x03C6), ("chi", 0x03C7), - ("psi", 0x03C8), ("omega", 0x03C9), ("thetasym", 0x03D1), ("upsih", 0x03D2), - ("piv", 0x03D6), ("ensp", 0x2002), ("emsp", 0x2003), ("thinsp", 0x2009), - ("zwnj", 0x200C), ("zwj", 0x200D), ("lrm", 0x200E), ("rlm", 0x200F), - ("ndash", 0x2013), ("mdash", 0x2014), ("lsquo", 0x2018), ("rsquo", 0x2019), - ("sbquo", 0x201A), ("ldquo", 0x201C), ("rdquo", 0x201D), ("bdquo", 0x201E), - ("dagger", 0x2020), ("Dagger", 0x2021), ("bull", 0x2022), - ("hellip", 0x2026), ("permil", 0x2030), ("prime", 0x2032), - ("Prime", 0x2033), ("lsaquo", 0x2039), ("rsaquo", 0x203A), - ("oline", 0x203E), ("frasl", 0x2044), ("euro", 0x20AC), - ("image", 0x2111), ("weierp", 0x2118), ("real", 0x211C), - ("trade", 0x2122), ("alefsym", 0x2135), ("larr", 0x2190), - ("uarr", 0x2191), ("rarr", 0x2192), ("darr", 0x2193), - ("harr", 0x2194), ("crarr", 0x21B5), ("lArr", 0x21D0), - ("uArr", 0x21D1), ("rArr", 0x21D2), ("dArr", 0x21D3), - ("hArr", 0x21D4), ("forall", 0x2200), ("part", 0x2202), - ("exist", 0x2203), ("empty", 0x2205), ("nabla", 0x2207), - ("isin", 0x2208), ("notin", 0x2209), ("ni", 0x220B), - ("prod", 0x220F), ("sum", 0x2211), ("minus", 0x2212), - ("lowast", 0x2217), ("radic", 0x221A), ("prop", 0x221D), - ("infin", 0x221E), ("ang", 0x2220), ("and", 0x2227), - ("or", 0x2228), ("cap", 0x2229), ("cup", 0x222A), - ("int", 0x222B), ("there4", 0x2234), ("sim", 0x223C), - ("cong", 0x2245), ("asymp", 0x2248), ("ne", 0x2260), - ("equiv", 0x2261), ("le", 0x2264), ("ge", 0x2265), - ("sub", 0x2282), ("sup", 0x2283), ("nsub", 0x2284), - ("sube", 0x2286), ("supe", 0x2287), ("oplus", 0x2295), - ("otimes", 0x2297), ("perp", 0x22A5), ("sdot", 0x22C5), - ("lceil", 0x2308), ("rceil", 0x2309), ("lfloor", 0x230A), - ("rfloor", 0x230B), ("lang", 0x2329), ("rang", 0x232A), - ("loz", 0x25CA), ("spades", 0x2660), ("clubs", 0x2663), - ("hearts", 0x2665), ("diams", 0x2666)] - proc allLower(s: string): bool = for c in s: if c < 'a' or c > 'z': return false @@ -436,18 +362,1538 @@ proc htmlTag*(s: string): HtmlTag = let s = if allLower(s): s else: toLowerAscii(s) result = toHtmlTag(s) +proc runeToEntity*(rune: Rune): string = + ## converts a Rune to its numeric HTML entity equivalent. + runnableExamples: + import unicode + doAssert runeToEntity(Rune(0)) == "" + doAssert runeToEntity(Rune(-1)) == "" + doAssert runeToEntity("Ü".runeAt(0)) == "#220" + doAssert runeToEntity("∈".runeAt(0)) == "#8712" + if rune.ord <= 0: result = "" + else: result = '#' & $rune.ord + +proc entityToRune*(entity: string): Rune = + ## Converts an HTML entity name like ``Ü`` or values like ``Ü`` + ## or ``Ü`` to its UTF-8 equivalent. + ## Rune(0) is returned if the entity name is unknown. + runnableExamples: + import unicode + doAssert entityToRune("") == Rune(0) + doAssert entityToRune("a") == Rune(0) + doAssert entityToRune("gt") == ">".runeAt(0) + doAssert entityToRune("Uuml") == "Ü".runeAt(0) + doAssert entityToRune("quest") == "?".runeAt(0) + doAssert entityToRune("#x0003F") == "?".runeAt(0) + if entity.len < 2: return # smallest entity has length 2 + if entity[0] == '#': + var runeValue = 0 + case entity[1] + of '0'..'9': + try: runeValue = parseInt(entity[1..^1]) + except: discard + of 'x', 'X': # not case sensitive here + try: runeValue = parseHexInt(entity[2..^1]) + except: discard + else: discard # other entities are not defined with prefix ``#`` + if runeValue notin 0..0x10FFFF: runeValue = 0 # only return legal values + return Rune(runeValue) + case entity # entity names are case sensitive + of "Tab": Rune(0x00009) + of "NewLine": Rune(0x0000A) + of "excl": Rune(0x00021) + of "quot", "QUOT": Rune(0x00022) + of "num": Rune(0x00023) + of "dollar": Rune(0x00024) + of "percnt": Rune(0x00025) + of "amp", "AMP": Rune(0x00026) + of "apos": Rune(0x00027) + of "lpar": Rune(0x00028) + of "rpar": Rune(0x00029) + of "ast", "midast": Rune(0x0002A) + of "plus": Rune(0x0002B) + of "comma": Rune(0x0002C) + of "period": Rune(0x0002E) + of "sol": Rune(0x0002F) + of "colon": Rune(0x0003A) + of "semi": Rune(0x0003B) + of "lt", "LT": Rune(0x0003C) + of "equals": Rune(0x0003D) + of "gt", "GT": Rune(0x0003E) + of "quest": Rune(0x0003F) + of "commat": Rune(0x00040) + of "lsqb", "lbrack": Rune(0x0005B) + of "bsol": Rune(0x0005C) + of "rsqb", "rbrack": Rune(0x0005D) + of "Hat": Rune(0x0005E) + of "lowbar": Rune(0x0005F) + of "grave", "DiacriticalGrave": Rune(0x00060) + of "lcub", "lbrace": Rune(0x0007B) + of "verbar", "vert", "VerticalLine": Rune(0x0007C) + of "rcub", "rbrace": Rune(0x0007D) + of "nbsp", "NonBreakingSpace": Rune(0x000A0) + of "iexcl": Rune(0x000A1) + of "cent": Rune(0x000A2) + of "pound": Rune(0x000A3) + of "curren": Rune(0x000A4) + of "yen": Rune(0x000A5) + of "brvbar": Rune(0x000A6) + of "sect": Rune(0x000A7) + of "Dot", "die", "DoubleDot", "uml": Rune(0x000A8) + of "copy", "COPY": Rune(0x000A9) + of "ordf": Rune(0x000AA) + of "laquo": Rune(0x000AB) + of "not": Rune(0x000AC) + of "shy": Rune(0x000AD) + of "reg", "circledR", "REG": Rune(0x000AE) + of "macr", "OverBar", "strns": Rune(0x000AF) + of "deg": Rune(0x000B0) + of "plusmn", "pm", "PlusMinus": Rune(0x000B1) + of "sup2": Rune(0x000B2) + of "sup3": Rune(0x000B3) + of "acute", "DiacriticalAcute": Rune(0x000B4) + of "micro": Rune(0x000B5) + of "para": Rune(0x000B6) + of "middot", "centerdot", "CenterDot": Rune(0x000B7) + of "cedil", "Cedilla": Rune(0x000B8) + of "sup1": Rune(0x000B9) + of "ordm": Rune(0x000BA) + of "raquo": Rune(0x000BB) + of "frac14": Rune(0x000BC) + of "frac12", "half": Rune(0x000BD) + of "frac34": Rune(0x000BE) + of "iquest": Rune(0x000BF) + of "Agrave": Rune(0x000C0) + of "Aacute": Rune(0x000C1) + of "Acirc": Rune(0x000C2) + of "Atilde": Rune(0x000C3) + of "Auml": Rune(0x000C4) + of "Aring": Rune(0x000C5) + of "AElig": Rune(0x000C6) + of "Ccedil": Rune(0x000C7) + of "Egrave": Rune(0x000C8) + of "Eacute": Rune(0x000C9) + of "Ecirc": Rune(0x000CA) + of "Euml": Rune(0x000CB) + of "Igrave": Rune(0x000CC) + of "Iacute": Rune(0x000CD) + of "Icirc": Rune(0x000CE) + of "Iuml": Rune(0x000CF) + of "ETH": Rune(0x000D0) + of "Ntilde": Rune(0x000D1) + of "Ograve": Rune(0x000D2) + of "Oacute": Rune(0x000D3) + of "Ocirc": Rune(0x000D4) + of "Otilde": Rune(0x000D5) + of "Ouml": Rune(0x000D6) + of "times": Rune(0x000D7) + of "Oslash": Rune(0x000D8) + of "Ugrave": Rune(0x000D9) + of "Uacute": Rune(0x000DA) + of "Ucirc": Rune(0x000DB) + of "Uuml": Rune(0x000DC) + of "Yacute": Rune(0x000DD) + of "THORN": Rune(0x000DE) + of "szlig": Rune(0x000DF) + of "agrave": Rune(0x000E0) + of "aacute": Rune(0x000E1) + of "acirc": Rune(0x000E2) + of "atilde": Rune(0x000E3) + of "auml": Rune(0x000E4) + of "aring": Rune(0x000E5) + of "aelig": Rune(0x000E6) + of "ccedil": Rune(0x000E7) + of "egrave": Rune(0x000E8) + of "eacute": Rune(0x000E9) + of "ecirc": Rune(0x000EA) + of "euml": Rune(0x000EB) + of "igrave": Rune(0x000EC) + of "iacute": Rune(0x000ED) + of "icirc": Rune(0x000EE) + of "iuml": Rune(0x000EF) + of "eth": Rune(0x000F0) + of "ntilde": Rune(0x000F1) + of "ograve": Rune(0x000F2) + of "oacute": Rune(0x000F3) + of "ocirc": Rune(0x000F4) + of "otilde": Rune(0x000F5) + of "ouml": Rune(0x000F6) + of "divide", "div": Rune(0x000F7) + of "oslash": Rune(0x000F8) + of "ugrave": Rune(0x000F9) + of "uacute": Rune(0x000FA) + of "ucirc": Rune(0x000FB) + of "uuml": Rune(0x000FC) + of "yacute": Rune(0x000FD) + of "thorn": Rune(0x000FE) + of "yuml": Rune(0x000FF) + of "Amacr": Rune(0x00100) + of "amacr": Rune(0x00101) + of "Abreve": Rune(0x00102) + of "abreve": Rune(0x00103) + of "Aogon": Rune(0x00104) + of "aogon": Rune(0x00105) + of "Cacute": Rune(0x00106) + of "cacute": Rune(0x00107) + of "Ccirc": Rune(0x00108) + of "ccirc": Rune(0x00109) + of "Cdot": Rune(0x0010A) + of "cdot": Rune(0x0010B) + of "Ccaron": Rune(0x0010C) + of "ccaron": Rune(0x0010D) + of "Dcaron": Rune(0x0010E) + of "dcaron": Rune(0x0010F) + of "Dstrok": Rune(0x00110) + of "dstrok": Rune(0x00111) + of "Emacr": Rune(0x00112) + of "emacr": Rune(0x00113) + of "Edot": Rune(0x00116) + of "edot": Rune(0x00117) + of "Eogon": Rune(0x00118) + of "eogon": Rune(0x00119) + of "Ecaron": Rune(0x0011A) + of "ecaron": Rune(0x0011B) + of "Gcirc": Rune(0x0011C) + of "gcirc": Rune(0x0011D) + of "Gbreve": Rune(0x0011E) + of "gbreve": Rune(0x0011F) + of "Gdot": Rune(0x00120) + of "gdot": Rune(0x00121) + of "Gcedil": Rune(0x00122) + of "Hcirc": Rune(0x00124) + of "hcirc": Rune(0x00125) + of "Hstrok": Rune(0x00126) + of "hstrok": Rune(0x00127) + of "Itilde": Rune(0x00128) + of "itilde": Rune(0x00129) + of "Imacr": Rune(0x0012A) + of "imacr": Rune(0x0012B) + of "Iogon": Rune(0x0012E) + of "iogon": Rune(0x0012F) + of "Idot": Rune(0x00130) + of "imath", "inodot": Rune(0x00131) + of "IJlig": Rune(0x00132) + of "ijlig": Rune(0x00133) + of "Jcirc": Rune(0x00134) + of "jcirc": Rune(0x00135) + of "Kcedil": Rune(0x00136) + of "kcedil": Rune(0x00137) + of "kgreen": Rune(0x00138) + of "Lacute": Rune(0x00139) + of "lacute": Rune(0x0013A) + of "Lcedil": Rune(0x0013B) + of "lcedil": Rune(0x0013C) + of "Lcaron": Rune(0x0013D) + of "lcaron": Rune(0x0013E) + of "Lmidot": Rune(0x0013F) + of "lmidot": Rune(0x00140) + of "Lstrok": Rune(0x00141) + of "lstrok": Rune(0x00142) + of "Nacute": Rune(0x00143) + of "nacute": Rune(0x00144) + of "Ncedil": Rune(0x00145) + of "ncedil": Rune(0x00146) + of "Ncaron": Rune(0x00147) + of "ncaron": Rune(0x00148) + of "napos": Rune(0x00149) + of "ENG": Rune(0x0014A) + of "eng": Rune(0x0014B) + of "Omacr": Rune(0x0014C) + of "omacr": Rune(0x0014D) + of "Odblac": Rune(0x00150) + of "odblac": Rune(0x00151) + of "OElig": Rune(0x00152) + of "oelig": Rune(0x00153) + of "Racute": Rune(0x00154) + of "racute": Rune(0x00155) + of "Rcedil": Rune(0x00156) + of "rcedil": Rune(0x00157) + of "Rcaron": Rune(0x00158) + of "rcaron": Rune(0x00159) + of "Sacute": Rune(0x0015A) + of "sacute": Rune(0x0015B) + of "Scirc": Rune(0x0015C) + of "scirc": Rune(0x0015D) + of "Scedil": Rune(0x0015E) + of "scedil": Rune(0x0015F) + of "Scaron": Rune(0x00160) + of "scaron": Rune(0x00161) + of "Tcedil": Rune(0x00162) + of "tcedil": Rune(0x00163) + of "Tcaron": Rune(0x00164) + of "tcaron": Rune(0x00165) + of "Tstrok": Rune(0x00166) + of "tstrok": Rune(0x00167) + of "Utilde": Rune(0x00168) + of "utilde": Rune(0x00169) + of "Umacr": Rune(0x0016A) + of "umacr": Rune(0x0016B) + of "Ubreve": Rune(0x0016C) + of "ubreve": Rune(0x0016D) + of "Uring": Rune(0x0016E) + of "uring": Rune(0x0016F) + of "Udblac": Rune(0x00170) + of "udblac": Rune(0x00171) + of "Uogon": Rune(0x00172) + of "uogon": Rune(0x00173) + of "Wcirc": Rune(0x00174) + of "wcirc": Rune(0x00175) + of "Ycirc": Rune(0x00176) + of "ycirc": Rune(0x00177) + of "Yuml": Rune(0x00178) + of "Zacute": Rune(0x00179) + of "zacute": Rune(0x0017A) + of "Zdot": Rune(0x0017B) + of "zdot": Rune(0x0017C) + of "Zcaron": Rune(0x0017D) + of "zcaron": Rune(0x0017E) + of "fnof": Rune(0x00192) + of "imped": Rune(0x001B5) + of "gacute": Rune(0x001F5) + of "jmath": Rune(0x00237) + of "circ": Rune(0x002C6) + of "caron", "Hacek": Rune(0x002C7) + of "breve", "Breve": Rune(0x002D8) + of "dot", "DiacriticalDot": Rune(0x002D9) + of "ring": Rune(0x002DA) + of "ogon": Rune(0x002DB) + of "tilde", "DiacriticalTilde": Rune(0x002DC) + of "dblac", "DiacriticalDoubleAcute": Rune(0x002DD) + of "DownBreve": Rune(0x00311) + of "UnderBar": Rune(0x00332) + of "Alpha": Rune(0x00391) + of "Beta": Rune(0x00392) + of "Gamma": Rune(0x00393) + of "Delta": Rune(0x00394) + of "Epsilon": Rune(0x00395) + of "Zeta": Rune(0x00396) + of "Eta": Rune(0x00397) + of "Theta": Rune(0x00398) + of "Iota": Rune(0x00399) + of "Kappa": Rune(0x0039A) + of "Lambda": Rune(0x0039B) + of "Mu": Rune(0x0039C) + of "Nu": Rune(0x0039D) + of "Xi": Rune(0x0039E) + of "Omicron": Rune(0x0039F) + of "Pi": Rune(0x003A0) + of "Rho": Rune(0x003A1) + of "Sigma": Rune(0x003A3) + of "Tau": Rune(0x003A4) + of "Upsilon": Rune(0x003A5) + of "Phi": Rune(0x003A6) + of "Chi": Rune(0x003A7) + of "Psi": Rune(0x003A8) + of "Omega": Rune(0x003A9) + of "alpha": Rune(0x003B1) + of "beta": Rune(0x003B2) + of "gamma": Rune(0x003B3) + of "delta": Rune(0x003B4) + of "epsiv", "varepsilon", "epsilon": Rune(0x003B5) + of "zeta": Rune(0x003B6) + of "eta": Rune(0x003B7) + of "theta": Rune(0x003B8) + of "iota": Rune(0x003B9) + of "kappa": Rune(0x003BA) + of "lambda": Rune(0x003BB) + of "mu": Rune(0x003BC) + of "nu": Rune(0x003BD) + of "xi": Rune(0x003BE) + of "omicron": Rune(0x003BF) + of "pi": Rune(0x003C0) + of "rho": Rune(0x003C1) + of "sigmav", "varsigma", "sigmaf": Rune(0x003C2) + of "sigma": Rune(0x003C3) + of "tau": Rune(0x003C4) + of "upsi", "upsilon": Rune(0x003C5) + of "phi", "phiv", "varphi": Rune(0x003C6) + of "chi": Rune(0x003C7) + of "psi": Rune(0x003C8) + of "omega": Rune(0x003C9) + of "thetav", "vartheta", "thetasym": Rune(0x003D1) + of "Upsi", "upsih": Rune(0x003D2) + of "straightphi": Rune(0x003D5) + of "piv", "varpi": Rune(0x003D6) + of "Gammad": Rune(0x003DC) + of "gammad", "digamma": Rune(0x003DD) + of "kappav", "varkappa": Rune(0x003F0) + of "rhov", "varrho": Rune(0x003F1) + of "epsi", "straightepsilon": Rune(0x003F5) + of "bepsi", "backepsilon": Rune(0x003F6) + of "IOcy": Rune(0x00401) + of "DJcy": Rune(0x00402) + of "GJcy": Rune(0x00403) + of "Jukcy": Rune(0x00404) + of "DScy": Rune(0x00405) + of "Iukcy": Rune(0x00406) + of "YIcy": Rune(0x00407) + of "Jsercy": Rune(0x00408) + of "LJcy": Rune(0x00409) + of "NJcy": Rune(0x0040A) + of "TSHcy": Rune(0x0040B) + of "KJcy": Rune(0x0040C) + of "Ubrcy": Rune(0x0040E) + of "DZcy": Rune(0x0040F) + of "Acy": Rune(0x00410) + of "Bcy": Rune(0x00411) + of "Vcy": Rune(0x00412) + of "Gcy": Rune(0x00413) + of "Dcy": Rune(0x00414) + of "IEcy": Rune(0x00415) + of "ZHcy": Rune(0x00416) + of "Zcy": Rune(0x00417) + of "Icy": Rune(0x00418) + of "Jcy": Rune(0x00419) + of "Kcy": Rune(0x0041A) + of "Lcy": Rune(0x0041B) + of "Mcy": Rune(0x0041C) + of "Ncy": Rune(0x0041D) + of "Ocy": Rune(0x0041E) + of "Pcy": Rune(0x0041F) + of "Rcy": Rune(0x00420) + of "Scy": Rune(0x00421) + of "Tcy": Rune(0x00422) + of "Ucy": Rune(0x00423) + of "Fcy": Rune(0x00424) + of "KHcy": Rune(0x00425) + of "TScy": Rune(0x00426) + of "CHcy": Rune(0x00427) + of "SHcy": Rune(0x00428) + of "SHCHcy": Rune(0x00429) + of "HARDcy": Rune(0x0042A) + of "Ycy": Rune(0x0042B) + of "SOFTcy": Rune(0x0042C) + of "Ecy": Rune(0x0042D) + of "YUcy": Rune(0x0042E) + of "YAcy": Rune(0x0042F) + of "acy": Rune(0x00430) + of "bcy": Rune(0x00431) + of "vcy": Rune(0x00432) + of "gcy": Rune(0x00433) + of "dcy": Rune(0x00434) + of "iecy": Rune(0x00435) + of "zhcy": Rune(0x00436) + of "zcy": Rune(0x00437) + of "icy": Rune(0x00438) + of "jcy": Rune(0x00439) + of "kcy": Rune(0x0043A) + of "lcy": Rune(0x0043B) + of "mcy": Rune(0x0043C) + of "ncy": Rune(0x0043D) + of "ocy": Rune(0x0043E) + of "pcy": Rune(0x0043F) + of "rcy": Rune(0x00440) + of "scy": Rune(0x00441) + of "tcy": Rune(0x00442) + of "ucy": Rune(0x00443) + of "fcy": Rune(0x00444) + of "khcy": Rune(0x00445) + of "tscy": Rune(0x00446) + of "chcy": Rune(0x00447) + of "shcy": Rune(0x00448) + of "shchcy": Rune(0x00449) + of "hardcy": Rune(0x0044A) + of "ycy": Rune(0x0044B) + of "softcy": Rune(0x0044C) + of "ecy": Rune(0x0044D) + of "yucy": Rune(0x0044E) + of "yacy": Rune(0x0044F) + of "iocy": Rune(0x00451) + of "djcy": Rune(0x00452) + of "gjcy": Rune(0x00453) + of "jukcy": Rune(0x00454) + of "dscy": Rune(0x00455) + of "iukcy": Rune(0x00456) + of "yicy": Rune(0x00457) + of "jsercy": Rune(0x00458) + of "ljcy": Rune(0x00459) + of "njcy": Rune(0x0045A) + of "tshcy": Rune(0x0045B) + of "kjcy": Rune(0x0045C) + of "ubrcy": Rune(0x0045E) + of "dzcy": Rune(0x0045F) + of "ensp": Rune(0x02002) + of "emsp": Rune(0x02003) + of "emsp13": Rune(0x02004) + of "emsp14": Rune(0x02005) + of "numsp": Rune(0x02007) + of "puncsp": Rune(0x02008) + of "thinsp", "ThinSpace": Rune(0x02009) + of "hairsp", "VeryThinSpace": Rune(0x0200A) + of "ZeroWidthSpace", "NegativeVeryThinSpace", "NegativeThinSpace", + "NegativeMediumSpace", "NegativeThickSpace": Rune(0x0200B) + of "zwnj": Rune(0x0200C) + of "zwj": Rune(0x0200D) + of "lrm": Rune(0x0200E) + of "rlm": Rune(0x0200F) + of "hyphen", "dash": Rune(0x02010) + of "ndash": Rune(0x02013) + of "mdash": Rune(0x02014) + of "horbar": Rune(0x02015) + of "Verbar", "Vert": Rune(0x02016) + of "lsquo", "OpenCurlyQuote": Rune(0x02018) + of "rsquo", "rsquor", "CloseCurlyQuote": Rune(0x02019) + of "lsquor", "sbquo": Rune(0x0201A) + of "ldquo", "OpenCurlyDoubleQuote": Rune(0x0201C) + of "rdquo", "rdquor", "CloseCurlyDoubleQuote": Rune(0x0201D) + of "ldquor", "bdquo": Rune(0x0201E) + of "dagger": Rune(0x02020) + of "Dagger", "ddagger": Rune(0x02021) + of "bull", "bullet": Rune(0x02022) + of "nldr": Rune(0x02025) + of "hellip", "mldr": Rune(0x02026) + of "permil": Rune(0x02030) + of "pertenk": Rune(0x02031) + of "prime": Rune(0x02032) + of "Prime": Rune(0x02033) + of "tprime": Rune(0x02034) + of "bprime", "backprime": Rune(0x02035) + of "lsaquo": Rune(0x02039) + of "rsaquo": Rune(0x0203A) + of "oline": Rune(0x0203E) + of "caret": Rune(0x02041) + of "hybull": Rune(0x02043) + of "frasl": Rune(0x02044) + of "bsemi": Rune(0x0204F) + of "qprime": Rune(0x02057) + of "MediumSpace": Rune(0x0205F) + of "NoBreak": Rune(0x02060) + of "ApplyFunction", "af": Rune(0x02061) + of "InvisibleTimes", "it": Rune(0x02062) + of "InvisibleComma", "ic": Rune(0x02063) + of "euro": Rune(0x020AC) + of "tdot", "TripleDot": Rune(0x020DB) + of "DotDot": Rune(0x020DC) + of "Copf", "complexes": Rune(0x02102) + of "incare": Rune(0x02105) + of "gscr": Rune(0x0210A) + of "hamilt", "HilbertSpace", "Hscr": Rune(0x0210B) + of "Hfr", "Poincareplane": Rune(0x0210C) + of "quaternions", "Hopf": Rune(0x0210D) + of "planckh": Rune(0x0210E) + of "planck", "hbar", "plankv", "hslash": Rune(0x0210F) + of "Iscr", "imagline": Rune(0x02110) + of "image", "Im", "imagpart", "Ifr": Rune(0x02111) + of "Lscr", "lagran", "Laplacetrf": Rune(0x02112) + of "ell": Rune(0x02113) + of "Nopf", "naturals": Rune(0x02115) + of "numero": Rune(0x02116) + of "copysr": Rune(0x02117) + of "weierp", "wp": Rune(0x02118) + of "Popf", "primes": Rune(0x02119) + of "rationals", "Qopf": Rune(0x0211A) + of "Rscr", "realine": Rune(0x0211B) + of "real", "Re", "realpart", "Rfr": Rune(0x0211C) + of "reals", "Ropf": Rune(0x0211D) + of "rx": Rune(0x0211E) + of "trade", "TRADE": Rune(0x02122) + of "integers", "Zopf": Rune(0x02124) + of "ohm": Rune(0x02126) + of "mho": Rune(0x02127) + of "Zfr", "zeetrf": Rune(0x02128) + of "iiota": Rune(0x02129) + of "angst": Rune(0x0212B) + of "bernou", "Bernoullis", "Bscr": Rune(0x0212C) + of "Cfr", "Cayleys": Rune(0x0212D) + of "escr": Rune(0x0212F) + of "Escr", "expectation": Rune(0x02130) + of "Fscr", "Fouriertrf": Rune(0x02131) + of "phmmat", "Mellintrf", "Mscr": Rune(0x02133) + of "order", "orderof", "oscr": Rune(0x02134) + of "alefsym", "aleph": Rune(0x02135) + of "beth": Rune(0x02136) + of "gimel": Rune(0x02137) + of "daleth": Rune(0x02138) + of "CapitalDifferentialD", "DD": Rune(0x02145) + of "DifferentialD", "dd": Rune(0x02146) + of "ExponentialE", "exponentiale", "ee": Rune(0x02147) + of "ImaginaryI", "ii": Rune(0x02148) + of "frac13": Rune(0x02153) + of "frac23": Rune(0x02154) + of "frac15": Rune(0x02155) + of "frac25": Rune(0x02156) + of "frac35": Rune(0x02157) + of "frac45": Rune(0x02158) + of "frac16": Rune(0x02159) + of "frac56": Rune(0x0215A) + of "frac18": Rune(0x0215B) + of "frac38": Rune(0x0215C) + of "frac58": Rune(0x0215D) + of "frac78": Rune(0x0215E) + of "larr", "leftarrow", "LeftArrow", "slarr", + "ShortLeftArrow": Rune(0x02190) + of "uarr", "uparrow", "UpArrow", "ShortUpArrow": Rune(0x02191) + of "rarr", "rightarrow", "RightArrow", "srarr", + "ShortRightArrow": Rune(0x02192) + of "darr", "downarrow", "DownArrow", + "ShortDownArrow": Rune(0x02193) + of "harr", "leftrightarrow", "LeftRightArrow": Rune(0x02194) + of "varr", "updownarrow", "UpDownArrow": Rune(0x02195) + of "nwarr", "UpperLeftArrow", "nwarrow": Rune(0x02196) + of "nearr", "UpperRightArrow", "nearrow": Rune(0x02197) + of "searr", "searrow", "LowerRightArrow": Rune(0x02198) + of "swarr", "swarrow", "LowerLeftArrow": Rune(0x02199) + of "nlarr", "nleftarrow": Rune(0x0219A) + of "nrarr", "nrightarrow": Rune(0x0219B) + of "rarrw", "rightsquigarrow": Rune(0x0219D) + of "Larr", "twoheadleftarrow": Rune(0x0219E) + of "Uarr": Rune(0x0219F) + of "Rarr", "twoheadrightarrow": Rune(0x021A0) + of "Darr": Rune(0x021A1) + of "larrtl", "leftarrowtail": Rune(0x021A2) + of "rarrtl", "rightarrowtail": Rune(0x021A3) + of "LeftTeeArrow", "mapstoleft": Rune(0x021A4) + of "UpTeeArrow", "mapstoup": Rune(0x021A5) + of "map", "RightTeeArrow", "mapsto": Rune(0x021A6) + of "DownTeeArrow", "mapstodown": Rune(0x021A7) + of "larrhk", "hookleftarrow": Rune(0x021A9) + of "rarrhk", "hookrightarrow": Rune(0x021AA) + of "larrlp", "looparrowleft": Rune(0x021AB) + of "rarrlp", "looparrowright": Rune(0x021AC) + of "harrw", "leftrightsquigarrow": Rune(0x021AD) + of "nharr", "nleftrightarrow": Rune(0x021AE) + of "lsh", "Lsh": Rune(0x021B0) + of "rsh", "Rsh": Rune(0x021B1) + of "ldsh": Rune(0x021B2) + of "rdsh": Rune(0x021B3) + of "crarr": Rune(0x021B5) + of "cularr", "curvearrowleft": Rune(0x021B6) + of "curarr", "curvearrowright": Rune(0x021B7) + of "olarr", "circlearrowleft": Rune(0x021BA) + of "orarr", "circlearrowright": Rune(0x021BB) + of "lharu", "LeftVector", "leftharpoonup": Rune(0x021BC) + of "lhard", "leftharpoondown", "DownLeftVector": Rune(0x021BD) + of "uharr", "upharpoonright", "RightUpVector": Rune(0x021BE) + of "uharl", "upharpoonleft", "LeftUpVector": Rune(0x021BF) + of "rharu", "RightVector", "rightharpoonup": Rune(0x021C0) + of "rhard", "rightharpoondown", "DownRightVector": Rune(0x021C1) + of "dharr", "RightDownVector", "downharpoonright": Rune(0x021C2) + of "dharl", "LeftDownVector", "downharpoonleft": Rune(0x021C3) + of "rlarr", "rightleftarrows", "RightArrowLeftArrow": Rune(0x021C4) + of "udarr", "UpArrowDownArrow": Rune(0x021C5) + of "lrarr", "leftrightarrows", "LeftArrowRightArrow": Rune(0x021C6) + of "llarr", "leftleftarrows": Rune(0x021C7) + of "uuarr", "upuparrows": Rune(0x021C8) + of "rrarr", "rightrightarrows": Rune(0x021C9) + of "ddarr", "downdownarrows": Rune(0x021CA) + of "lrhar", "ReverseEquilibrium", + "leftrightharpoons": Rune(0x021CB) + of "rlhar", "rightleftharpoons", "Equilibrium": Rune(0x021CC) + of "nlArr", "nLeftarrow": Rune(0x021CD) + of "nhArr", "nLeftrightarrow": Rune(0x021CE) + of "nrArr", "nRightarrow": Rune(0x021CF) + of "lArr", "Leftarrow", "DoubleLeftArrow": Rune(0x021D0) + of "uArr", "Uparrow", "DoubleUpArrow": Rune(0x021D1) + of "rArr", "Rightarrow", "Implies", + "DoubleRightArrow": Rune(0x021D2) + of "dArr", "Downarrow", "DoubleDownArrow": Rune(0x021D3) + of "hArr", "Leftrightarrow", "DoubleLeftRightArrow", + "iff": Rune(0x021D4) + of "vArr", "Updownarrow", "DoubleUpDownArrow": Rune(0x021D5) + of "nwArr": Rune(0x021D6) + of "neArr": Rune(0x021D7) + of "seArr": Rune(0x021D8) + of "swArr": Rune(0x021D9) + of "lAarr", "Lleftarrow": Rune(0x021DA) + of "rAarr", "Rrightarrow": Rune(0x021DB) + of "zigrarr": Rune(0x021DD) + of "larrb", "LeftArrowBar": Rune(0x021E4) + of "rarrb", "RightArrowBar": Rune(0x021E5) + of "duarr", "DownArrowUpArrow": Rune(0x021F5) + of "loarr": Rune(0x021FD) + of "roarr": Rune(0x021FE) + of "hoarr": Rune(0x021FF) + of "forall", "ForAll": Rune(0x02200) + of "comp", "complement": Rune(0x02201) + of "part", "PartialD": Rune(0x02202) + of "exist", "Exists": Rune(0x02203) + of "nexist", "NotExists", "nexists": Rune(0x02204) + of "empty", "emptyset", "emptyv", "varnothing": Rune(0x02205) + of "nabla", "Del": Rune(0x02207) + of "isin", "isinv", "Element", "in": Rune(0x02208) + of "notin", "NotElement", "notinva": Rune(0x02209) + of "niv", "ReverseElement", "ni", "SuchThat": Rune(0x0220B) + of "notni", "notniva", "NotReverseElement": Rune(0x0220C) + of "prod", "Product": Rune(0x0220F) + of "coprod", "Coproduct": Rune(0x02210) + of "sum", "Sum": Rune(0x02211) + of "minus": Rune(0x02212) + of "mnplus", "mp", "MinusPlus": Rune(0x02213) + of "plusdo", "dotplus": Rune(0x02214) + of "setmn", "setminus", "Backslash", "ssetmn", + "smallsetminus": Rune(0x02216) + of "lowast": Rune(0x02217) + of "compfn", "SmallCircle": Rune(0x02218) + of "radic", "Sqrt": Rune(0x0221A) + of "prop", "propto", "Proportional", "vprop", + "varpropto": Rune(0x0221D) + of "infin": Rune(0x0221E) + of "angrt": Rune(0x0221F) + of "ang", "angle": Rune(0x02220) + of "angmsd", "measuredangle": Rune(0x02221) + of "angsph": Rune(0x02222) + of "mid", "VerticalBar", "smid", "shortmid": Rune(0x02223) + of "nmid", "NotVerticalBar", "nsmid", "nshortmid": Rune(0x02224) + of "par", "parallel", "DoubleVerticalBar", "spar", + "shortparallel": Rune(0x02225) + of "npar", "nparallel", "NotDoubleVerticalBar", "nspar", + "nshortparallel": Rune(0x02226) + of "and", "wedge": Rune(0x02227) + of "or", "vee": Rune(0x02228) + of "cap": Rune(0x02229) + of "cup": Rune(0x0222A) + of "int", "Integral": Rune(0x0222B) + of "Int": Rune(0x0222C) + of "tint", "iiint": Rune(0x0222D) + of "conint", "oint", "ContourIntegral": Rune(0x0222E) + of "Conint", "DoubleContourIntegral": Rune(0x0222F) + of "Cconint": Rune(0x02230) + of "cwint": Rune(0x02231) + of "cwconint", "ClockwiseContourIntegral": Rune(0x02232) + of "awconint", "CounterClockwiseContourIntegral": Rune(0x02233) + of "there4", "therefore", "Therefore": Rune(0x02234) + of "becaus", "because", "Because": Rune(0x02235) + of "ratio": Rune(0x02236) + of "Colon", "Proportion": Rune(0x02237) + of "minusd", "dotminus": Rune(0x02238) + of "mDDot": Rune(0x0223A) + of "homtht": Rune(0x0223B) + of "sim", "Tilde", "thksim", "thicksim": Rune(0x0223C) + of "bsim", "backsim": Rune(0x0223D) + of "ac", "mstpos": Rune(0x0223E) + of "acd": Rune(0x0223F) + of "wreath", "VerticalTilde", "wr": Rune(0x02240) + of "nsim", "NotTilde": Rune(0x02241) + of "esim", "EqualTilde", "eqsim": Rune(0x02242) + of "sime", "TildeEqual", "simeq": Rune(0x02243) + of "nsime", "nsimeq", "NotTildeEqual": Rune(0x02244) + of "cong", "TildeFullEqual": Rune(0x02245) + of "simne": Rune(0x02246) + of "ncong", "NotTildeFullEqual": Rune(0x02247) + of "asymp", "ap", "TildeTilde", "approx", "thkap", + "thickapprox": Rune(0x02248) + of "nap", "NotTildeTilde", "napprox": Rune(0x02249) + of "ape", "approxeq": Rune(0x0224A) + of "apid": Rune(0x0224B) + of "bcong", "backcong": Rune(0x0224C) + of "asympeq", "CupCap": Rune(0x0224D) + of "bump", "HumpDownHump", "Bumpeq": Rune(0x0224E) + of "bumpe", "HumpEqual", "bumpeq": Rune(0x0224F) + of "esdot", "DotEqual", "doteq": Rune(0x02250) + of "eDot", "doteqdot": Rune(0x02251) + of "efDot", "fallingdotseq": Rune(0x02252) + of "erDot", "risingdotseq": Rune(0x02253) + of "colone", "coloneq", "Assign": Rune(0x02254) + of "ecolon", "eqcolon": Rune(0x02255) + of "ecir", "eqcirc": Rune(0x02256) + of "cire", "circeq": Rune(0x02257) + of "wedgeq": Rune(0x02259) + of "veeeq": Rune(0x0225A) + of "trie", "triangleq": Rune(0x0225C) + of "equest", "questeq": Rune(0x0225F) + of "ne", "NotEqual": Rune(0x02260) + of "equiv", "Congruent": Rune(0x02261) + of "nequiv", "NotCongruent": Rune(0x02262) + of "le", "leq": Rune(0x02264) + of "ge", "GreaterEqual", "geq": Rune(0x02265) + of "lE", "LessFullEqual", "leqq": Rune(0x02266) + of "gE", "GreaterFullEqual", "geqq": Rune(0x02267) + of "lnE", "lneqq": Rune(0x02268) + of "gnE", "gneqq": Rune(0x02269) + of "Lt", "NestedLessLess", "ll": Rune(0x0226A) + of "Gt", "NestedGreaterGreater", "gg": Rune(0x0226B) + of "twixt", "between": Rune(0x0226C) + of "NotCupCap": Rune(0x0226D) + of "nlt", "NotLess", "nless": Rune(0x0226E) + of "ngt", "NotGreater", "ngtr": Rune(0x0226F) + of "nle", "NotLessEqual", "nleq": Rune(0x02270) + of "nge", "NotGreaterEqual", "ngeq": Rune(0x02271) + of "lsim", "LessTilde", "lesssim": Rune(0x02272) + of "gsim", "gtrsim", "GreaterTilde": Rune(0x02273) + of "nlsim", "NotLessTilde": Rune(0x02274) + of "ngsim", "NotGreaterTilde": Rune(0x02275) + of "lg", "lessgtr", "LessGreater": Rune(0x02276) + of "gl", "gtrless", "GreaterLess": Rune(0x02277) + of "ntlg", "NotLessGreater": Rune(0x02278) + of "ntgl", "NotGreaterLess": Rune(0x02279) + of "pr", "Precedes", "prec": Rune(0x0227A) + of "sc", "Succeeds", "succ": Rune(0x0227B) + of "prcue", "PrecedesSlantEqual", "preccurlyeq": Rune(0x0227C) + of "sccue", "SucceedsSlantEqual", "succcurlyeq": Rune(0x0227D) + of "prsim", "precsim", "PrecedesTilde": Rune(0x0227E) + of "scsim", "succsim", "SucceedsTilde": Rune(0x0227F) + of "npr", "nprec", "NotPrecedes": Rune(0x02280) + of "nsc", "nsucc", "NotSucceeds": Rune(0x02281) + of "sub", "subset": Rune(0x02282) + of "sup", "supset", "Superset": Rune(0x02283) + of "nsub": Rune(0x02284) + of "nsup": Rune(0x02285) + of "sube", "SubsetEqual", "subseteq": Rune(0x02286) + of "supe", "supseteq", "SupersetEqual": Rune(0x02287) + of "nsube", "nsubseteq", "NotSubsetEqual": Rune(0x02288) + of "nsupe", "nsupseteq", "NotSupersetEqual": Rune(0x02289) + of "subne", "subsetneq": Rune(0x0228A) + of "supne", "supsetneq": Rune(0x0228B) + of "cupdot": Rune(0x0228D) + of "uplus", "UnionPlus": Rune(0x0228E) + of "sqsub", "SquareSubset", "sqsubset": Rune(0x0228F) + of "sqsup", "SquareSuperset", "sqsupset": Rune(0x02290) + of "sqsube", "SquareSubsetEqual", "sqsubseteq": Rune(0x02291) + of "sqsupe", "SquareSupersetEqual", "sqsupseteq": Rune(0x02292) + of "sqcap", "SquareIntersection": Rune(0x02293) + of "sqcup", "SquareUnion": Rune(0x02294) + of "oplus", "CirclePlus": Rune(0x02295) + of "ominus", "CircleMinus": Rune(0x02296) + of "otimes", "CircleTimes": Rune(0x02297) + of "osol": Rune(0x02298) + of "odot", "CircleDot": Rune(0x02299) + of "ocir", "circledcirc": Rune(0x0229A) + of "oast", "circledast": Rune(0x0229B) + of "odash", "circleddash": Rune(0x0229D) + of "plusb", "boxplus": Rune(0x0229E) + of "minusb", "boxminus": Rune(0x0229F) + of "timesb", "boxtimes": Rune(0x022A0) + of "sdotb", "dotsquare": Rune(0x022A1) + of "vdash", "RightTee": Rune(0x022A2) + of "dashv", "LeftTee": Rune(0x022A3) + of "top", "DownTee": Rune(0x022A4) + of "bottom", "bot", "perp", "UpTee": Rune(0x022A5) + of "models": Rune(0x022A7) + of "vDash", "DoubleRightTee": Rune(0x022A8) + of "Vdash": Rune(0x022A9) + of "Vvdash": Rune(0x022AA) + of "VDash": Rune(0x022AB) + of "nvdash": Rune(0x022AC) + of "nvDash": Rune(0x022AD) + of "nVdash": Rune(0x022AE) + of "nVDash": Rune(0x022AF) + of "prurel": Rune(0x022B0) + of "vltri", "vartriangleleft", "LeftTriangle": Rune(0x022B2) + of "vrtri", "vartriangleright", "RightTriangle": Rune(0x022B3) + of "ltrie", "trianglelefteq", "LeftTriangleEqual": Rune(0x022B4) + of "rtrie", "trianglerighteq", "RightTriangleEqual": Rune(0x022B5) + of "origof": Rune(0x022B6) + of "imof": Rune(0x022B7) + of "mumap", "multimap": Rune(0x022B8) + of "hercon": Rune(0x022B9) + of "intcal", "intercal": Rune(0x022BA) + of "veebar": Rune(0x022BB) + of "barvee": Rune(0x022BD) + of "angrtvb": Rune(0x022BE) + of "lrtri": Rune(0x022BF) + of "xwedge", "Wedge", "bigwedge": Rune(0x022C0) + of "xvee", "Vee", "bigvee": Rune(0x022C1) + of "xcap", "Intersection", "bigcap": Rune(0x022C2) + of "xcup", "Union", "bigcup": Rune(0x022C3) + of "diam", "diamond", "Diamond": Rune(0x022C4) + of "sdot": Rune(0x022C5) + of "sstarf", "Star": Rune(0x022C6) + of "divonx", "divideontimes": Rune(0x022C7) + of "bowtie": Rune(0x022C8) + of "ltimes": Rune(0x022C9) + of "rtimes": Rune(0x022CA) + of "lthree", "leftthreetimes": Rune(0x022CB) + of "rthree", "rightthreetimes": Rune(0x022CC) + of "bsime", "backsimeq": Rune(0x022CD) + of "cuvee", "curlyvee": Rune(0x022CE) + of "cuwed", "curlywedge": Rune(0x022CF) + of "Sub", "Subset": Rune(0x022D0) + of "Sup", "Supset": Rune(0x022D1) + of "Cap": Rune(0x022D2) + of "Cup": Rune(0x022D3) + of "fork", "pitchfork": Rune(0x022D4) + of "epar": Rune(0x022D5) + of "ltdot", "lessdot": Rune(0x022D6) + of "gtdot", "gtrdot": Rune(0x022D7) + of "Ll": Rune(0x022D8) + of "Gg", "ggg": Rune(0x022D9) + of "leg", "LessEqualGreater", "lesseqgtr": Rune(0x022DA) + of "gel", "gtreqless", "GreaterEqualLess": Rune(0x022DB) + of "cuepr", "curlyeqprec": Rune(0x022DE) + of "cuesc", "curlyeqsucc": Rune(0x022DF) + of "nprcue", "NotPrecedesSlantEqual": Rune(0x022E0) + of "nsccue", "NotSucceedsSlantEqual": Rune(0x022E1) + of "nsqsube", "NotSquareSubsetEqual": Rune(0x022E2) + of "nsqsupe", "NotSquareSupersetEqual": Rune(0x022E3) + of "lnsim": Rune(0x022E6) + of "gnsim": Rune(0x022E7) + of "prnsim", "precnsim": Rune(0x022E8) + of "scnsim", "succnsim": Rune(0x022E9) + of "nltri", "ntriangleleft", "NotLeftTriangle": Rune(0x022EA) + of "nrtri", "ntriangleright", "NotRightTriangle": Rune(0x022EB) + of "nltrie", "ntrianglelefteq", + "NotLeftTriangleEqual": Rune(0x022EC) + of "nrtrie", "ntrianglerighteq", + "NotRightTriangleEqual": Rune(0x022ED) + of "vellip": Rune(0x022EE) + of "ctdot": Rune(0x022EF) + of "utdot": Rune(0x022F0) + of "dtdot": Rune(0x022F1) + of "disin": Rune(0x022F2) + of "isinsv": Rune(0x022F3) + of "isins": Rune(0x022F4) + of "isindot": Rune(0x022F5) + of "notinvc": Rune(0x022F6) + of "notinvb": Rune(0x022F7) + of "isinE": Rune(0x022F9) + of "nisd": Rune(0x022FA) + of "xnis": Rune(0x022FB) + of "nis": Rune(0x022FC) + of "notnivc": Rune(0x022FD) + of "notnivb": Rune(0x022FE) + of "barwed", "barwedge": Rune(0x02305) + of "Barwed", "doublebarwedge": Rune(0x02306) + of "lceil", "LeftCeiling": Rune(0x02308) + of "rceil", "RightCeiling": Rune(0x02309) + of "lfloor", "LeftFloor": Rune(0x0230A) + of "rfloor", "RightFloor": Rune(0x0230B) + of "drcrop": Rune(0x0230C) + of "dlcrop": Rune(0x0230D) + of "urcrop": Rune(0x0230E) + of "ulcrop": Rune(0x0230F) + of "bnot": Rune(0x02310) + of "profline": Rune(0x02312) + of "profsurf": Rune(0x02313) + of "telrec": Rune(0x02315) + of "target": Rune(0x02316) + of "ulcorn", "ulcorner": Rune(0x0231C) + of "urcorn", "urcorner": Rune(0x0231D) + of "dlcorn", "llcorner": Rune(0x0231E) + of "drcorn", "lrcorner": Rune(0x0231F) + of "frown", "sfrown": Rune(0x02322) + of "smile", "ssmile": Rune(0x02323) + of "cylcty": Rune(0x0232D) + of "profalar": Rune(0x0232E) + of "topbot": Rune(0x02336) + of "ovbar": Rune(0x0233D) + of "solbar": Rune(0x0233F) + of "angzarr": Rune(0x0237C) + of "lmoust", "lmoustache": Rune(0x023B0) + of "rmoust", "rmoustache": Rune(0x023B1) + of "tbrk", "OverBracket": Rune(0x023B4) + of "bbrk", "UnderBracket": Rune(0x023B5) + of "bbrktbrk": Rune(0x023B6) + of "OverParenthesis": Rune(0x023DC) + of "UnderParenthesis": Rune(0x023DD) + of "OverBrace": Rune(0x023DE) + of "UnderBrace": Rune(0x023DF) + of "trpezium": Rune(0x023E2) + of "elinters": Rune(0x023E7) + of "blank": Rune(0x02423) + of "oS", "circledS": Rune(0x024C8) + of "boxh", "HorizontalLine": Rune(0x02500) + of "boxv": Rune(0x02502) + of "boxdr": Rune(0x0250C) + of "boxdl": Rune(0x02510) + of "boxur": Rune(0x02514) + of "boxul": Rune(0x02518) + of "boxvr": Rune(0x0251C) + of "boxvl": Rune(0x02524) + of "boxhd": Rune(0x0252C) + of "boxhu": Rune(0x02534) + of "boxvh": Rune(0x0253C) + of "boxH": Rune(0x02550) + of "boxV": Rune(0x02551) + of "boxdR": Rune(0x02552) + of "boxDr": Rune(0x02553) + of "boxDR": Rune(0x02554) + of "boxdL": Rune(0x02555) + of "boxDl": Rune(0x02556) + of "boxDL": Rune(0x02557) + of "boxuR": Rune(0x02558) + of "boxUr": Rune(0x02559) + of "boxUR": Rune(0x0255A) + of "boxuL": Rune(0x0255B) + of "boxUl": Rune(0x0255C) + of "boxUL": Rune(0x0255D) + of "boxvR": Rune(0x0255E) + of "boxVr": Rune(0x0255F) + of "boxVR": Rune(0x02560) + of "boxvL": Rune(0x02561) + of "boxVl": Rune(0x02562) + of "boxVL": Rune(0x02563) + of "boxHd": Rune(0x02564) + of "boxhD": Rune(0x02565) + of "boxHD": Rune(0x02566) + of "boxHu": Rune(0x02567) + of "boxhU": Rune(0x02568) + of "boxHU": Rune(0x02569) + of "boxvH": Rune(0x0256A) + of "boxVh": Rune(0x0256B) + of "boxVH": Rune(0x0256C) + of "uhblk": Rune(0x02580) + of "lhblk": Rune(0x02584) + of "block": Rune(0x02588) + of "blk14": Rune(0x02591) + of "blk12": Rune(0x02592) + of "blk34": Rune(0x02593) + of "squ", "square", "Square": Rune(0x025A1) + of "squf", "squarf", "blacksquare", + "FilledVerySmallSquare": Rune(0x025AA) + of "EmptyVerySmallSquare": Rune(0x025AB) + of "rect": Rune(0x025AD) + of "marker": Rune(0x025AE) + of "fltns": Rune(0x025B1) + of "xutri", "bigtriangleup": Rune(0x025B3) + of "utrif", "blacktriangle": Rune(0x025B4) + of "utri", "triangle": Rune(0x025B5) + of "rtrif", "blacktriangleright": Rune(0x025B8) + of "rtri", "triangleright": Rune(0x025B9) + of "xdtri", "bigtriangledown": Rune(0x025BD) + of "dtrif", "blacktriangledown": Rune(0x025BE) + of "dtri", "triangledown": Rune(0x025BF) + of "ltrif", "blacktriangleleft": Rune(0x025C2) + of "ltri", "triangleleft": Rune(0x025C3) + of "loz", "lozenge": Rune(0x025CA) + of "cir": Rune(0x025CB) + of "tridot": Rune(0x025EC) + of "xcirc", "bigcirc": Rune(0x025EF) + of "ultri": Rune(0x025F8) + of "urtri": Rune(0x025F9) + of "lltri": Rune(0x025FA) + of "EmptySmallSquare": Rune(0x025FB) + of "FilledSmallSquare": Rune(0x025FC) + of "starf", "bigstar": Rune(0x02605) + of "star": Rune(0x02606) + of "phone": Rune(0x0260E) + of "female": Rune(0x02640) + of "male": Rune(0x02642) + of "spades", "spadesuit": Rune(0x02660) + of "clubs", "clubsuit": Rune(0x02663) + of "hearts", "heartsuit": Rune(0x02665) + of "diams", "diamondsuit": Rune(0x02666) + of "sung": Rune(0x0266A) + of "flat": Rune(0x0266D) + of "natur", "natural": Rune(0x0266E) + of "sharp": Rune(0x0266F) + of "check", "checkmark": Rune(0x02713) + of "cross": Rune(0x02717) + of "malt", "maltese": Rune(0x02720) + of "sext": Rune(0x02736) + of "VerticalSeparator": Rune(0x02758) + of "lbbrk": Rune(0x02772) + of "rbbrk": Rune(0x02773) + of "lobrk", "LeftDoubleBracket": Rune(0x027E6) + of "robrk", "RightDoubleBracket": Rune(0x027E7) + of "lang", "LeftAngleBracket", "langle": Rune(0x027E8) + of "rang", "RightAngleBracket", "rangle": Rune(0x027E9) + of "Lang": Rune(0x027EA) + of "Rang": Rune(0x027EB) + of "loang": Rune(0x027EC) + of "roang": Rune(0x027ED) + of "xlarr", "longleftarrow", "LongLeftArrow": Rune(0x027F5) + of "xrarr", "longrightarrow", "LongRightArrow": Rune(0x027F6) + of "xharr", "longleftrightarrow", + "LongLeftRightArrow": Rune(0x027F7) + of "xlArr", "Longleftarrow", "DoubleLongLeftArrow": Rune(0x027F8) + of "xrArr", "Longrightarrow", "DoubleLongRightArrow": Rune(0x027F9) + of "xhArr", "Longleftrightarrow", + "DoubleLongLeftRightArrow": Rune(0x027FA) + of "xmap", "longmapsto": Rune(0x027FC) + of "dzigrarr": Rune(0x027FF) + of "nvlArr": Rune(0x02902) + of "nvrArr": Rune(0x02903) + of "nvHarr": Rune(0x02904) + of "Map": Rune(0x02905) + of "lbarr": Rune(0x0290C) + of "rbarr", "bkarow": Rune(0x0290D) + of "lBarr": Rune(0x0290E) + of "rBarr", "dbkarow": Rune(0x0290F) + of "RBarr", "drbkarow": Rune(0x02910) + of "DDotrahd": Rune(0x02911) + of "UpArrowBar": Rune(0x02912) + of "DownArrowBar": Rune(0x02913) + of "Rarrtl": Rune(0x02916) + of "latail": Rune(0x02919) + of "ratail": Rune(0x0291A) + of "lAtail": Rune(0x0291B) + of "rAtail": Rune(0x0291C) + of "larrfs": Rune(0x0291D) + of "rarrfs": Rune(0x0291E) + of "larrbfs": Rune(0x0291F) + of "rarrbfs": Rune(0x02920) + of "nwarhk": Rune(0x02923) + of "nearhk": Rune(0x02924) + of "searhk", "hksearow": Rune(0x02925) + of "swarhk", "hkswarow": Rune(0x02926) + of "nwnear": Rune(0x02927) + of "nesear", "toea": Rune(0x02928) + of "seswar", "tosa": Rune(0x02929) + of "swnwar": Rune(0x0292A) + of "rarrc": Rune(0x02933) + of "cudarrr": Rune(0x02935) + of "ldca": Rune(0x02936) + of "rdca": Rune(0x02937) + of "cudarrl": Rune(0x02938) + of "larrpl": Rune(0x02939) + of "curarrm": Rune(0x0293C) + of "cularrp": Rune(0x0293D) + of "rarrpl": Rune(0x02945) + of "harrcir": Rune(0x02948) + of "Uarrocir": Rune(0x02949) + of "lurdshar": Rune(0x0294A) + of "ldrushar": Rune(0x0294B) + of "LeftRightVector": Rune(0x0294E) + of "RightUpDownVector": Rune(0x0294F) + of "DownLeftRightVector": Rune(0x02950) + of "LeftUpDownVector": Rune(0x02951) + of "LeftVectorBar": Rune(0x02952) + of "RightVectorBar": Rune(0x02953) + of "RightUpVectorBar": Rune(0x02954) + of "RightDownVectorBar": Rune(0x02955) + of "DownLeftVectorBar": Rune(0x02956) + of "DownRightVectorBar": Rune(0x02957) + of "LeftUpVectorBar": Rune(0x02958) + of "LeftDownVectorBar": Rune(0x02959) + of "LeftTeeVector": Rune(0x0295A) + of "RightTeeVector": Rune(0x0295B) + of "RightUpTeeVector": Rune(0x0295C) + of "RightDownTeeVector": Rune(0x0295D) + of "DownLeftTeeVector": Rune(0x0295E) + of "DownRightTeeVector": Rune(0x0295F) + of "LeftUpTeeVector": Rune(0x02960) + of "LeftDownTeeVector": Rune(0x02961) + of "lHar": Rune(0x02962) + of "uHar": Rune(0x02963) + of "rHar": Rune(0x02964) + of "dHar": Rune(0x02965) + of "luruhar": Rune(0x02966) + of "ldrdhar": Rune(0x02967) + of "ruluhar": Rune(0x02968) + of "rdldhar": Rune(0x02969) + of "lharul": Rune(0x0296A) + of "llhard": Rune(0x0296B) + of "rharul": Rune(0x0296C) + of "lrhard": Rune(0x0296D) + of "udhar", "UpEquilibrium": Rune(0x0296E) + of "duhar", "ReverseUpEquilibrium": Rune(0x0296F) + of "RoundImplies": Rune(0x02970) + of "erarr": Rune(0x02971) + of "simrarr": Rune(0x02972) + of "larrsim": Rune(0x02973) + of "rarrsim": Rune(0x02974) + of "rarrap": Rune(0x02975) + of "ltlarr": Rune(0x02976) + of "gtrarr": Rune(0x02978) + of "subrarr": Rune(0x02979) + of "suplarr": Rune(0x0297B) + of "lfisht": Rune(0x0297C) + of "rfisht": Rune(0x0297D) + of "ufisht": Rune(0x0297E) + of "dfisht": Rune(0x0297F) + of "lopar": Rune(0x02985) + of "ropar": Rune(0x02986) + of "lbrke": Rune(0x0298B) + of "rbrke": Rune(0x0298C) + of "lbrkslu": Rune(0x0298D) + of "rbrksld": Rune(0x0298E) + of "lbrksld": Rune(0x0298F) + of "rbrkslu": Rune(0x02990) + of "langd": Rune(0x02991) + of "rangd": Rune(0x02992) + of "lparlt": Rune(0x02993) + of "rpargt": Rune(0x02994) + of "gtlPar": Rune(0x02995) + of "ltrPar": Rune(0x02996) + of "vzigzag": Rune(0x0299A) + of "vangrt": Rune(0x0299C) + of "angrtvbd": Rune(0x0299D) + of "ange": Rune(0x029A4) + of "range": Rune(0x029A5) + of "dwangle": Rune(0x029A6) + of "uwangle": Rune(0x029A7) + of "angmsdaa": Rune(0x029A8) + of "angmsdab": Rune(0x029A9) + of "angmsdac": Rune(0x029AA) + of "angmsdad": Rune(0x029AB) + of "angmsdae": Rune(0x029AC) + of "angmsdaf": Rune(0x029AD) + of "angmsdag": Rune(0x029AE) + of "angmsdah": Rune(0x029AF) + of "bemptyv": Rune(0x029B0) + of "demptyv": Rune(0x029B1) + of "cemptyv": Rune(0x029B2) + of "raemptyv": Rune(0x029B3) + of "laemptyv": Rune(0x029B4) + of "ohbar": Rune(0x029B5) + of "omid": Rune(0x029B6) + of "opar": Rune(0x029B7) + of "operp": Rune(0x029B9) + of "olcross": Rune(0x029BB) + of "odsold": Rune(0x029BC) + of "olcir": Rune(0x029BE) + of "ofcir": Rune(0x029BF) + of "olt": Rune(0x029C0) + of "ogt": Rune(0x029C1) + of "cirscir": Rune(0x029C2) + of "cirE": Rune(0x029C3) + of "solb": Rune(0x029C4) + of "bsolb": Rune(0x029C5) + of "boxbox": Rune(0x029C9) + of "trisb": Rune(0x029CD) + of "rtriltri": Rune(0x029CE) + of "LeftTriangleBar": Rune(0x029CF) + of "RightTriangleBar": Rune(0x029D0) + of "race": Rune(0x029DA) + of "iinfin": Rune(0x029DC) + of "infintie": Rune(0x029DD) + of "nvinfin": Rune(0x029DE) + of "eparsl": Rune(0x029E3) + of "smeparsl": Rune(0x029E4) + of "eqvparsl": Rune(0x029E5) + of "lozf", "blacklozenge": Rune(0x029EB) + of "RuleDelayed": Rune(0x029F4) + of "dsol": Rune(0x029F6) + of "xodot", "bigodot": Rune(0x02A00) + of "xoplus", "bigoplus": Rune(0x02A01) + of "xotime", "bigotimes": Rune(0x02A02) + of "xuplus", "biguplus": Rune(0x02A04) + of "xsqcup", "bigsqcup": Rune(0x02A06) + of "qint", "iiiint": Rune(0x02A0C) + of "fpartint": Rune(0x02A0D) + of "cirfnint": Rune(0x02A10) + of "awint": Rune(0x02A11) + of "rppolint": Rune(0x02A12) + of "scpolint": Rune(0x02A13) + of "npolint": Rune(0x02A14) + of "pointint": Rune(0x02A15) + of "quatint": Rune(0x02A16) + of "intlarhk": Rune(0x02A17) + of "pluscir": Rune(0x02A22) + of "plusacir": Rune(0x02A23) + of "simplus": Rune(0x02A24) + of "plusdu": Rune(0x02A25) + of "plussim": Rune(0x02A26) + of "plustwo": Rune(0x02A27) + of "mcomma": Rune(0x02A29) + of "minusdu": Rune(0x02A2A) + of "loplus": Rune(0x02A2D) + of "roplus": Rune(0x02A2E) + of "Cross": Rune(0x02A2F) + of "timesd": Rune(0x02A30) + of "timesbar": Rune(0x02A31) + of "smashp": Rune(0x02A33) + of "lotimes": Rune(0x02A34) + of "rotimes": Rune(0x02A35) + of "otimesas": Rune(0x02A36) + of "Otimes": Rune(0x02A37) + of "odiv": Rune(0x02A38) + of "triplus": Rune(0x02A39) + of "triminus": Rune(0x02A3A) + of "tritime": Rune(0x02A3B) + of "iprod", "intprod": Rune(0x02A3C) + of "amalg": Rune(0x02A3F) + of "capdot": Rune(0x02A40) + of "ncup": Rune(0x02A42) + of "ncap": Rune(0x02A43) + of "capand": Rune(0x02A44) + of "cupor": Rune(0x02A45) + of "cupcap": Rune(0x02A46) + of "capcup": Rune(0x02A47) + of "cupbrcap": Rune(0x02A48) + of "capbrcup": Rune(0x02A49) + of "cupcup": Rune(0x02A4A) + of "capcap": Rune(0x02A4B) + of "ccups": Rune(0x02A4C) + of "ccaps": Rune(0x02A4D) + of "ccupssm": Rune(0x02A50) + of "And": Rune(0x02A53) + of "Or": Rune(0x02A54) + of "andand": Rune(0x02A55) + of "oror": Rune(0x02A56) + of "orslope": Rune(0x02A57) + of "andslope": Rune(0x02A58) + of "andv": Rune(0x02A5A) + of "orv": Rune(0x02A5B) + of "andd": Rune(0x02A5C) + of "ord": Rune(0x02A5D) + of "wedbar": Rune(0x02A5F) + of "sdote": Rune(0x02A66) + of "simdot": Rune(0x02A6A) + of "congdot": Rune(0x02A6D) + of "easter": Rune(0x02A6E) + of "apacir": Rune(0x02A6F) + of "apE": Rune(0x02A70) + of "eplus": Rune(0x02A71) + of "pluse": Rune(0x02A72) + of "Esim": Rune(0x02A73) + of "Colone": Rune(0x02A74) + of "Equal": Rune(0x02A75) + of "eDDot", "ddotseq": Rune(0x02A77) + of "equivDD": Rune(0x02A78) + of "ltcir": Rune(0x02A79) + of "gtcir": Rune(0x02A7A) + of "ltquest": Rune(0x02A7B) + of "gtquest": Rune(0x02A7C) + of "les", "LessSlantEqual", "leqslant": Rune(0x02A7D) + of "ges", "GreaterSlantEqual", "geqslant": Rune(0x02A7E) + of "lesdot": Rune(0x02A7F) + of "gesdot": Rune(0x02A80) + of "lesdoto": Rune(0x02A81) + of "gesdoto": Rune(0x02A82) + of "lesdotor": Rune(0x02A83) + of "gesdotol": Rune(0x02A84) + of "lap", "lessapprox": Rune(0x02A85) + of "gap", "gtrapprox": Rune(0x02A86) + of "lne", "lneq": Rune(0x02A87) + of "gne", "gneq": Rune(0x02A88) + of "lnap", "lnapprox": Rune(0x02A89) + of "gnap", "gnapprox": Rune(0x02A8A) + of "lEg", "lesseqqgtr": Rune(0x02A8B) + of "gEl", "gtreqqless": Rune(0x02A8C) + of "lsime": Rune(0x02A8D) + of "gsime": Rune(0x02A8E) + of "lsimg": Rune(0x02A8F) + of "gsiml": Rune(0x02A90) + of "lgE": Rune(0x02A91) + of "glE": Rune(0x02A92) + of "lesges": Rune(0x02A93) + of "gesles": Rune(0x02A94) + of "els", "eqslantless": Rune(0x02A95) + of "egs", "eqslantgtr": Rune(0x02A96) + of "elsdot": Rune(0x02A97) + of "egsdot": Rune(0x02A98) + of "el": Rune(0x02A99) + of "eg": Rune(0x02A9A) + of "siml": Rune(0x02A9D) + of "simg": Rune(0x02A9E) + of "simlE": Rune(0x02A9F) + of "simgE": Rune(0x02AA0) + of "LessLess": Rune(0x02AA1) + of "GreaterGreater": Rune(0x02AA2) + of "glj": Rune(0x02AA4) + of "gla": Rune(0x02AA5) + of "ltcc": Rune(0x02AA6) + of "gtcc": Rune(0x02AA7) + of "lescc": Rune(0x02AA8) + of "gescc": Rune(0x02AA9) + of "smt": Rune(0x02AAA) + of "lat": Rune(0x02AAB) + of "smte": Rune(0x02AAC) + of "late": Rune(0x02AAD) + of "bumpE": Rune(0x02AAE) + of "pre", "preceq", "PrecedesEqual": Rune(0x02AAF) + of "sce", "succeq", "SucceedsEqual": Rune(0x02AB0) + of "prE": Rune(0x02AB3) + of "scE": Rune(0x02AB4) + of "prnE", "precneqq": Rune(0x02AB5) + of "scnE", "succneqq": Rune(0x02AB6) + of "prap", "precapprox": Rune(0x02AB7) + of "scap", "succapprox": Rune(0x02AB8) + of "prnap", "precnapprox": Rune(0x02AB9) + of "scnap", "succnapprox": Rune(0x02ABA) + of "Pr": Rune(0x02ABB) + of "Sc": Rune(0x02ABC) + of "subdot": Rune(0x02ABD) + of "supdot": Rune(0x02ABE) + of "subplus": Rune(0x02ABF) + of "supplus": Rune(0x02AC0) + of "submult": Rune(0x02AC1) + of "supmult": Rune(0x02AC2) + of "subedot": Rune(0x02AC3) + of "supedot": Rune(0x02AC4) + of "subE", "subseteqq": Rune(0x02AC5) + of "supE", "supseteqq": Rune(0x02AC6) + of "subsim": Rune(0x02AC7) + of "supsim": Rune(0x02AC8) + of "subnE", "subsetneqq": Rune(0x02ACB) + of "supnE", "supsetneqq": Rune(0x02ACC) + of "csub": Rune(0x02ACF) + of "csup": Rune(0x02AD0) + of "csube": Rune(0x02AD1) + of "csupe": Rune(0x02AD2) + of "subsup": Rune(0x02AD3) + of "supsub": Rune(0x02AD4) + of "subsub": Rune(0x02AD5) + of "supsup": Rune(0x02AD6) + of "suphsub": Rune(0x02AD7) + of "supdsub": Rune(0x02AD8) + of "forkv": Rune(0x02AD9) + of "topfork": Rune(0x02ADA) + of "mlcp": Rune(0x02ADB) + of "Dashv", "DoubleLeftTee": Rune(0x02AE4) + of "Vdashl": Rune(0x02AE6) + of "Barv": Rune(0x02AE7) + of "vBar": Rune(0x02AE8) + of "vBarv": Rune(0x02AE9) + of "Vbar": Rune(0x02AEB) + of "Not": Rune(0x02AEC) + of "bNot": Rune(0x02AED) + of "rnmid": Rune(0x02AEE) + of "cirmid": Rune(0x02AEF) + of "midcir": Rune(0x02AF0) + of "topcir": Rune(0x02AF1) + of "nhpar": Rune(0x02AF2) + of "parsim": Rune(0x02AF3) + of "parsl": Rune(0x02AFD) + of "fflig": Rune(0x0FB00) + of "filig": Rune(0x0FB01) + of "fllig": Rune(0x0FB02) + of "ffilig": Rune(0x0FB03) + of "ffllig": Rune(0x0FB04) + of "Ascr": Rune(0x1D49C) + of "Cscr": Rune(0x1D49E) + of "Dscr": Rune(0x1D49F) + of "Gscr": Rune(0x1D4A2) + of "Jscr": Rune(0x1D4A5) + of "Kscr": Rune(0x1D4A6) + of "Nscr": Rune(0x1D4A9) + of "Oscr": Rune(0x1D4AA) + of "Pscr": Rune(0x1D4AB) + of "Qscr": Rune(0x1D4AC) + of "Sscr": Rune(0x1D4AE) + of "Tscr": Rune(0x1D4AF) + of "Uscr": Rune(0x1D4B0) + of "Vscr": Rune(0x1D4B1) + of "Wscr": Rune(0x1D4B2) + of "Xscr": Rune(0x1D4B3) + of "Yscr": Rune(0x1D4B4) + of "Zscr": Rune(0x1D4B5) + of "ascr": Rune(0x1D4B6) + of "bscr": Rune(0x1D4B7) + of "cscr": Rune(0x1D4B8) + of "dscr": Rune(0x1D4B9) + of "fscr": Rune(0x1D4BB) + of "hscr": Rune(0x1D4BD) + of "iscr": Rune(0x1D4BE) + of "jscr": Rune(0x1D4BF) + of "kscr": Rune(0x1D4C0) + of "lscr": Rune(0x1D4C1) + of "mscr": Rune(0x1D4C2) + of "nscr": Rune(0x1D4C3) + of "pscr": Rune(0x1D4C5) + of "qscr": Rune(0x1D4C6) + of "rscr": Rune(0x1D4C7) + of "sscr": Rune(0x1D4C8) + of "tscr": Rune(0x1D4C9) + of "uscr": Rune(0x1D4CA) + of "vscr": Rune(0x1D4CB) + of "wscr": Rune(0x1D4CC) + of "xscr": Rune(0x1D4CD) + of "yscr": Rune(0x1D4CE) + of "zscr": Rune(0x1D4CF) + of "Afr": Rune(0x1D504) + of "Bfr": Rune(0x1D505) + of "Dfr": Rune(0x1D507) + of "Efr": Rune(0x1D508) + of "Ffr": Rune(0x1D509) + of "Gfr": Rune(0x1D50A) + of "Jfr": Rune(0x1D50D) + of "Kfr": Rune(0x1D50E) + of "Lfr": Rune(0x1D50F) + of "Mfr": Rune(0x1D510) + of "Nfr": Rune(0x1D511) + of "Ofr": Rune(0x1D512) + of "Pfr": Rune(0x1D513) + of "Qfr": Rune(0x1D514) + of "Sfr": Rune(0x1D516) + of "Tfr": Rune(0x1D517) + of "Ufr": Rune(0x1D518) + of "Vfr": Rune(0x1D519) + of "Wfr": Rune(0x1D51A) + of "Xfr": Rune(0x1D51B) + of "Yfr": Rune(0x1D51C) + of "afr": Rune(0x1D51E) + of "bfr": Rune(0x1D51F) + of "cfr": Rune(0x1D520) + of "dfr": Rune(0x1D521) + of "efr": Rune(0x1D522) + of "ffr": Rune(0x1D523) + of "gfr": Rune(0x1D524) + of "hfr": Rune(0x1D525) + of "ifr": Rune(0x1D526) + of "jfr": Rune(0x1D527) + of "kfr": Rune(0x1D528) + of "lfr": Rune(0x1D529) + of "mfr": Rune(0x1D52A) + of "nfr": Rune(0x1D52B) + of "ofr": Rune(0x1D52C) + of "pfr": Rune(0x1D52D) + of "qfr": Rune(0x1D52E) + of "rfr": Rune(0x1D52F) + of "sfr": Rune(0x1D530) + of "tfr": Rune(0x1D531) + of "ufr": Rune(0x1D532) + of "vfr": Rune(0x1D533) + of "wfr": Rune(0x1D534) + of "xfr": Rune(0x1D535) + of "yfr": Rune(0x1D536) + of "zfr": Rune(0x1D537) + of "Aopf": Rune(0x1D538) + of "Bopf": Rune(0x1D539) + of "Dopf": Rune(0x1D53B) + of "Eopf": Rune(0x1D53C) + of "Fopf": Rune(0x1D53D) + of "Gopf": Rune(0x1D53E) + of "Iopf": Rune(0x1D540) + of "Jopf": Rune(0x1D541) + of "Kopf": Rune(0x1D542) + of "Lopf": Rune(0x1D543) + of "Mopf": Rune(0x1D544) + of "Oopf": Rune(0x1D546) + of "Sopf": Rune(0x1D54A) + of "Topf": Rune(0x1D54B) + of "Uopf": Rune(0x1D54C) + of "Vopf": Rune(0x1D54D) + of "Wopf": Rune(0x1D54E) + of "Xopf": Rune(0x1D54F) + of "Yopf": Rune(0x1D550) + of "aopf": Rune(0x1D552) + of "bopf": Rune(0x1D553) + of "copf": Rune(0x1D554) + of "dopf": Rune(0x1D555) + of "eopf": Rune(0x1D556) + of "fopf": Rune(0x1D557) + of "gopf": Rune(0x1D558) + of "hopf": Rune(0x1D559) + of "iopf": Rune(0x1D55A) + of "jopf": Rune(0x1D55B) + of "kopf": Rune(0x1D55C) + of "lopf": Rune(0x1D55D) + of "mopf": Rune(0x1D55E) + of "nopf": Rune(0x1D55F) + of "oopf": Rune(0x1D560) + of "popf": Rune(0x1D561) + of "qopf": Rune(0x1D562) + of "ropf": Rune(0x1D563) + of "sopf": Rune(0x1D564) + of "topf": Rune(0x1D565) + of "uopf": Rune(0x1D566) + of "vopf": Rune(0x1D567) + of "wopf": Rune(0x1D568) + of "xopf": Rune(0x1D569) + of "yopf": Rune(0x1D56A) + of "zopf": Rune(0x1D56B) + else: Rune(0) + proc entityToUtf8*(entity: string): string = - ## Converts an HTML entity name like ``Ü`` to its UTF-8 equivalent. + ## Converts an HTML entity name like ``Ü`` or values like ``Ü`` + ## or ``Ü`` to its UTF-8 equivalent. ## "" is returned if the entity name is unknown. The HTML parser ## already converts entities to UTF-8. - for name, val in items(Entities): - if name == entity: return toUTF8(Rune(val)) - result = "" + runnableExamples: + const sigma = "Σ" + doAssert entityToUtf8("") == "" + doAssert entityToUtf8("a") == "" + doAssert entityToUtf8("gt") == ">" + doAssert entityToUtf8("Uuml") == "Ü" + doAssert entityToUtf8("quest") == "?" + doAssert entityToUtf8("#63") == "?" + doAssert entityToUtf8("Sigma") == sigma + doAssert entityToUtf8("#931") == sigma + doAssert entityToUtf8("#0931") == sigma + doAssert entityToUtf8("#x3A3") == sigma + doAssert entityToUtf8("#x03A3") == sigma + doAssert entityToUtf8("#x3a3") == sigma + doAssert entityToUtf8("#X3a3") == sigma + let rune = entityToRune(entity) + if rune.ord <= 0: result = "" + else: result = toUTF8(rune) proc addNode(father, son: XmlNode) = if son != nil: add(father, son) -proc parse(x: var XmlParser, errors: var seq[string]): XmlNode +proc parse(x: var XmlParser, errors: var seq[string]): XmlNode {.gcsafe.} proc expected(x: var XmlParser, n: XmlNode): string = result = errorMsg(x, "</" & n.tag & "> expected") diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 648481ca5..139d4bb50 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -73,12 +73,18 @@ ## progress of the HTTP request. ## ## .. code-block:: Nim -## var client = newAsyncHttpClient() +## import asyncdispatch, httpclient +## ## proc onProgressChanged(total, progress, speed: BiggestInt) {.async.} = ## echo("Downloaded ", progress, " of ", total) ## echo("Current rate: ", speed div 1000, "kb/s") -## client.onProgressChanged = onProgressChanged -## discard await client.getContent("http://speedtest-ams2.digitalocean.com/100mb.test") +## +## proc asyncProc() {.async.} = +## var client = newAsyncHttpClient() +## client.onProgressChanged = onProgressChanged +## discard await client.getContent("http://speedtest-ams2.digitalocean.com/100mb.test") +## +## waitFor asyncProc() ## ## If you would like to remove the callback simply set it to ``nil``. ## @@ -179,7 +185,7 @@ proc body*(response: Response): string = ## Retrieves the specified response's body. ## ## The response's body stream is read synchronously. - if response.body.isNil(): + if response.body.len == 0: response.body = response.bodyStream.readAll() return response.body @@ -192,7 +198,7 @@ proc `body=`*(response: Response, value: string) {.deprecated.} = proc body*(response: AsyncResponse): Future[string] {.async.} = ## Reads the response's body and caches it. The read is performed only ## once. - if response.body.isNil: + if response.body.len == 0: response.body = await readAll(response.bodyStream) return response.body @@ -213,10 +219,6 @@ type ## and ``postContent`` proc, ## when the server returns an error -{.deprecated: [TResponse: Response, PProxy: Proxy, - EInvalidProtocol: ProtocolError, EHttpRequestErr: HttpRequestError -].} - const defUserAgent* = "Nim httpclient/" & NimVersion proc httpError(msg: string) = @@ -241,7 +243,7 @@ proc parseChunks(s: Socket, timeout: int): string = var i = 0 if chunkSizeStr == "": httpError("Server terminated connection prematurely") - while true: + while i < chunkSizeStr.len: case chunkSizeStr[i] of '0'..'9': chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0')) @@ -249,8 +251,6 @@ proc parseChunks(s: Socket, timeout: int): string = chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10) of 'A'..'F': chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10) - of '\0': - break of ';': # http://tools.ietf.org/html/rfc2616#section-3.6.1 # We don't care about chunk-extensions. @@ -358,21 +358,17 @@ proc parseResponse(s: Socket, getBody: bool, timeout: int): Response = else: result.body = "" -{.deprecated: [THttpMethod: HttpMethod].} - when not defined(ssl): type SSLContext = ref object var defaultSSLContext {.threadvar.}: SSLContext -when defined(ssl): - defaultSSLContext = newContext(verifyMode = CVerifyNone) - template contextOrDefault(ctx: SSLContext): SSLContext = - var result = ctx - if ctx == nil: - if defaultSSLContext == nil: - defaultSSLContext = newContext(verifyMode = CVerifyNone) +proc getDefaultSSL(): SSLContext = + result = defaultSslContext + when defined(ssl): + if result == nil: + defaultSSLContext = newContext(verifyMode = CVerifyNone) result = defaultSSLContext - result + doAssert result != nil, "failure to initialize the SSL context" proc newProxy*(url: string, auth = ""): Proxy = ## Constructs a new ``TProxy`` object. @@ -382,23 +378,23 @@ proc newMultipartData*: MultipartData = ## Constructs a new ``MultipartData`` object. MultipartData(content: @[]) -proc add*(p: var MultipartData, name, content: string, filename: string = nil, - contentType: string = nil) = +proc add*(p: var MultipartData, name, content: string, filename: string = "", + contentType: string = "") = ## Add a value to the multipart data. Raises a `ValueError` exception if ## `name`, `filename` or `contentType` contain newline characters. if {'\c','\L'} in name: raise newException(ValueError, "name contains a newline character") - if filename != nil and {'\c','\L'} in filename: + if {'\c','\L'} in filename: raise newException(ValueError, "filename contains a newline character") - if contentType != nil and {'\c','\L'} in contentType: + if {'\c','\L'} in contentType: raise newException(ValueError, "contentType contains a newline character") var str = "Content-Disposition: form-data; name=\"" & name & "\"" - if filename != nil: + if filename.len > 0: str.add("; filename=\"" & filename & "\"") str.add("\c\L") - if contentType != nil: + if contentType.len > 0: str.add("Content-Type: " & contentType & "\c\L") str.add("\c\L" & content & "\c\L") @@ -438,7 +434,7 @@ proc addFiles*(p: var MultipartData, xs: openarray[tuple[name, file: string]]): var contentType: string let (_, fName, ext) = splitFile(file) if ext.len > 0: - contentType = m.getMimetype(ext[1..ext.high], nil) + contentType = m.getMimetype(ext[1..ext.high], "") p.add(name, readFile(file), fName & ext, contentType) result = p @@ -461,7 +457,7 @@ proc `[]=`*(p: var MultipartData, name: string, p.add(name, file.content, file.name, file.contentType) proc format(p: MultipartData): tuple[contentType, body: string] = - if p == nil or p.content == nil or p.content.len == 0: + if p == nil or p.content.len == 0: return ("", "") # Create boundary that is not in the data to be formatted @@ -482,9 +478,9 @@ proc format(p: MultipartData): tuple[contentType, body: string] = result.body.add("--" & bound & "--\c\L") proc request*(url: string, httpMethod: string, extraHeaders = "", - body = "", sslContext = defaultSSLContext, timeout = -1, + body = "", sslContext = getDefaultSSL(), timeout = -1, userAgent = defUserAgent, proxy: Proxy = nil): Response - {.deprecated.} = + {.deprecated: "use HttpClient.request instead".} = ## | Requests ``url`` with the custom method string specified by the ## | ``httpMethod`` parameter. ## | Extra headers can be specified and must be separated by ``\c\L`` @@ -581,8 +577,8 @@ proc request*(url: string, httpMethod: string, extraHeaders = "", result = parseResponse(s, httpMethod != "HEAD", timeout) -proc request*(url: string, httpMethod = httpGET, extraHeaders = "", - body = "", sslContext = defaultSSLContext, timeout = -1, +proc request*(url: string, httpMethod = HttpGET, extraHeaders = "", + body = "", sslContext = getDefaultSSL(), timeout = -1, userAgent = defUserAgent, proxy: Proxy = nil): Response {.deprecated.} = ## | Requests ``url`` with the specified ``httpMethod``. @@ -613,7 +609,7 @@ proc getNewLocation(lastURL: string, headers: HttpHeaders): string = result = $parsed proc get*(url: string, extraHeaders = "", maxRedirects = 5, - sslContext: SSLContext = defaultSSLContext, + sslContext: SSLContext = getDefaultSSL(), timeout = -1, userAgent = defUserAgent, proxy: Proxy = nil): Response {.deprecated.} = ## | GETs the ``url`` and returns a ``Response`` object @@ -622,19 +618,19 @@ proc get*(url: string, extraHeaders = "", maxRedirects = 5, ## | An optional timeout can be specified in milliseconds, if reading from the ## server takes longer than specified an ETimeout exception will be raised. ## - ## ## **Deprecated since version 0.15.0**: use ``HttpClient.get`` instead. - result = request(url, httpGET, extraHeaders, "", sslContext, timeout, + ## **Deprecated since version 0.15.0**: use ``HttpClient.get`` instead. + result = request(url, HttpGET, extraHeaders, "", sslContext, timeout, userAgent, proxy) var lastURL = url for i in 1..maxRedirects: if result.status.redirection(): let redirectTo = getNewLocation(lastURL, result.headers) - result = request(redirectTo, httpGET, extraHeaders, "", sslContext, + result = request(redirectTo, HttpGET, extraHeaders, "", sslContext, timeout, userAgent, proxy) lastURL = redirectTo proc getContent*(url: string, extraHeaders = "", maxRedirects = 5, - sslContext: SSLContext = defaultSSLContext, + sslContext: SSLContext = getDefaultSSL(), timeout = -1, userAgent = defUserAgent, proxy: Proxy = nil): string {.deprecated.} = ## | GETs the body and returns it as a string. @@ -653,7 +649,7 @@ proc getContent*(url: string, extraHeaders = "", maxRedirects = 5, proc post*(url: string, extraHeaders = "", body = "", maxRedirects = 5, - sslContext: SSLContext = defaultSSLContext, + sslContext: SSLContext = getDefaultSSL(), timeout = -1, userAgent = defUserAgent, proxy: Proxy = nil, multipart: MultipartData = nil): Response {.deprecated.} = @@ -683,20 +679,20 @@ proc post*(url: string, extraHeaders = "", body = "", if not multipart.isNil: xh.add(withNewLine("Content-Type: " & mpContentType)) - result = request(url, httpPOST, xh, xb, sslContext, timeout, userAgent, + result = request(url, HttpPOST, xh, xb, sslContext, timeout, userAgent, proxy) var lastURL = url for i in 1..maxRedirects: if result.status.redirection(): let redirectTo = getNewLocation(lastURL, result.headers) - var meth = if result.status != "307": httpGet else: httpPost + var meth = if result.status != "307": HttpGet else: HttpPost result = request(redirectTo, meth, xh, xb, sslContext, timeout, userAgent, proxy) lastURL = redirectTo proc postContent*(url: string, extraHeaders = "", body = "", maxRedirects = 5, - sslContext: SSLContext = defaultSSLContext, + sslContext: SSLContext = getDefaultSSL(), timeout = -1, userAgent = defUserAgent, proxy: Proxy = nil, multipart: MultipartData = nil): string @@ -719,7 +715,7 @@ proc postContent*(url: string, extraHeaders = "", body = "", return r.body proc downloadFile*(url: string, outputFilename: string, - sslContext: SSLContext = defaultSSLContext, + sslContext: SSLContext = getDefaultSSL(), timeout = -1, userAgent = defUserAgent, proxy: Proxy = nil) {.deprecated.} = ## | Downloads ``url`` and saves it to ``outputFilename`` @@ -739,12 +735,13 @@ proc downloadFile*(url: string, outputFilename: string, proc generateHeaders(requestUrl: Uri, httpMethod: string, headers: HttpHeaders, body: string, proxy: Proxy): string = # GET - result = httpMethod.toUpperAscii() + let upperMethod = httpMethod.toUpperAscii() + result = upperMethod result.add ' ' - if proxy.isNil or (not proxy.isNil and requestUrl.scheme == "https"): + if proxy.isNil or requestUrl.scheme == "https": # /path?query - if requestUrl.path[0] != '/': result.add '/' + if not requestUrl.path.startsWith("/"): result.add '/' result.add(requestUrl.path) if requestUrl.query.len > 0: result.add("?" & requestUrl.query) @@ -768,7 +765,9 @@ proc generateHeaders(requestUrl: Uri, httpMethod: string, add(result, "Connection: Keep-Alive\c\L") # Content length header. - if body.len > 0 and not headers.hasKey("Content-Length"): + const requiresBody = ["POST", "PUT", "PATCH"] + let needsContentLength = body.len > 0 or upperMethod in requiresBody + if needsContentLength and not headers.hasKey("Content-Length"): add(result, "Content-Length: " & $body.len & "\c\L") # Proxy auth header. @@ -808,6 +807,7 @@ type lastProgressReport: float when SocketType is AsyncSocket: bodyStream: FutureStream[string] + parseBodyFut: Future[void] else: bodyStream: Stream getBody: bool ## When `false`, the body is never read in requestAux. @@ -816,7 +816,7 @@ type HttpClient* = HttpClientBase[Socket] proc newHttpClient*(userAgent = defUserAgent, - maxRedirects = 5, sslContext = defaultSslContext, proxy: Proxy = nil, + maxRedirects = 5, sslContext = getDefaultSSL(), proxy: Proxy = nil, timeout = -1): HttpClient = ## Creates a new HttpClient instance. ## @@ -843,7 +843,7 @@ proc newHttpClient*(userAgent = defUserAgent, result.bodyStream = newStringStream() result.getBody = true when defined(ssl): - result.sslContext = contextOrDefault(sslContext) + result.sslContext = sslContext type AsyncHttpClient* = HttpClientBase[AsyncSocket] @@ -851,7 +851,7 @@ type {.deprecated: [PAsyncHttpClient: AsyncHttpClient].} proc newAsyncHttpClient*(userAgent = defUserAgent, - maxRedirects = 5, sslContext = defaultSslContext, + maxRedirects = 5, sslContext = getDefaultSSL(), proxy: Proxy = nil): AsyncHttpClient = ## Creates a new AsyncHttpClient instance. ## @@ -875,7 +875,7 @@ proc newAsyncHttpClient*(userAgent = defUserAgent, result.bodyStream = newFutureStream[string]("newAsyncHttpClient") result.getBody = true when defined(ssl): - result.sslContext = contextOrDefault(sslContext) + result.sslContext = sslContext proc close*(client: HttpClient | AsyncHttpClient) = ## Closes any connections held by the HTTP client. @@ -929,7 +929,7 @@ proc parseChunks(client: HttpClient | AsyncHttpClient): Future[void] var i = 0 if chunkSizeStr == "": httpError("Server terminated connection prematurely") - while true: + while i < chunkSizeStr.len: case chunkSizeStr[i] of '0'..'9': chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0')) @@ -937,8 +937,6 @@ proc parseChunks(client: HttpClient | AsyncHttpClient): Future[void] chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10) of 'A'..'F': chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10) - of '\0': - break of ';': # http://tools.ietf.org/html/rfc2616#section-3.6.1 # We don't care about chunk-extensions. @@ -1069,10 +1067,14 @@ proc parseResponse(client: HttpClient | AsyncHttpClient, if getBody: when client is HttpClient: client.bodyStream = newStringStream() + result.bodyStream = client.bodyStream + parseBody(client, result.headers, result.version) else: client.bodyStream = newFutureStream[string]("parseResponse") - await parseBody(client, result.headers, result.version) - result.bodyStream = client.bodyStream + result.bodyStream = client.bodyStream + assert(client.parseBodyFut.isNil or client.parseBodyFut.finished) + client.parseBodyFut = parseBody(client, result.headers, result.version) + # do not wait here for the body request to complete proc newConnection(client: HttpClient | AsyncHttpClient, url: Uri) {.multisync.} = @@ -1162,6 +1164,12 @@ proc requestAux(client: HttpClient | AsyncHttpClient, url: string, # Helper that actually makes the request. Does not handle redirects. let requestUrl = parseUri(url) + when client is AsyncHttpClient: + if not client.parseBodyFut.isNil: + # let the current operation finish before making another request + await client.parseBodyFut + client.parseBodyFut = nil + await newConnection(client, requestUrl) let effectiveHeaders = client.headers.override(headers) @@ -1187,7 +1195,7 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string, ## Connects to the hostname specified by the URL and performs a request ## using the custom method string specified by ``httpMethod``. ## - ## Connection will kept alive. Further requests on the same ``client`` to + ## Connection will be kept alive. Further requests on the same ``client`` to ## the same hostname will not require a new connection to be made. The ## connection can be closed by using the ``close`` procedure. ## @@ -1199,7 +1207,7 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string, for i in 1..client.maxRedirects: if result.status.redirection(): let redirectTo = getNewLocation(lastURL, result.headers) - result = await client.request(redirectTo, httpMethod, body, headers) + result = await client.requestAux(redirectTo, httpMethod, body, headers) lastURL = redirectTo diff --git a/lib/pure/httpcore.nim b/lib/pure/httpcore.nim index f150fa1c1..f85375111 100644 --- a/lib/pure/httpcore.nim +++ b/lib/pure/httpcore.nim @@ -45,10 +45,6 @@ type ## TCP/IP tunnel, usually used for proxies. HttpPatch ## Applies partial modifications to a resource. -{.deprecated: [httpGet: HttpGet, httpHead: HttpHead, httpPost: HttpPost, - httpPut: HttpPut, httpDelete: HttpDelete, httpTrace: HttpTrace, - httpOptions: HttpOptions, httpConnect: HttpConnect].} - const Http100* = HttpCode(100) @@ -190,11 +186,11 @@ proc len*(headers: HttpHeaders): int = return headers.table.len proc parseList(line: string, list: var seq[string], start: int): int = var i = 0 var current = "" - while line[start + i] notin {'\c', '\l', '\0'}: + while start+i < line.len and line[start + i] notin {'\c', '\l'}: i += line.skipWhitespace(start + i) i += line.parseUntil(current, {'\c', '\l', ','}, start + i) list.add(current) - if line[start + i] == ',': + if start+i < line.len and line[start + i] == ',': i.inc # Skip , current.setLen(0) diff --git a/lib/pure/httpserver.nim b/lib/pure/httpserver.nim index 632eb198a..a81e8c0a8 100644 --- a/lib/pure/httpserver.nim +++ b/lib/pure/httpserver.nim @@ -110,7 +110,6 @@ when false: # TODO: Fix this, or get rid of it. type RequestMethod = enum reqGet, reqPost - {.deprecated: [TRequestMethod: RequestMethod].} proc executeCgi(client: Socket, path, query: string, meth: RequestMethod) = var env = newStringTable(modeCaseInsensitive) @@ -126,7 +125,7 @@ when false: var dataAvail = false while dataAvail: dataAvail = recvLine(client, buf) # TODO: This is incorrect. - var L = toLower(buf.string) + var L = toLowerAscii(buf.string) if L.startsWith("content-length:"): var i = len("content-length:") while L[i] in Whitespace: inc(i) @@ -199,7 +198,7 @@ when false: notFound(client) else: when defined(Windows): - var ext = splitFile(path).ext.toLower + var ext = splitFile(path).ext.toLowerAscii if ext == ".exe" or ext == ".cgi": # XXX: extract interpreter information here? cgi = true @@ -225,7 +224,6 @@ type PAsyncHTTPServer* = ref AsyncHTTPServer AsyncHTTPServer = object of Server asyncSocket: AsyncSocket -{.deprecated: [TAsyncHTTPServer: AsyncHTTPServer, TServer: Server].} proc open*(s: var Server, port = Port(80), reuseAddr = false) = ## creates a new server at port `port`. If ``port == 0`` a free port is @@ -303,7 +301,7 @@ proc next*(s: var Server) = if s.reqMethod == "POST": # Check for Expect header if s.headers.hasKey("Expect"): - if s.headers["Expect"].toLower == "100-continue": + if s.headers["Expect"].toLowerAscii == "100-continue": s.client.sendStatus("100 Continue") else: s.client.sendStatus("417 Expectation Failed") @@ -427,7 +425,7 @@ proc nextAsync(s: PAsyncHTTPServer) = if s.reqMethod == "POST": # Check for Expect header if s.headers.hasKey("Expect"): - if s.headers["Expect"].toLower == "100-continue": + if s.headers["Expect"].toLowerAscii == "100-continue": s.client.sendStatus("100 Continue") else: s.client.sendStatus("417 Expectation Failed") diff --git a/lib/pure/includes/asynccommon.nim b/lib/pure/includes/asynccommon.nim index 45b10c584..06f4958c6 100644 --- a/lib/pure/includes/asynccommon.nim +++ b/lib/pure/includes/asynccommon.nim @@ -18,13 +18,13 @@ proc createAsyncNativeSocket*(domain: Domain = Domain.AF_INET, createAsyncNativeSocketImpl(domain, sockType, protocol) proc newAsyncNativeSocket*(domain: cint, sockType: cint, - protocol: cint): AsyncFD {.deprecated.} = + protocol: cint): AsyncFD {.deprecated: "use createAsyncNativeSocket instead".} = createAsyncNativeSocketImpl(domain, sockType, protocol) proc newAsyncNativeSocket*(domain: Domain = Domain.AF_INET, sockType: SockType = SOCK_STREAM, protocol: Protocol = IPPROTO_TCP): AsyncFD - {.deprecated.} = + {.deprecated: "use createAsyncNativeSocket instead".} = createAsyncNativeSocketImpl(domain, sockType, protocol) when defined(windows) or defined(nimdoc): diff --git a/lib/pure/includes/oserr.nim b/lib/pure/includes/oserr.nim index 0889d7383..493e8e174 100644 --- a/lib/pure/includes/oserr.nim +++ b/lib/pure/includes/oserr.nim @@ -12,50 +12,6 @@ when not defined(nimscript): when defined(windows): import winlean -proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} = - ## Retrieves the operating system's error flag, ``errno``. - ## On Windows ``GetLastError`` is checked before ``errno``. - ## Returns "" if no error occurred. - ## - ## **Deprecated since version 0.9.4**: use the other ``osErrorMsg`` proc. - - result = "" - when defined(Windows) and not defined(nimscript): - var err = getLastError() - if err != 0'i32: - when useWinUnicode: - var msgbuf: WideCString - if formatMessageW(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF, - nil, err, 0, addr(msgbuf), 0, nil) != 0'i32: - result = $msgbuf - if msgbuf != nil: localFree(cast[pointer](msgbuf)) - else: - var msgbuf: cstring - if formatMessageA(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF, - nil, err, 0, addr(msgbuf), 0, nil) != 0'i32: - result = $msgbuf - if msgbuf != nil: localFree(msgbuf) - when not defined(nimscript): - if errno != 0'i32: - result = $c_strerror(errno) - -{.push warning[deprecated]: off.} -proc raiseOSError*(msg: string = "") {.noinline, rtl, extern: "nos$1", - deprecated.} = - ## raises an OSError exception with the given message ``msg``. - ## If ``msg == ""``, the operating system's error flag - ## (``errno``) is converted to a readable error message. On Windows - ## ``GetLastError`` is checked before ``errno``. - ## If no error flag is set, the message ``unknown OS error`` is used. - ## - ## **Deprecated since version 0.9.4**: use the other ``raiseOSError`` proc. - if len(msg) == 0: - var m = osErrorMsg() - raise newException(OSError, if m.len > 0: m else: "unknown OS error") - else: - raise newException(OSError, msg) -{.pop.} - proc `==`*(err1, err2: OSErrorCode): bool {.borrow.} proc `$`*(err: OSErrorCode): string {.borrow.} @@ -104,13 +60,13 @@ proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} = if additionalInfo.len == 0: e.msg = osErrorMsg(errorCode) else: - e.msg = osErrorMsg(errorCode) & "\nAdditional info: " & additionalInfo + e.msg = osErrorMsg(errorCode) & "\nAdditional info: '" & additionalInfo & "'" if e.msg == "": e.msg = "unknown OS error" raise e {.push stackTrace:off.} -proc osLastError*(): OSErrorCode = +proc osLastError*(): OSErrorCode {.sideEffect.} = ## Retrieves the last operating system error code. ## ## This procedure is useful in the event when an OS call fails. In that case diff --git a/lib/pure/ioselects/ioselectors_epoll.nim b/lib/pure/ioselects/ioselectors_epoll.nim index 98b8a2b2b..8b3f14f34 100644 --- a/lib/pure/ioselects/ioselectors_epoll.nim +++ b/lib/pure/ioselects/ioselectors_epoll.nim @@ -48,16 +48,6 @@ when not defined(android): proc signalfd(fd: cint, mask: var Sigset, flags: cint): cint {.cdecl, importc: "signalfd", header: "<sys/signalfd.h>".} -var RLIMIT_NOFILE {.importc: "RLIMIT_NOFILE", - header: "<sys/resource.h>".}: cint -type - RLimit {.importc: "struct rlimit", - header: "<sys/resource.h>", pure, final.} = object - rlim_cur: int - rlim_max: int -proc getrlimit(resource: cint, rlp: var RLimit): cint - {.importc: "getrlimit",header: "<sys/resource.h>".} - when hasThreadSupport: type SelectorImpl[T] = object @@ -82,7 +72,7 @@ type proc newSelector*[T](): Selector[T] = # Retrieve the maximum fd count (for current OS) via getrlimit() var a = RLimit() - if getrlimit(RLIMIT_NOFILE, a) != 0: + if getrlimit(posix.RLIMIT_NOFILE, a) != 0: raiseOsError(osLastError()) var maxFD = int(a.rlim_max) doAssert(maxFD > 0) @@ -102,6 +92,9 @@ proc newSelector*[T](): Selector[T] = result.maxFD = maxFD result.fds = newSeq[SelectorKey[T]](maxFD) + for i in 0 ..< maxFD: + result.fds[i].ident = InvalidIdent + proc close*[T](s: Selector[T]) = let res = posix.close(s.epollFD) when hasThreadSupport: @@ -110,12 +103,6 @@ proc close*[T](s: Selector[T]) = if res != 0: raiseIOSelectorsError(osLastError()) -template clearKey[T](key: ptr SelectorKey[T]) = - var empty: T - key.ident = 0 - key.events = {} - key.data = empty - proc newSelectEvent*(): SelectEvent = let fdci = eventfd(0, 0) if fdci == -1: @@ -145,7 +132,7 @@ proc registerHandle*[T](s: Selector[T], fd: int | SocketHandle, events: set[Event], data: T) = let fdi = int(fd) s.checkFd(fdi) - doAssert(s.fds[fdi].ident == 0, "Descriptor $# already registered" % $fdi) + doAssert(s.fds[fdi].ident == InvalidIdent, "Descriptor $# already registered" % $fdi) s.setKey(fdi, events, 0, data) if events != {}: var epv = EpollEvent(events: EPOLLRDHUP) @@ -162,7 +149,7 @@ proc updateHandle*[T](s: Selector[T], fd: int | SocketHandle, events: set[Event] let fdi = int(fd) s.checkFd(fdi) var pkey = addr(s.fds[fdi]) - doAssert(pkey.ident != 0, + doAssert(pkey.ident != InvalidIdent, "Descriptor $# is not registered in the selector!" % $fdi) doAssert(pkey.events * maskEvents == {}) if pkey.events != events: @@ -190,7 +177,7 @@ proc unregister*[T](s: Selector[T], fd: int|SocketHandle) = let fdi = int(fd) s.checkFd(fdi) var pkey = addr(s.fds[fdi]) - doAssert(pkey.ident != 0, + doAssert(pkey.ident != InvalidIdent, "Descriptor $# is not registered in the selector!" % $fdi) if pkey.events != {}: when not defined(android): @@ -253,7 +240,7 @@ proc unregister*[T](s: Selector[T], ev: SelectEvent) = let fdi = int(ev.efd) s.checkFd(fdi) var pkey = addr(s.fds[fdi]) - doAssert(pkey.ident != 0, "Event is not registered in the queue!") + doAssert(pkey.ident != InvalidIdent, "Event is not registered in the queue!") doAssert(Event.User in pkey.events) var epv = EpollEvent() if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0: @@ -272,7 +259,7 @@ proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool, setNonBlocking(fdi.cint) s.checkFd(fdi) - doAssert(s.fds[fdi].ident == 0) + doAssert(s.fds[fdi].ident == InvalidIdent) var events = {Event.Timer} var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP) @@ -317,7 +304,7 @@ when not defined(android): setNonBlocking(fdi.cint) s.checkFd(fdi) - doAssert(s.fds[fdi].ident == 0) + doAssert(s.fds[fdi].ident == InvalidIdent) var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP) epv.data.u64 = fdi.uint @@ -344,7 +331,7 @@ when not defined(android): setNonBlocking(fdi.cint) s.checkFd(fdi) - doAssert(s.fds[fdi].ident == 0) + doAssert(s.fds[fdi].ident == InvalidIdent) var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP) epv.data.u64 = fdi.uint @@ -357,7 +344,7 @@ when not defined(android): proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) = let fdi = int(ev.efd) - doAssert(s.fds[fdi].ident == 0, "Event is already registered in the queue!") + doAssert(s.fds[fdi].ident == InvalidIdent, "Event is already registered in the queue!") s.setKey(fdi, {Event.User}, 0, data) var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP) epv.data.u64 = ev.efd.uint @@ -391,7 +378,7 @@ proc selectInto*[T](s: Selector[T], timeout: int, let fdi = int(resTable[i].data.u64) let pevents = resTable[i].events var pkey = addr(s.fds[fdi]) - doAssert(pkey.ident != 0) + doAssert(pkey.ident != InvalidIdent) var rkey = ReadyKey(fd: fdi, events: {}) if (pevents and EPOLLERR) != 0 or (pevents and EPOLLHUP) != 0: @@ -492,7 +479,7 @@ template isEmpty*[T](s: Selector[T]): bool = (s.count == 0) proc contains*[T](s: Selector[T], fd: SocketHandle|int): bool {.inline.} = - return s.fds[fd.int].ident != 0 + return s.fds[fd.int].ident != InvalidIdent proc getData*[T](s: Selector[T], fd: SocketHandle|int): var T = let fdi = int(fd) @@ -528,4 +515,4 @@ template withData*[T](s: Selector[T], fd: SocketHandle|int, value, body1, body2 proc getFd*[T](s: Selector[T]): int = - return s.epollFd.int \ No newline at end of file + return s.epollFd.int diff --git a/lib/pure/ioselects/ioselectors_kqueue.nim b/lib/pure/ioselects/ioselectors_kqueue.nim index 10e23c072..0e133f650 100644 --- a/lib/pure/ioselects/ioselectors_kqueue.nim +++ b/lib/pure/ioselects/ioselectors_kqueue.nim @@ -114,6 +114,9 @@ proc newSelector*[T](): Selector[T] = result.fds = newSeq[SelectorKey[T]](maxFD) result.changes = newSeqOfCap[KEvent](MAX_KQUEUE_EVENTS) + for i in 0 ..< maxFD: + result.fds[i].ident = InvalidIdent + result.sock = usock result.kqFD = kqFD result.maxFD = maxFD.int @@ -128,12 +131,6 @@ proc close*[T](s: Selector[T]) = if res1 != 0 or res2 != 0: raiseIOSelectorsError(osLastError()) -template clearKey[T](key: ptr SelectorKey[T]) = - var empty: T - key.ident = 0 - key.events = {} - key.data = empty - proc newSelectEvent*(): SelectEvent = var fds: array[2, cint] if posix.pipe(fds) != 0: @@ -221,7 +218,7 @@ proc registerHandle*[T](s: Selector[T], fd: int | SocketHandle, events: set[Event], data: T) = let fdi = int(fd) s.checkFd(fdi) - doAssert(s.fds[fdi].ident == 0) + doAssert(s.fds[fdi].ident == InvalidIdent) s.setKey(fdi, events, 0, data) if events != {}: @@ -242,7 +239,7 @@ proc updateHandle*[T](s: Selector[T], fd: int | SocketHandle, let fdi = int(fd) s.checkFd(fdi) var pkey = addr(s.fds[fdi]) - doAssert(pkey.ident != 0, + doAssert(pkey.ident != InvalidIdent, "Descriptor $# is not registered in the queue!" % $fdi) doAssert(pkey.events * maskEvents == {}) @@ -269,7 +266,7 @@ proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool, data: T): int {.discardable.} = let fdi = getUnique(s) s.checkFd(fdi) - doAssert(s.fds[fdi].ident == 0) + doAssert(s.fds[fdi].ident == InvalidIdent) let events = if oneshot: {Event.Timer, Event.Oneshot} else: {Event.Timer} let flags: cushort = if oneshot: EV_ONESHOT or EV_ADD else: EV_ADD @@ -291,7 +288,7 @@ proc registerSignal*[T](s: Selector[T], signal: int, data: T): int {.discardable.} = let fdi = getUnique(s) s.checkFd(fdi) - doAssert(s.fds[fdi].ident == 0) + doAssert(s.fds[fdi].ident == InvalidIdent) s.setKey(fdi, {Event.Signal}, signal, data) var nmask, omask: Sigset @@ -315,7 +312,7 @@ proc registerProcess*[T](s: Selector[T], pid: int, data: T): int {.discardable.} = let fdi = getUnique(s) s.checkFd(fdi) - doAssert(s.fds[fdi].ident == 0) + doAssert(s.fds[fdi].ident == InvalidIdent) var kflags: cushort = EV_ONESHOT or EV_ADD setKey(s, fdi, {Event.Process, Event.Oneshot}, pid, data) @@ -331,7 +328,7 @@ proc registerProcess*[T](s: Selector[T], pid: int, proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) = let fdi = ev.rfd.int - doAssert(s.fds[fdi].ident == 0, "Event is already registered in the queue!") + doAssert(s.fds[fdi].ident == InvalidIdent, "Event is already registered in the queue!") setKey(s, fdi, {Event.User}, 0, data) modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil) @@ -374,7 +371,7 @@ proc unregister*[T](s: Selector[T], fd: int|SocketHandle) = let fdi = int(fd) s.checkFd(fdi) var pkey = addr(s.fds[fdi]) - doAssert(pkey.ident != 0, + doAssert(pkey.ident != InvalidIdent, "Descriptor [" & $fdi & "] is not registered in the queue!") if pkey.events != {}: @@ -434,7 +431,7 @@ proc unregister*[T](s: Selector[T], ev: SelectEvent) = let fdi = int(ev.rfd) s.checkFd(fdi) var pkey = addr(s.fds[fdi]) - doAssert(pkey.ident != 0, "Event is not registered in the queue!") + doAssert(pkey.ident != InvalidIdent, "Event is not registered in the queue!") doAssert(Event.User in pkey.events) modifyKQueue(s, uint(fdi), EVFILT_READ, EV_DELETE, 0, 0, nil) when not declared(CACHE_EVENTS): @@ -570,8 +567,11 @@ proc selectInto*[T](s: Selector[T], timeout: int, doAssert(true, "Unsupported kqueue filter in the queue!") if (kevent.flags and EV_EOF) != 0: + # TODO this error handling needs to be rethought. + # `fflags` can sometimes be `0x80000000` and thus we use 'cast' + # here: if kevent.fflags != 0: - rkey.errorCode = kevent.fflags.OSErrorCode + rkey.errorCode = cast[OSErrorCode](kevent.fflags) else: # This assumes we are dealing with sockets. # TODO: For future-proofing it might be a good idea to give the @@ -593,7 +593,7 @@ template isEmpty*[T](s: Selector[T]): bool = (s.count == 0) proc contains*[T](s: Selector[T], fd: SocketHandle|int): bool {.inline.} = - return s.fds[fd.int].ident != 0 + return s.fds[fd.int].ident != InvalidIdent proc getData*[T](s: Selector[T], fd: SocketHandle|int): var T = let fdi = int(fd) diff --git a/lib/pure/ioselects/ioselectors_poll.nim b/lib/pure/ioselects/ioselectors_poll.nim index 66d52b352..9d708b0c1 100644 --- a/lib/pure/ioselects/ioselectors_poll.nim +++ b/lib/pure/ioselects/ioselectors_poll.nim @@ -40,16 +40,6 @@ type wfd: cint SelectEvent* = ptr SelectEventImpl -var RLIMIT_NOFILE {.importc: "RLIMIT_NOFILE", - header: "<sys/resource.h>".}: cint -type - rlimit {.importc: "struct rlimit", - header: "<sys/resource.h>", pure, final.} = object - rlim_cur: int - rlim_max: int -proc getrlimit(resource: cint, rlp: var rlimit): cint - {.importc: "getrlimit",header: "<sys/resource.h>".} - when hasThreadSupport: template withPollLock[T](s: Selector[T], body: untyped) = acquire(s.lock) @@ -63,8 +53,8 @@ else: body proc newSelector*[T](): Selector[T] = - var a = rlimit() - if getrlimit(RLIMIT_NOFILE, a) != 0: + var a = RLimit() + if getrlimit(posix.RLIMIT_NOFILE, a) != 0: raiseIOSelectorsError(osLastError()) var maxFD = int(a.rlim_max) @@ -80,6 +70,9 @@ proc newSelector*[T](): Selector[T] = result.fds = newSeq[SelectorKey[T]](maxFD) result.pollfds = newSeq[TPollFd](maxFD) + for i in 0 ..< maxFD: + result.fds[i].ident = InvalidIdent + proc close*[T](s: Selector[T]) = when hasThreadSupport: deinitLock(s.lock) @@ -87,12 +80,6 @@ proc close*[T](s: Selector[T]) = deallocSharedArray(s.pollfds) deallocShared(cast[pointer](s)) -template clearKey[T](key: ptr SelectorKey[T]) = - var empty: T - key.ident = 0 - key.events = {} - key.data = empty - template pollAdd[T](s: Selector[T], sock: cint, events: set[Event]) = withPollLock(s): var pollev: cshort = 0 @@ -145,7 +132,7 @@ proc registerHandle*[T](s: Selector[T], fd: int | SocketHandle, events: set[Event], data: T) = var fdi = int(fd) s.checkFd(fdi) - doAssert(s.fds[fdi].ident == 0) + doAssert(s.fds[fdi].ident == InvalidIdent) setKey(s, fdi, events, 0, data) if events != {}: s.pollAdd(fdi.cint, events) @@ -156,7 +143,7 @@ proc updateHandle*[T](s: Selector[T], fd: int | SocketHandle, let fdi = int(fd) s.checkFd(fdi) var pkey = addr(s.fds[fdi]) - doAssert(pkey.ident != 0, + doAssert(pkey.ident != InvalidIdent, "Descriptor [" & $fdi & "] is not registered in the queue!") doAssert(pkey.events * maskEvents == {}) @@ -172,7 +159,7 @@ proc updateHandle*[T](s: Selector[T], fd: int | SocketHandle, proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) = var fdi = int(ev.rfd) - doAssert(s.fds[fdi].ident == 0, "Event is already registered in the queue!") + doAssert(s.fds[fdi].ident == InvalidIdent, "Event is already registered in the queue!") var events = {Event.User} setKey(s, fdi, events, 0, data) events.incl(Event.Read) @@ -182,9 +169,9 @@ proc unregister*[T](s: Selector[T], fd: int|SocketHandle) = let fdi = int(fd) s.checkFd(fdi) var pkey = addr(s.fds[fdi]) - doAssert(pkey.ident != 0, + doAssert(pkey.ident != InvalidIdent, "Descriptor [" & $fdi & "] is not registered in the queue!") - pkey.ident = 0 + pkey.ident = InvalidIdent pkey.events = {} s.pollRemove(fdi.cint) @@ -192,9 +179,9 @@ proc unregister*[T](s: Selector[T], ev: SelectEvent) = let fdi = int(ev.rfd) s.checkFd(fdi) var pkey = addr(s.fds[fdi]) - doAssert(pkey.ident != 0, "Event is not registered in the queue!") + doAssert(pkey.ident != InvalidIdent, "Event is not registered in the queue!") doAssert(Event.User in pkey.events) - pkey.ident = 0 + pkey.ident = InvalidIdent pkey.events = {} s.pollRemove(fdi.cint) @@ -280,7 +267,7 @@ template isEmpty*[T](s: Selector[T]): bool = (s.count == 0) proc contains*[T](s: Selector[T], fd: SocketHandle|int): bool {.inline.} = - return s.fds[fd.int].ident != 0 + return s.fds[fd.int].ident != InvalidIdent proc getData*[T](s: Selector[T], fd: SocketHandle|int): var T = let fdi = int(fd) @@ -317,4 +304,4 @@ template withData*[T](s: Selector[T], fd: SocketHandle|int, value, body1, proc getFd*[T](s: Selector[T]): int = - return -1 \ No newline at end of file + return -1 diff --git a/lib/pure/ioselects/ioselectors_select.nim b/lib/pure/ioselects/ioselectors_select.nim index 7ed250307..521b31a64 100644 --- a/lib/pure/ioselects/ioselectors_select.nim +++ b/lib/pure/ioselects/ioselectors_select.nim @@ -99,6 +99,9 @@ proc newSelector*[T](): Selector[T] = result = Selector[T]() result.fds = newSeq[SelectorKey[T]](FD_SETSIZE) + for i in 0 ..< FD_SETSIZE: + result.fds[i].ident = InvalidIdent + IOFD_ZERO(addr result.rSet) IOFD_ZERO(addr result.wSet) IOFD_ZERO(addr result.eSet) @@ -195,7 +198,7 @@ proc setSelectKey[T](s: Selector[T], fd: SocketHandle, events: set[Event], var i = 0 let fdi = int(fd) while i < FD_SETSIZE: - if s.fds[i].ident == 0: + if s.fds[i].ident == InvalidIdent: var pkey = addr(s.fds[i]) pkey.ident = fdi pkey.events = events @@ -221,7 +224,7 @@ proc delKey[T](s: Selector[T], fd: SocketHandle) = var i = 0 while i < FD_SETSIZE: if s.fds[i].ident == fd.int: - s.fds[i].ident = 0 + s.fds[i].ident = InvalidIdent s.fds[i].events = {} s.fds[i].data = empty break @@ -307,7 +310,10 @@ proc selectInto*[T](s: Selector[T], timeout: int, var rset, wset, eset: FdSet if timeout != -1: - tv.tv_sec = timeout.int32 div 1_000 + when defined(genode): + tv.tv_sec = Time(timeout div 1_000) + else: + tv.tv_sec = timeout.int32 div 1_000 tv.tv_usec = (timeout.int32 %% 1_000) * 1_000 else: ptv = nil @@ -335,7 +341,7 @@ proc selectInto*[T](s: Selector[T], timeout: int, var k = 0 while (i < FD_SETSIZE) and (k < count): - if s.fds[i].ident != 0: + if s.fds[i].ident != InvalidIdent: var flag = false var pkey = addr(s.fds[i]) var rkey = ReadyKey(fd: int(pkey.ident), events: {}) @@ -388,7 +394,6 @@ proc contains*[T](s: Selector[T], fd: SocketHandle|int): bool {.inline.} = for i in 0..<FD_SETSIZE: if s.fds[i].ident == fdi: return true - inc(i) when hasThreadSupport: template withSelectLock[T](s: Selector[T], body: untyped) = diff --git a/lib/pure/json.nim b/lib/pure/json.nim index bbde4db5f..9279fea77 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -89,510 +89,22 @@ ## echo j2 import - hashes, tables, strutils, lexbase, streams, unicode, macros + hashes, tables, strutils, lexbase, streams, unicode, macros, parsejson export tables.`$` +export + parsejson.JsonEventKind, parsejson.JsonError, JsonParser, JsonKindError, + open, close, str, getInt, getFloat, kind, getColumn, getLine, getFilename, + errorMsg, errorMsgExpected, next, JsonParsingError, raiseParseErr + when defined(nimJsonGet): {.pragma: deprecatedGet, deprecated.} else: {.pragma: deprecatedGet.} type - JsonEventKind* = enum ## enumeration of all events that may occur when parsing - jsonError, ## an error occurred during parsing - jsonEof, ## end of file reached - jsonString, ## a string literal - jsonInt, ## an integer literal - jsonFloat, ## a float literal - jsonTrue, ## the value ``true`` - jsonFalse, ## the value ``false`` - jsonNull, ## the value ``null`` - jsonObjectStart, ## start of an object: the ``{`` token - jsonObjectEnd, ## end of an object: the ``}`` token - jsonArrayStart, ## start of an array: the ``[`` token - jsonArrayEnd ## start of an array: the ``]`` token - - TokKind = enum # must be synchronized with TJsonEventKind! - tkError, - tkEof, - tkString, - tkInt, - tkFloat, - tkTrue, - tkFalse, - tkNull, - tkCurlyLe, - tkCurlyRi, - tkBracketLe, - tkBracketRi, - tkColon, - tkComma - - JsonError* = enum ## enumeration that lists all errors that can occur - errNone, ## no error - errInvalidToken, ## invalid token - errStringExpected, ## string expected - errColonExpected, ## ``:`` expected - errCommaExpected, ## ``,`` expected - errBracketRiExpected, ## ``]`` expected - errCurlyRiExpected, ## ``}`` expected - errQuoteExpected, ## ``"`` or ``'`` expected - errEOC_Expected, ## ``*/`` expected - errEofExpected, ## EOF expected - errExprExpected ## expr expected - - ParserState = enum - stateEof, stateStart, stateObject, stateArray, stateExpectArrayComma, - stateExpectObjectComma, stateExpectColon, stateExpectValue - - JsonParser* = object of BaseLexer ## the parser object. - a: string - tok: TokKind - kind: JsonEventKind - err: JsonError - state: seq[ParserState] - filename: string - - JsonKindError* = object of ValueError ## raised by the ``to`` macro if the - ## JSON kind is incorrect. - -{.deprecated: [TJsonEventKind: JsonEventKind, TJsonError: JsonError, - TJsonParser: JsonParser, TTokKind: TokKind].} - -const - errorMessages: array[JsonError, string] = [ - "no error", - "invalid token", - "string expected", - "':' expected", - "',' expected", - "']' expected", - "'}' expected", - "'\"' or \"'\" expected", - "'*/' expected", - "EOF expected", - "expression expected" - ] - tokToStr: array[TokKind, string] = [ - "invalid token", - "EOF", - "string literal", - "int literal", - "float literal", - "true", - "false", - "null", - "{", "}", "[", "]", ":", "," - ] - -proc open*(my: var JsonParser, input: Stream, filename: string) = - ## initializes the parser with an input stream. `Filename` is only used - ## for nice error messages. - lexbase.open(my, input) - my.filename = filename - my.state = @[stateStart] - my.kind = jsonError - my.a = "" - -proc close*(my: var JsonParser) {.inline.} = - ## closes the parser `my` and its associated input stream. - lexbase.close(my) - -proc str*(my: JsonParser): string {.inline.} = - ## returns the character data for the events: ``jsonInt``, ``jsonFloat``, - ## ``jsonString`` - assert(my.kind in {jsonInt, jsonFloat, jsonString}) - return my.a - -proc getInt*(my: JsonParser): BiggestInt {.inline.} = - ## returns the number for the event: ``jsonInt`` - assert(my.kind == jsonInt) - return parseBiggestInt(my.a) - -proc getFloat*(my: JsonParser): float {.inline.} = - ## returns the number for the event: ``jsonFloat`` - assert(my.kind == jsonFloat) - return parseFloat(my.a) - -proc kind*(my: JsonParser): JsonEventKind {.inline.} = - ## returns the current event type for the JSON parser - return my.kind - -proc getColumn*(my: JsonParser): int {.inline.} = - ## get the current column the parser has arrived at. - result = getColNumber(my, my.bufpos) - -proc getLine*(my: JsonParser): int {.inline.} = - ## get the current line the parser has arrived at. - result = my.lineNumber - -proc getFilename*(my: JsonParser): string {.inline.} = - ## get the filename of the file that the parser processes. - result = my.filename - -proc errorMsg*(my: JsonParser): string = - ## returns a helpful error message for the event ``jsonError`` - assert(my.kind == jsonError) - result = "$1($2, $3) Error: $4" % [ - my.filename, $getLine(my), $getColumn(my), errorMessages[my.err]] - -proc errorMsgExpected*(my: JsonParser, e: string): string = - ## returns an error message "`e` expected" in the same format as the - ## other error messages - result = "$1($2, $3) Error: $4" % [ - my.filename, $getLine(my), $getColumn(my), e & " expected"] - -proc handleHexChar(c: char, x: var int): bool = - result = true # Success - case c - of '0'..'9': x = (x shl 4) or (ord(c) - ord('0')) - of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10) - of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10) - else: result = false # error - -proc parseEscapedUTF16(buf: cstring, pos: var int): int = - result = 0 - #UTF-16 escape is always 4 bytes. - for _ in 0..3: - if handleHexChar(buf[pos], result): - inc(pos) - else: - return -1 - -proc parseString(my: var JsonParser): TokKind = - result = tkString - var pos = my.bufpos + 1 - var buf = my.buf - while true: - case buf[pos] - of '\0': - my.err = errQuoteExpected - result = tkError - break - of '"': - inc(pos) - break - of '\\': - case buf[pos+1] - of '\\', '"', '\'', '/': - add(my.a, buf[pos+1]) - inc(pos, 2) - of 'b': - add(my.a, '\b') - inc(pos, 2) - of 'f': - add(my.a, '\f') - inc(pos, 2) - of 'n': - add(my.a, '\L') - inc(pos, 2) - of 'r': - add(my.a, '\C') - inc(pos, 2) - of 't': - add(my.a, '\t') - inc(pos, 2) - of 'u': - inc(pos, 2) - var r = parseEscapedUTF16(buf, pos) - if r < 0: - my.err = errInvalidToken - break - # Deal with surrogates - if (r and 0xfc00) == 0xd800: - if buf[pos] & buf[pos+1] != "\\u": - my.err = errInvalidToken - break - inc(pos, 2) - var s = parseEscapedUTF16(buf, pos) - if (s and 0xfc00) == 0xdc00 and s > 0: - r = 0x10000 + (((r - 0xd800) shl 10) or (s - 0xdc00)) - else: - my.err = errInvalidToken - break - add(my.a, toUTF8(Rune(r))) - else: - # don't bother with the error - add(my.a, buf[pos]) - inc(pos) - of '\c': - pos = lexbase.handleCR(my, pos) - buf = my.buf - add(my.a, '\c') - of '\L': - pos = lexbase.handleLF(my, pos) - buf = my.buf - add(my.a, '\L') - else: - add(my.a, buf[pos]) - inc(pos) - my.bufpos = pos # store back - -proc skip(my: var JsonParser) = - var pos = my.bufpos - var buf = my.buf - while true: - case buf[pos] - of '/': - if buf[pos+1] == '/': - # skip line comment: - inc(pos, 2) - while true: - case buf[pos] - of '\0': - break - of '\c': - pos = lexbase.handleCR(my, pos) - buf = my.buf - break - of '\L': - pos = lexbase.handleLF(my, pos) - buf = my.buf - break - else: - inc(pos) - elif buf[pos+1] == '*': - # skip long comment: - inc(pos, 2) - while true: - case buf[pos] - of '\0': - my.err = errEOC_Expected - break - of '\c': - pos = lexbase.handleCR(my, pos) - buf = my.buf - of '\L': - pos = lexbase.handleLF(my, pos) - buf = my.buf - of '*': - inc(pos) - if buf[pos] == '/': - inc(pos) - break - else: - inc(pos) - else: - break - of ' ', '\t': - inc(pos) - of '\c': - pos = lexbase.handleCR(my, pos) - buf = my.buf - of '\L': - pos = lexbase.handleLF(my, pos) - buf = my.buf - else: - break - my.bufpos = pos - -proc parseNumber(my: var JsonParser) = - var pos = my.bufpos - var buf = my.buf - if buf[pos] == '-': - add(my.a, '-') - inc(pos) - if buf[pos] == '.': - add(my.a, "0.") - inc(pos) - else: - while buf[pos] in Digits: - add(my.a, buf[pos]) - inc(pos) - if buf[pos] == '.': - add(my.a, '.') - inc(pos) - # digits after the dot: - while buf[pos] in Digits: - add(my.a, buf[pos]) - inc(pos) - if buf[pos] in {'E', 'e'}: - add(my.a, buf[pos]) - inc(pos) - if buf[pos] in {'+', '-'}: - add(my.a, buf[pos]) - inc(pos) - while buf[pos] in Digits: - add(my.a, buf[pos]) - inc(pos) - my.bufpos = pos - -proc parseName(my: var JsonParser) = - var pos = my.bufpos - var buf = my.buf - if buf[pos] in IdentStartChars: - while buf[pos] in IdentChars: - add(my.a, buf[pos]) - inc(pos) - my.bufpos = pos - -proc getTok(my: var JsonParser): TokKind = - setLen(my.a, 0) - skip(my) # skip whitespace, comments - case my.buf[my.bufpos] - of '-', '.', '0'..'9': - parseNumber(my) - if {'.', 'e', 'E'} in my.a: - result = tkFloat - else: - result = tkInt - of '"': - result = parseString(my) - of '[': - inc(my.bufpos) - result = tkBracketLe - of '{': - inc(my.bufpos) - result = tkCurlyLe - of ']': - inc(my.bufpos) - result = tkBracketRi - of '}': - inc(my.bufpos) - result = tkCurlyRi - of ',': - inc(my.bufpos) - result = tkComma - of ':': - inc(my.bufpos) - result = tkColon - of '\0': - result = tkEof - of 'a'..'z', 'A'..'Z', '_': - parseName(my) - case my.a - of "null": result = tkNull - of "true": result = tkTrue - of "false": result = tkFalse - else: result = tkError - else: - inc(my.bufpos) - result = tkError - my.tok = result - -proc next*(my: var JsonParser) = - ## retrieves the first/next event. This controls the parser. - var tk = getTok(my) - var i = my.state.len-1 - # the following code is a state machine. If we had proper coroutines, - # the code could be much simpler. - case my.state[i] - of stateEof: - if tk == tkEof: - my.kind = jsonEof - else: - my.kind = jsonError - my.err = errEofExpected - of stateStart: - # tokens allowed? - case tk - of tkString, tkInt, tkFloat, tkTrue, tkFalse, tkNull: - my.state[i] = stateEof # expect EOF next! - my.kind = JsonEventKind(ord(tk)) - of tkBracketLe: - my.state.add(stateArray) # we expect any - my.kind = jsonArrayStart - of tkCurlyLe: - my.state.add(stateObject) - my.kind = jsonObjectStart - of tkEof: - my.kind = jsonEof - else: - my.kind = jsonError - my.err = errEofExpected - of stateObject: - case tk - of tkString, tkInt, tkFloat, tkTrue, tkFalse, tkNull: - my.state.add(stateExpectColon) - my.kind = JsonEventKind(ord(tk)) - of tkBracketLe: - my.state.add(stateExpectColon) - my.state.add(stateArray) - my.kind = jsonArrayStart - of tkCurlyLe: - my.state.add(stateExpectColon) - my.state.add(stateObject) - my.kind = jsonObjectStart - of tkCurlyRi: - my.kind = jsonObjectEnd - discard my.state.pop() - else: - my.kind = jsonError - my.err = errCurlyRiExpected - of stateArray: - case tk - of tkString, tkInt, tkFloat, tkTrue, tkFalse, tkNull: - my.state.add(stateExpectArrayComma) # expect value next! - my.kind = JsonEventKind(ord(tk)) - of tkBracketLe: - my.state.add(stateExpectArrayComma) - my.state.add(stateArray) - my.kind = jsonArrayStart - of tkCurlyLe: - my.state.add(stateExpectArrayComma) - my.state.add(stateObject) - my.kind = jsonObjectStart - of tkBracketRi: - my.kind = jsonArrayEnd - discard my.state.pop() - else: - my.kind = jsonError - my.err = errBracketRiExpected - of stateExpectArrayComma: - case tk - of tkComma: - discard my.state.pop() - next(my) - of tkBracketRi: - my.kind = jsonArrayEnd - discard my.state.pop() # pop stateExpectArrayComma - discard my.state.pop() # pop stateArray - else: - my.kind = jsonError - my.err = errBracketRiExpected - of stateExpectObjectComma: - case tk - of tkComma: - discard my.state.pop() - next(my) - of tkCurlyRi: - my.kind = jsonObjectEnd - discard my.state.pop() # pop stateExpectObjectComma - discard my.state.pop() # pop stateObject - else: - my.kind = jsonError - my.err = errCurlyRiExpected - of stateExpectColon: - case tk - of tkColon: - my.state[i] = stateExpectValue - next(my) - else: - my.kind = jsonError - my.err = errColonExpected - of stateExpectValue: - case tk - of tkString, tkInt, tkFloat, tkTrue, tkFalse, tkNull: - my.state[i] = stateExpectObjectComma - my.kind = JsonEventKind(ord(tk)) - of tkBracketLe: - my.state[i] = stateExpectObjectComma - my.state.add(stateArray) - my.kind = jsonArrayStart - of tkCurlyLe: - my.state[i] = stateExpectObjectComma - my.state.add(stateObject) - my.kind = jsonObjectStart - else: - my.kind = jsonError - my.err = errExprExpected - - -# ------------- higher level interface --------------------------------------- - -type JsonNodeKind* = enum ## possible JSON node types JNull, JBool, @@ -620,15 +132,6 @@ type of JArray: elems*: seq[JsonNode] - JsonParsingError* = object of ValueError ## is raised for a JSON error - -{.deprecated: [EJsonParsingError: JsonParsingError, TJsonNode: JsonNodeObj, - PJsonNode: JsonNode, TJsonNodeKind: JsonNodeKind].} - -proc raiseParseErr*(p: JsonParser, msg: string) {.noinline, noreturn.} = - ## raises an `EJsonParsingError` exception. - raise newException(JsonParsingError, errorMsgExpected(p, msg)) - proc newJString*(s: string): JsonNode = ## Creates a new `JString JsonNode`. new(result) @@ -695,8 +198,8 @@ proc getBiggestInt*(n: JsonNode, default: BiggestInt = 0): BiggestInt = if n.isNil or n.kind != JInt: return default else: return n.num -proc getNum*(n: JsonNode, default: BiggestInt = 0): BiggestInt {.deprecated.} = - ## Deprecated - use getInt or getBiggestInt instead +proc getNum*(n: JsonNode, default: BiggestInt = 0): BiggestInt {.deprecated: "use getInt or getBiggestInt instead".} = + ## **Deprecated since v0.18.2:** use ``getInt`` or ``getBiggestInt`` instead. getBiggestInt(n, default) proc getFloat*(n: JsonNode, default: float = 0.0): float = @@ -709,8 +212,8 @@ proc getFloat*(n: JsonNode, default: float = 0.0): float = of JInt: return float(n.num) else: return default -proc getFNum*(n: JsonNode, default: float = 0.0): float {.deprecated.} = - ## Deprecated - use getFloat instead +proc getFNum*(n: JsonNode, default: float = 0.0): float {.deprecated: "use getFloat instead".} = + ## **Deprecated since v0.18.2:** use ``getFloat`` instead. getFloat(n, default) proc getBool*(n: JsonNode, default: bool = false): bool = @@ -720,8 +223,8 @@ proc getBool*(n: JsonNode, default: bool = false): bool = if n.isNil or n.kind != JBool: return default else: return n.bval -proc getBVal*(n: JsonNode, default: bool = false): bool {.deprecated.} = - ## Deprecated - use getBVal instead +proc getBVal*(n: JsonNode, default: bool = false): bool {.deprecated: "use getBool instead".} = + ## **Deprecated since v0.18.2:** use ``getBool`` instead. getBool(n, default) proc getFields*(n: JsonNode, @@ -734,7 +237,7 @@ proc getFields*(n: JsonNode, else: return n.fields proc getElems*(n: JsonNode, default: seq[JsonNode] = @[]): seq[JsonNode] = - ## Retrieves the int value of a `JArray JsonNode`. + ## Retrieves the array of a `JArray JsonNode`. ## ## Returns ``default`` if ``n`` is not a ``JArray``, or if ``n`` is nil. if n.isNil or n.kind != JArray: return default @@ -753,7 +256,6 @@ proc add*(obj: JsonNode, key: string, val: JsonNode) = proc `%`*(s: string): JsonNode = ## Generic constructor for JSON data. Creates a new `JString JsonNode`. new(result) - if s.isNil: return result.kind = JString result.str = s @@ -825,21 +327,24 @@ proc toJson(x: NimNode): NimNode {.compiletime.} = result = newNimNode(nnkBracket) for i in 0 ..< x.len: result.add(toJson(x[i])) - result = newCall(bindSym"%", result) + result = newCall(bindSym("%", brOpen), result) of nnkTableConstr: # object if x.len == 0: return newCall(bindSym"newJObject") result = newNimNode(nnkTableConstr) for i in 0 ..< x.len: x[i].expectKind nnkExprColonExpr result.add newTree(nnkExprColonExpr, x[i][0], toJson(x[i][1])) - result = newCall(bindSym"%", result) + result = newCall(bindSym("%", brOpen), result) of nnkCurly: # empty object x.expectLen(0) result = newCall(bindSym"newJObject") of nnkNilLit: result = newCall(bindSym"newJNull") + of nnkPar: + if x.len == 1: result = toJson(x[0]) + else: result = newCall(bindSym("%", brOpen), x) else: - result = newCall(bindSym"%", x) + result = newCall(bindSym("%", brOpen), x) macro `%*`*(x: untyped): untyped = ## Convert an expression to a JsonNode directly, without having to specify @@ -946,8 +451,8 @@ proc contains*(node: JsonNode, val: JsonNode): bool = assert(node.kind == JArray) find(node.elems, val) >= 0 -proc existsKey*(node: JsonNode, key: string): bool {.deprecated.} = node.hasKey(key) - ## Deprecated for `hasKey` +proc existsKey*(node: JsonNode, key: string): bool {.deprecated: "use hasKey instead".} = node.hasKey(key) + ## **Deprecated:** use `hasKey` instead. proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) {.inline.} = ## Sets a field from a `JObject`. @@ -958,12 +463,29 @@ proc `{}`*(node: JsonNode, keys: varargs[string]): JsonNode = ## Traverses the node and gets the given value. If any of the ## keys do not exist, returns ``nil``. Also returns ``nil`` if one of the ## intermediate data structures is not an object. + ## + ## This proc can be used to create tree structures on the + ## fly (sometimes called `autovivification`:idx:): + ## + ## .. code-block:: nim + ## myjson{"parent", "child", "grandchild"} = newJInt(1) + ## result = node for key in keys: if isNil(result) or result.kind != JObject: return nil result = result.fields.getOrDefault(key) +proc `{}`*(node: JsonNode, index: varargs[int]): JsonNode = + ## Traverses the node and gets the given value. If any of the + ## indexes do not exist, returns ``nil``. Also returns ``nil`` if one of the + ## intermediate data structures is not an array. + result = node + for i in index: + if isNil(result) or result.kind != JArray or i >= node.len: + return nil + result = result.elems[i] + proc getOrDefault*(node: JsonNode, key: string): JsonNode = ## Gets a field from a `node`. If `node` is nil or not an object or ## value at `key` does not exist, returns nil @@ -986,7 +508,7 @@ proc delete*(obj: JsonNode, key: string) = ## Deletes ``obj[key]``. assert(obj.kind == JObject) if not obj.fields.hasKey(key): - raise newException(IndexError, "key not in object") + raise newException(KeyError, "key not in object") obj.fields.del(key) proc copy*(p: JsonNode): JsonNode = @@ -1023,10 +545,9 @@ proc newIndent(curr, indent: int, ml: bool): int = proc nl(s: var string, ml: bool) = s.add(if ml: "\n" else: " ") -proc escapeJson*(s: string; result: var string) = - ## Converts a string `s` to its JSON representation. +proc escapeJsonUnquoted*(s: string; result: var string) = + ## Converts a string `s` to its JSON representation without quotes. ## Appends to ``result``. - result.add("\"") for c in s: case c of '\L': result.add("\\n") @@ -1035,12 +556,25 @@ proc escapeJson*(s: string; result: var string) = of '\t': result.add("\\t") of '\r': result.add("\\r") of '"': result.add("\\\"") + of '\0'..'\7': result.add("\\u000" & $ord(c)) + of '\14'..'\31': result.add("\\u00" & $ord(c)) of '\\': result.add("\\\\") else: result.add(c) + +proc escapeJsonUnquoted*(s: string): string = + ## Converts a string `s` to its JSON representation without quotes. + result = newStringOfCap(s.len + s.len shr 3) + escapeJsonUnquoted(s, result) + +proc escapeJson*(s: string; result: var string) = + ## Converts a string `s` to its JSON representation with quotes. + ## Appends to ``result``. + result.add("\"") + escapeJsonUnquoted(s, result) result.add("\"") proc escapeJson*(s: string): string = - ## Converts a string `s` to its JSON representation. + ## Converts a string `s` to its JSON representation with quotes. result = newStringOfCap(s.len + s.len shr 3) escapeJson(s, result) @@ -1180,10 +714,6 @@ iterator mpairs*(node: var JsonNode): tuple[key: string, val: var JsonNode] = for key, val in mpairs(node.fields): yield (key, val) -proc eat(p: var JsonParser, tok: TokKind) = - if p.tok == tok: discard getTok(p) - else: raiseParseErr(p, tokToStr[tok]) - proc parseJson(p: var JsonParser): JsonNode = ## Parses JSON from a JSON Parser `p`. case p.tok @@ -1239,10 +769,12 @@ when not defined(js): ## If `s` contains extra data, it will raise `JsonParsingError`. var p: JsonParser p.open(s, filename) - defer: p.close() - discard getTok(p) # read first token - result = p.parseJson() - eat(p, tkEof) # check if there is no extra data + try: + discard getTok(p) # read first token + result = p.parseJson() + eat(p, tkEof) # check if there is no extra data + finally: + p.close() proc parseJson*(buffer: string): JsonNode = ## Parses JSON from `buffer`. @@ -1260,7 +792,6 @@ else: from math import `mod` type JSObject = object - {.deprecated: [TJSObject: JSObject].} proc parseNativeJson(x: cstring): JSObject {.importc: "JSON.parse".} @@ -1482,6 +1013,13 @@ proc processElseBranch(recCaseNode, elseBranch, jsonNode, kindType, exprColonExpr.add(ifStmt) proc createConstructor(typeSym, jsonNode: NimNode): NimNode {.compileTime.} + +proc detectDistinctType(typeSym: NimNode): NimNode = + let + typeImpl = getTypeImpl(typeSym) + typeInst = getTypeInst(typeSym) + result = if typeImpl.typeKind == ntyDistinct: typeImpl else: typeInst + proc processObjField(field, jsonNode: NimNode): seq[NimNode] = ## Process a field from a ``RecList``. ## @@ -1500,8 +1038,8 @@ proc processObjField(field, jsonNode: NimNode): seq[NimNode] = # Add the field value. # -> jsonNode["`field`"] let indexedJsonNode = createJsonIndexer(jsonNode, $field) - exprColonExpr.add(createConstructor(getTypeInst(field), indexedJsonNode)) - + let typeNode = detectDistinctType(field) + exprColonExpr.add(createConstructor(typeNode, indexedJsonNode)) of nnkRecCase: # A "case" field that introduces a variant. let exprColonExpr = newNimNode(nnkExprColonExpr) @@ -1615,7 +1153,7 @@ proc processType(typeName: NimNode, obj: NimNode, result = quote do: ( verifyJsonKind(`jsonNode`, {JString, JNull}, astToStr(`jsonNode`)); - if `jsonNode`.kind == JNull: nil else: `jsonNode`.str + if `jsonNode`.kind == JNull: "" else: `jsonNode`.str ) of "biggestint": result = quote do: @@ -1687,7 +1225,7 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode = result = quote do: ( - if `lenientJsonNode`.isNil: `workaround`[`optionGeneric`]() else: some[`optionGeneric`](`value`) + if `lenientJsonNode`.isNil or `jsonNode`.kind == JNull: `workaround`[`optionGeneric`]() else: some[`optionGeneric`](`value`) ) of "table", "orderedtable": let tableKeyType = typeSym[1] @@ -1726,7 +1264,7 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode = let seqT = typeSym[1] let forLoopI = genSym(nskForVar, "i") let indexerNode = createJsonIndexer(jsonNode, forLoopI) - let constructorNode = createConstructor(seqT, indexerNode) + let constructorNode = createConstructor(detectDistinctType(seqT), indexerNode) # Create a statement expression containing a for loop. result = quote do: @@ -1762,17 +1300,35 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode = # Handle all other types. let obj = getType(typeSym) - if obj.kind == nnkBracketExpr: + let typeNode = getTypeImpl(typeSym) + if typeNode.typeKind == ntyDistinct: + result = createConstructor(typeNode, jsonNode) + elif obj.kind == nnkBracketExpr: # When `Sym "Foo"` turns out to be a `ref object`. result = createConstructor(obj, jsonNode) else: result = processType(typeSym, obj, jsonNode, false) of nnkTupleTy: result = processType(typeSym, typeSym, jsonNode, false) - of nnkPar: + of nnkPar, nnkTupleConstr: # TODO: The fact that `jsonNode` here works to give a good line number # is weird. Specifying typeSym should work but doesn't. error("Use a named tuple instead of: " & $toStrLit(typeSym), jsonNode) + of nnkDistinctTy: + var baseType = typeSym + # solve nested distinct types + while baseType.typeKind == ntyDistinct: + let impl = getTypeImpl(baseType[0]) + if impl.typeKind != ntyDistinct: + baseType = baseType[0] + break + baseType = impl + let ret = createConstructor(baseType, jsonNode) + let typeInst = getTypeInst(typeSym) + result = quote do: + ( + `typeInst`(`ret`) + ) else: doAssert false, "Unable to create constructor for: " & $typeSym.kind @@ -1896,7 +1452,7 @@ macro to*(node: JsonNode, T: typedesc): untyped = ## doAssert data.person.age == 21 ## doAssert data.list == @[1, 2, 3, 4] - let typeNode = getTypeInst(T) + let typeNode = getTypeImpl(T) expectKind(typeNode, nnkBracketExpr) doAssert(($typeNode[0]).normalize == "typedesc") @@ -1974,22 +1530,23 @@ when isMainModule: doAssert parsedAgain["abc"].num == 5 # Bounds checking - try: - let a = testJson["a"][9] - doAssert(false, "EInvalidIndex not thrown") - except IndexError: - discard - try: - let a = testJson["a"][-1] - doAssert(false, "EInvalidIndex not thrown") - except IndexError: - discard - try: - doAssert(testJson["a"][0].num == 1, "Index doesn't correspond to its value") - except: - doAssert(false, "EInvalidIndex thrown for valid index") - - doAssert(testJson{"b"}.str=="asd", "Couldn't fetch a singly nested key with {}") + when compileOption("boundChecks"): + try: + let a = testJson["a"][9] + doAssert(false, "IndexError not thrown") + except IndexError: + discard + try: + let a = testJson["a"][-1] + doAssert(false, "IndexError not thrown") + except IndexError: + discard + try: + doAssert(testJson["a"][0].num == 1, "Index doesn't correspond to its value") + except: + doAssert(false, "IndexError thrown for valid index") + + doAssert(testJson{"b"}.getStr()=="asd", "Couldn't fetch a singly nested key with {}") doAssert(isNil(testJson{"nonexistent"}), "Non-existent keys should return nil") doAssert(isNil(testJson{"a", "b"}), "Indexing through a list should return nil") doAssert(isNil(testJson{"a", "b"}), "Indexing through a list should return nil") @@ -2060,7 +1617,10 @@ when isMainModule: var parsed2 = parseFile("tests/testdata/jsontest2.json") doAssert(parsed2{"repository", "description"}.str=="IRC Library for Haskell", "Couldn't fetch via multiply nested key using {}") + doAssert escapeJsonUnquoted("\10Foo🎃barÄ") == "\\nFoo🎃barÄ" + doAssert escapeJsonUnquoted("\0\7\20") == "\\u0000\\u0007\\u0020" # for #7887 doAssert escapeJson("\10Foo🎃barÄ") == "\"\\nFoo🎃barÄ\"" + doAssert escapeJson("\0\7\20") == "\"\\u0000\\u0007\\u0020\"" # for #7887 # Test with extra data when not defined(js): @@ -2079,5 +1639,3 @@ when isMainModule: # bug #6438 doAssert($ %*[] == "[]") doAssert($ %*{} == "{}") - - echo("Tests succeeded!") diff --git a/lib/pure/lenientops.nim b/lib/pure/lenientops.nim index b124a9512..cc7784c19 100644 --- a/lib/pure/lenientops.nim +++ b/lib/pure/lenientops.nim @@ -26,33 +26,33 @@ import typetraits -proc `+`*[I: SomeInteger, F: SomeReal](i: I, f: F): F {.noSideEffect, inline.} = +proc `+`*[I: SomeInteger, F: SomeFloat](i: I, f: F): F {.noSideEffect, inline.} = F(i) + f -proc `+`*[I: SomeInteger, F: SomeReal](f: F, i: I): F {.noSideEffect, inline.} = +proc `+`*[I: SomeInteger, F: SomeFloat](f: F, i: I): F {.noSideEffect, inline.} = f + F(i) -proc `-`*[I: SomeInteger, F: SomeReal](i: I, f: F): F {.noSideEffect, inline.} = +proc `-`*[I: SomeInteger, F: SomeFloat](i: I, f: F): F {.noSideEffect, inline.} = F(i) - f -proc `-`*[I: SomeInteger, F: SomeReal](f: F, i: I): F {.noSideEffect, inline.} = +proc `-`*[I: SomeInteger, F: SomeFloat](f: F, i: I): F {.noSideEffect, inline.} = f - F(i) -proc `*`*[I: SomeInteger, F: SomeReal](i: I, f: F): F {.noSideEffect, inline.} = +proc `*`*[I: SomeInteger, F: SomeFloat](i: I, f: F): F {.noSideEffect, inline.} = F(i) * f -proc `*`*[I: SomeInteger, F: SomeReal](f: F, i: I): F {.noSideEffect, inline.} = +proc `*`*[I: SomeInteger, F: SomeFloat](f: F, i: I): F {.noSideEffect, inline.} = f * F(i) -proc `/`*[I: SomeInteger, F: SomeReal](i: I, f: F): F {.noSideEffect, inline.} = +proc `/`*[I: SomeInteger, F: SomeFloat](i: I, f: F): F {.noSideEffect, inline.} = F(i) / f -proc `/`*[I: SomeInteger, F: SomeReal](f: F, i: I): F {.noSideEffect, inline.} = +proc `/`*[I: SomeInteger, F: SomeFloat](f: F, i: I): F {.noSideEffect, inline.} = f / F(i) -proc `<`*[I: SomeInteger, F: SomeReal](i: I, f: F): bool {.noSideEffect, inline.} = +proc `<`*[I: SomeInteger, F: SomeFloat](i: I, f: F): bool {.noSideEffect, inline.} = F(i) < f -proc `<`*[I: SomeInteger, F: SomeReal](f: F, i: I): bool {.noSideEffect, inline.} = +proc `<`*[I: SomeInteger, F: SomeFloat](f: F, i: I): bool {.noSideEffect, inline.} = f < F(i) -proc `<=`*[I: SomeInteger, F: SomeReal](i: I, f: F): bool {.noSideEffect, inline.} = +proc `<=`*[I: SomeInteger, F: SomeFloat](i: I, f: F): bool {.noSideEffect, inline.} = F(i) <= f -proc `<=`*[I: SomeInteger, F: SomeReal](f: F, i: I): bool {.noSideEffect, inline.} = +proc `<=`*[I: SomeInteger, F: SomeFloat](f: F, i: I): bool {.noSideEffect, inline.} = f <= F(i) # Note that we must not defined `>=` and `>`, because system.nim already has a diff --git a/lib/pure/lexbase.nim b/lib/pure/lexbase.nim index 15a390f0b..e38acd5ef 100644 --- a/lib/pure/lexbase.nim +++ b/lib/pure/lexbase.nim @@ -40,8 +40,6 @@ type offsetBase*: int # use ``offsetBase + bufpos`` to get the offset refillChars: set[char] -{.deprecated: [TBaseLexer: BaseLexer].} - const chrSize = sizeof(char) diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index 751fc0e8d..f2f5cac9e 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -96,10 +96,6 @@ when not defined(js): logFiles: int # how many log files already created, e.g. basename.1, basename.2... bufSize: int # size of output buffer (-1: use system defaults, 0: unbuffered, >0: fixed buffer size) - {.deprecated: [PFileLogger: FileLogger, PRollingFileLogger: RollingFileLogger].} - -{.deprecated: [TLevel: Level, PLogger: Logger, PConsoleLogger: ConsoleLogger].} - var level {.threadvar.}: Level ## global log filter handlers {.threadvar.}: seq[Logger] ## handlers with their own log levels @@ -107,14 +103,9 @@ var proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): string = ## Format a log message using the ``frmt`` format string, ``level`` and varargs. ## See the module documentation for the format string syntax. - const nilString = "nil" - var msgLen = 0 for arg in args: - if arg.isNil: - msgLen += nilString.len - else: - msgLen += arg.len + msgLen += arg.len result = newStringOfCap(frmt.len + msgLen + 20) var i = 0 while i < frmt.len: @@ -126,7 +117,7 @@ proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): str var v = "" let app = when defined(js): "" else: getAppFilename() while frmt[i] in IdentChars: - v.add(toLower(frmt[i])) + v.add(toLowerAscii(frmt[i])) inc(i) case v of "date": result.add(getDateStr()) @@ -141,10 +132,7 @@ proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): str of "levelname": result.add(LevelNames[level]) else: discard for arg in args: - if arg.isNil: - result.add(nilString) - else: - result.add(arg) + result.add(arg) method log*(logger: Logger, level: Level, args: varargs[string, `$`]) {. raises: [Exception], gcsafe, @@ -342,7 +330,6 @@ template fatal*(args: varargs[string, `$`]) = proc addHandler*(handler: Logger) = ## Adds ``handler`` to the list of handlers. - if handlers.isNil: handlers = @[] handlers.add(handler) proc getHandlers*(): seq[Logger] = diff --git a/lib/pure/marshal.nim b/lib/pure/marshal.nim index 6ee830786..b0bcfe535 100644 --- a/lib/pure/marshal.nim +++ b/lib/pure/marshal.nim @@ -98,8 +98,7 @@ proc storeAny(s: Stream, a: Any, stored: var IntSet) = of akProc, akPointer, akCString: s.write($a.getPointer.ptrToInt) of akString: var x = getString(a) - if isNil(x): s.write("null") - elif x.validateUtf8() == -1: s.write(escapeJson(x)) + if x.validateUtf8() == -1: s.write(escapeJson(x)) else: s.write("[") var i = 0 @@ -270,6 +269,8 @@ proc store*[T](s: Stream, data: T) = proc `$$`*[T](x: T): string = ## returns a string representation of `x`. + ## + ## Note: to serialize `x` to JSON use $(%x) from the ``json`` module var stored = initIntSet() var d: T shallowCopy(d, x) @@ -279,6 +280,17 @@ proc `$$`*[T](x: T): string = proc to*[T](data: string): T = ## reads data and transforms it to a ``T``. + runnableExamples: + type + Foo = object + id: int + bar: string + + let x = Foo(id: 1, bar: "baz") + # serialize + let y = ($$x) + # deserialize back to type 'Foo': + let z = y.to[:Foo] var tab = initTable[BiggestInt, pointer]() loadAny(newStringStream(data), toAny(result), tab) @@ -309,7 +321,6 @@ when not defined(testing) and isMainModule: Node = object next, prev: PNode data: string - {.deprecated: [TNode: Node].} proc buildList(): PNode = new(result) diff --git a/lib/pure/matchers.nim b/lib/pure/matchers.nim index 6366fee1a..97223ed01 100644 --- a/lib/pure/matchers.nim +++ b/lib/pure/matchers.nim @@ -12,7 +12,7 @@ ## **Warning:** This module is deprecated since version 0.14.0. {.deprecated.} -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated {.push debugger:off .} # the user does not want to trace a part # of the standard library! @@ -29,21 +29,21 @@ proc validEmailAddress*(s: string): bool {.noSideEffect, chars = Letters + Digits + {'!','#','$','%','&', '\'','*','+','/','=','?','^','_','`','{','}','|','~','-','.'} var i = 0 - if s[i] notin chars or s[i] == '.': return false - while s[i] in chars: - if s[i] == '.' and s[i+1] == '.': return false + if i >= s.len or s[i] notin chars or s[i] == '.': return false + while i < s.len and s[i] in chars: + if i+1 < s.len and s[i] == '.' and s[i+1] == '.': return false inc(i) - if s[i] != '@': return false + if i >= s.len or s[i] != '@': return false var j = len(s)-1 - if s[j] notin Letters: return false + if j >= 0 and s[j] notin Letters: return false while j >= i and s[j] in Letters: dec(j) inc(i) # skip '@' - while s[i] in {'0'..'9', 'a'..'z', '-', '.'}: inc(i) - if s[i] != '\0': return false + while i < s.len and s[i] in {'0'..'9', 'a'..'z', '-', '.'}: inc(i) + if i != s.len: return false var x = substr(s, j+1) if len(x) == 2 and x[0] in Letters and x[1] in Letters: return true - case toLower(x) + case toLowerAscii(x) of "com", "org", "net", "gov", "mil", "biz", "info", "mobi", "name", "aero", "jobs", "museum": return true else: return false diff --git a/lib/pure/math.nim b/lib/pure/math.nim index cbd04a145..bc804eb86 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -21,6 +21,8 @@ include "system/inclrtl" {.push debugger:off .} # the user does not want to trace a part # of the standard library! +import bitops + proc binom*(n, k: int): int {.noSideEffect.} = ## Computes the binomial coefficient if k <= 0: return 1 @@ -29,15 +31,25 @@ proc binom*(n, k: int): int {.noSideEffect.} = for i in countup(2, k): result = (result * (n + 1 - i)) div i -proc fac*(n: int): int {.noSideEffect.} = +proc createFactTable[N: static[int]]: array[N, int] = + result[0] = 1 + for i in 1 ..< N: + result[i] = result[i - 1] * i + +proc fac*(n: int): int = ## Computes the faculty/factorial function. - result = 1 - for i in countup(2, n): - result = result * i + const factTable = + when sizeof(int) == 4: + createFactTable[13]() + else: + createFactTable[21]() + assert(n >= 0, $n & " must not be negative.") + assert(n < factTable.len, $n & " is too large to look up in the table") + factTable[n] {.push checks:off, line_dir:off, stack_trace:off.} -when defined(Posix) and not defined(haiku): +when defined(Posix): {.passl: "-lm".} const @@ -117,8 +129,14 @@ proc sum*[T](x: openArray[T]): T {.noSideEffect.} = ## If `x` is empty, 0 is returned. for i in items(x): result = result + i +proc prod*[T](x: openArray[T]): T {.noSideEffect.} = + ## Computes the product of the elements in ``x``. + ## If ``x`` is empty, 1 is returned. + result = 1.T + for i in items(x): result = result * i + {.push noSideEffect.} -when not defined(JS): +when not defined(JS): # C proc sqrt*(x: float32): float32 {.importc: "sqrtf", header: "<math.h>".} proc sqrt*(x: float64): float64 {.importc: "sqrt", header: "<math.h>".} ## Computes the square root of `x`. @@ -129,15 +147,45 @@ when not defined(JS): proc ln*(x: float32): float32 {.importc: "logf", header: "<math.h>".} proc ln*(x: float64): float64 {.importc: "log", header: "<math.h>".} ## Computes the natural log of `x` +else: # JS + proc sqrt*(x: float32): float32 {.importc: "Math.sqrt", nodecl.} + proc sqrt*(x: float64): float64 {.importc: "Math.sqrt", nodecl.} + + proc ln*(x: float32): float32 {.importc: "Math.log", nodecl.} + proc ln*(x: float64): float64 {.importc: "Math.log", nodecl.} + +proc log*[T: SomeFloat](x, base: T): T = + ## Computes the logarithm ``base`` of ``x`` + ln(x) / ln(base) + +when not defined(JS): # C proc log10*(x: float32): float32 {.importc: "log10f", header: "<math.h>".} proc log10*(x: float64): float64 {.importc: "log10", header: "<math.h>".} ## Computes the common logarithm (base 10) of `x` - proc log2*[T: float32|float64](x: T): T = return ln(x) / ln(2.0) - ## Computes the binary logarithm (base 2) of `x` proc exp*(x: float32): float32 {.importc: "expf", header: "<math.h>".} proc exp*(x: float64): float64 {.importc: "exp", header: "<math.h>".} ## Computes the exponential function of `x` (pow(E, x)) + proc sin*(x: float32): float32 {.importc: "sinf", header: "<math.h>".} + proc sin*(x: float64): float64 {.importc: "sin", header: "<math.h>".} + ## Computes the sine of `x` + proc cos*(x: float32): float32 {.importc: "cosf", header: "<math.h>".} + proc cos*(x: float64): float64 {.importc: "cos", header: "<math.h>".} + ## Computes the cosine of `x` + proc tan*(x: float32): float32 {.importc: "tanf", header: "<math.h>".} + proc tan*(x: float64): float64 {.importc: "tan", header: "<math.h>".} + ## Computes the tangent of `x` + + proc sinh*(x: float32): float32 {.importc: "sinhf", header: "<math.h>".} + proc sinh*(x: float64): float64 {.importc: "sinh", header: "<math.h>".} + ## Computes the hyperbolic sine of `x` + proc cosh*(x: float32): float32 {.importc: "coshf", header: "<math.h>".} + proc cosh*(x: float64): float64 {.importc: "cosh", header: "<math.h>".} + ## Computes the hyperbolic cosine of `x` + proc tanh*(x: float32): float32 {.importc: "tanhf", header: "<math.h>".} + proc tanh*(x: float64): float64 {.importc: "tanh", header: "<math.h>".} + ## Computes the hyperbolic tangent of `x` + proc arccos*(x: float32): float32 {.importc: "acosf", header: "<math.h>".} proc arccos*(x: float64): float64 {.importc: "acos", header: "<math.h>".} ## Computes the arc cosine of `x` @@ -154,52 +202,104 @@ when not defined(JS): ## results even when the resulting angle is near pi/2 or -pi/2 ## (`x` near 0). - proc cos*(x: float32): float32 {.importc: "cosf", header: "<math.h>".} - proc cos*(x: float64): float64 {.importc: "cos", header: "<math.h>".} - ## Computes the cosine of `x` - - proc cosh*(x: float32): float32 {.importc: "coshf", header: "<math.h>".} - proc cosh*(x: float64): float64 {.importc: "cosh", header: "<math.h>".} - ## Computes the hyperbolic cosine of `x` + proc arcsinh*(x: float32): float32 {.importc: "asinhf", header: "<math.h>".} + proc arcsinh*(x: float64): float64 {.importc: "asinh", header: "<math.h>".} + ## Computes the inverse hyperbolic sine of `x` + proc arccosh*(x: float32): float32 {.importc: "acoshf", header: "<math.h>".} + proc arccosh*(x: float64): float64 {.importc: "acosh", header: "<math.h>".} + ## Computes the inverse hyperbolic cosine of `x` + proc arctanh*(x: float32): float32 {.importc: "atanhf", header: "<math.h>".} + proc arctanh*(x: float64): float64 {.importc: "atanh", header: "<math.h>".} + ## Computes the inverse hyperbolic tangent of `x` + +else: # JS + proc log10*(x: float32): float32 {.importc: "Math.log10", nodecl.} + proc log10*(x: float64): float64 {.importc: "Math.log10", nodecl.} + proc log2*(x: float32): float32 {.importc: "Math.log2", nodecl.} + proc log2*(x: float64): float64 {.importc: "Math.log2", nodecl.} + proc exp*(x: float32): float32 {.importc: "Math.exp", nodecl.} + proc exp*(x: float64): float64 {.importc: "Math.exp", nodecl.} + proc sin*[T: float32|float64](x: T): T {.importc: "Math.sin", nodecl.} + proc cos*[T: float32|float64](x: T): T {.importc: "Math.cos", nodecl.} + proc tan*[T: float32|float64](x: T): T {.importc: "Math.tan", nodecl.} + + proc sinh*[T: float32|float64](x: T): T {.importc: "Math.sinh", nodecl.} + proc cosh*[T: float32|float64](x: T): T {.importc: "Math.cosh", nodecl.} + proc tanh*[T: float32|float64](x: T): T {.importc: "Math.tanh", nodecl.} + + proc arcsin*[T: float32|float64](x: T): T {.importc: "Math.asin", nodecl.} + proc arccos*[T: float32|float64](x: T): T {.importc: "Math.acos", nodecl.} + proc arctan*[T: float32|float64](x: T): T {.importc: "Math.atan", nodecl.} + proc arctan2*[T: float32|float64](y, x: T): T {.importC: "Math.atan2", nodecl.} + + proc arcsinh*[T: float32|float64](x: T): T {.importc: "Math.asinh", nodecl.} + proc arccosh*[T: float32|float64](x: T): T {.importc: "Math.acosh", nodecl.} + proc arctanh*[T: float32|float64](x: T): T {.importc: "Math.atanh", nodecl.} + +proc cot*[T: float32|float64](x: T): T = 1.0 / tan(x) + ## Computes the cotangent of `x` +proc sec*[T: float32|float64](x: T): T = 1.0 / cos(x) + ## Computes the secant of `x`. +proc csc*[T: float32|float64](x: T): T = 1.0 / sin(x) + ## Computes the cosecant of `x` + +proc coth*[T: float32|float64](x: T): T = 1.0 / tanh(x) + ## Computes the hyperbolic cotangent of `x` +proc sech*[T: float32|float64](x: T): T = 1.0 / cosh(x) + ## Computes the hyperbolic secant of `x` +proc csch*[T: float32|float64](x: T): T = 1.0 / sinh(x) + ## Computes the hyperbolic cosecant of `x` + +proc arccot*[T: float32|float64](x: T): T = arctan(1.0 / x) + ## Computes the inverse cotangent of `x` +proc arcsec*[T: float32|float64](x: T): T = arccos(1.0 / x) + ## Computes the inverse secant of `x` +proc arccsc*[T: float32|float64](x: T): T = arcsin(1.0 / x) + ## Computes the inverse cosecant of `x` + +proc arccoth*[T: float32|float64](x: T): T = arctanh(1.0 / x) + ## Computes the inverse hyperbolic cotangent of `x` +proc arcsech*[T: float32|float64](x: T): T = arccosh(1.0 / x) + ## Computes the inverse hyperbolic secant of `x` +proc arccsch*[T: float32|float64](x: T): T = arcsinh(1.0 / x) + ## Computes the inverse hyperbolic cosecant of `x` + +const windowsCC89 = defined(windows) and defined(bcc) + +when not defined(JS): # C proc hypot*(x, y: float32): float32 {.importc: "hypotf", header: "<math.h>".} proc hypot*(x, y: float64): float64 {.importc: "hypot", header: "<math.h>".} ## Computes the hypotenuse of a right-angle triangle with `x` and ## `y` as its base and height. Equivalent to ``sqrt(x*x + y*y)``. - proc sinh*(x: float32): float32 {.importc: "sinhf", header: "<math.h>".} - proc sinh*(x: float64): float64 {.importc: "sinh", header: "<math.h>".} - ## Computes the hyperbolic sine of `x` - proc sin*(x: float32): float32 {.importc: "sinf", header: "<math.h>".} - proc sin*(x: float64): float64 {.importc: "sin", header: "<math.h>".} - ## Computes the sine of `x` - - proc tan*(x: float32): float32 {.importc: "tanf", header: "<math.h>".} - proc tan*(x: float64): float64 {.importc: "tan", header: "<math.h>".} - ## Computes the tangent of `x` - proc tanh*(x: float32): float32 {.importc: "tanhf", header: "<math.h>".} - proc tanh*(x: float64): float64 {.importc: "tanh", header: "<math.h>".} - ## Computes the hyperbolic tangent of `x` - proc pow*(x, y: float32): float32 {.importc: "powf", header: "<math.h>".} proc pow*(x, y: float64): float64 {.importc: "pow", header: "<math.h>".} ## computes x to power raised of y. ## ## To compute power between integers, use `^` e.g. 2 ^ 6 - proc erf*(x: float32): float32 {.importc: "erff", header: "<math.h>".} - proc erf*(x: float64): float64 {.importc: "erf", header: "<math.h>".} - ## The error function - proc erfc*(x: float32): float32 {.importc: "erfcf", header: "<math.h>".} - proc erfc*(x: float64): float64 {.importc: "erfc", header: "<math.h>".} - ## The complementary error function - - proc lgamma*(x: float32): float32 {.importc: "lgammaf", header: "<math.h>".} - proc lgamma*(x: float64): float64 {.importc: "lgamma", header: "<math.h>".} - ## Natural log of the gamma function - proc tgamma*(x: float32): float32 {.importc: "tgammaf", header: "<math.h>".} - proc tgamma*(x: float64): float64 {.importc: "tgamma", header: "<math.h>".} - ## The gamma function + # TODO: add C89 version on windows + when not windowsCC89: + proc erf*(x: float32): float32 {.importc: "erff", header: "<math.h>".} + proc erf*(x: float64): float64 {.importc: "erf", header: "<math.h>".} + ## The error function + proc erfc*(x: float32): float32 {.importc: "erfcf", header: "<math.h>".} + proc erfc*(x: float64): float64 {.importc: "erfc", header: "<math.h>".} + ## The complementary error function + + proc gamma*(x: float32): float32 {.importc: "tgammaf", header: "<math.h>".} + proc gamma*(x: float64): float64 {.importc: "tgamma", header: "<math.h>".} + ## The gamma function + proc tgamma*(x: float32): float32 + {.deprecated: "use gamma instead", importc: "tgammaf", header: "<math.h>".} + proc tgamma*(x: float64): float64 + {.deprecated: "use gamma instead", importc: "tgamma", header: "<math.h>".} + ## The gamma function + ## **Deprecated since version 0.19.0**: Use ``gamma`` instead. + proc lgamma*(x: float32): float32 {.importc: "lgammaf", header: "<math.h>".} + proc lgamma*(x: float64): float64 {.importc: "lgamma", header: "<math.h>".} + ## Natural log of the gamma function proc floor*(x: float32): float32 {.importc: "floorf", header: "<math.h>".} proc floor*(x: float64): float64 {.importc: "floor", header: "<math.h>".} @@ -215,7 +315,7 @@ when not defined(JS): ## .. code-block:: nim ## echo ceil(-2.1) ## -2.0 - when defined(windows) and (defined(vcc) or defined(bcc)): + when windowsCC89: # MSVC 2010 don't have trunc/truncf # this implementation was inspired by Go-lang Math.Trunc proc truncImpl(f: float64): float64 = @@ -283,57 +383,31 @@ when not defined(JS): ## .. code-block:: nim ## echo trunc(PI) # 3.0 - proc fmod*(x, y: float32): float32 {.importc: "fmodf", header: "<math.h>".} - proc fmod*(x, y: float64): float64 {.importc: "fmod", header: "<math.h>".} + proc fmod*(x, y: float32): float32 {.deprecated, importc: "fmodf", header: "<math.h>".} + proc fmod*(x, y: float64): float64 {.deprecated, importc: "fmod", header: "<math.h>".} ## Computes the remainder of `x` divided by `y` ## ## .. code-block:: nim ## echo fmod(-2.5, 0.3) ## -0.1 -else: - proc trunc*(x: float32): float32 {.importc: "Math.trunc", nodecl.} - proc trunc*(x: float64): float64 {.importc: "Math.trunc", nodecl.} + proc `mod`*(x, y: float32): float32 {.importc: "fmodf", header: "<math.h>".} + proc `mod`*(x, y: float64): float64 {.importc: "fmod", header: "<math.h>".} + ## Computes the modulo operation for float operators. +else: # JS + proc hypot*[T: float32|float64](x, y: T): T = return sqrt(x*x + y*y) + proc pow*(x, y: float32): float32 {.importC: "Math.pow", nodecl.} + proc pow*(x, y: float64): float64 {.importc: "Math.pow", nodecl.} proc floor*(x: float32): float32 {.importc: "Math.floor", nodecl.} proc floor*(x: float64): float64 {.importc: "Math.floor", nodecl.} proc ceil*(x: float32): float32 {.importc: "Math.ceil", nodecl.} proc ceil*(x: float64): float64 {.importc: "Math.ceil", nodecl.} - - proc sqrt*(x: float32): float32 {.importc: "Math.sqrt", nodecl.} - proc sqrt*(x: float64): float64 {.importc: "Math.sqrt", nodecl.} - proc ln*(x: float32): float32 {.importc: "Math.log", nodecl.} - proc ln*(x: float64): float64 {.importc: "Math.log", nodecl.} - proc log10*[T: float32|float64](x: T): T = return ln(x) / ln(10.0) - proc log2*[T: float32|float64](x: T): T = return ln(x) / ln(2.0) - - proc exp*(x: float32): float32 {.importc: "Math.exp", nodecl.} - proc exp*(x: float64): float64 {.importc: "Math.exp", nodecl.} proc round0(x: float): float {.importc: "Math.round", nodecl.} + proc trunc*(x: float32): float32 {.importc: "Math.trunc", nodecl.} + proc trunc*(x: float64): float64 {.importc: "Math.trunc", nodecl.} - proc pow*(x, y: float32): float32 {.importC: "Math.pow", nodecl.} - proc pow*(x, y: float64): float64 {.importc: "Math.pow", nodecl.} - - proc arccos*(x: float32): float32 {.importc: "Math.acos", nodecl.} - proc arccos*(x: float64): float64 {.importc: "Math.acos", nodecl.} - proc arcsin*(x: float32): float32 {.importc: "Math.asin", nodecl.} - proc arcsin*(x: float64): float64 {.importc: "Math.asin", nodecl.} - proc arctan*(x: float32): float32 {.importc: "Math.atan", nodecl.} - proc arctan*(x: float64): float64 {.importc: "Math.atan", nodecl.} - proc arctan2*(y, x: float32): float32 {.importC: "Math.atan2", nodecl.} - proc arctan2*(y, x: float64): float64 {.importc: "Math.atan2", nodecl.} - - proc cos*(x: float32): float32 {.importc: "Math.cos", nodecl.} - proc cos*(x: float64): float64 {.importc: "Math.cos", nodecl.} - proc cosh*(x: float32): float32 = return (exp(x)+exp(-x))*0.5 - proc cosh*(x: float64): float64 = return (exp(x)+exp(-x))*0.5 - proc hypot*[T: float32|float64](x, y: T): T = return sqrt(x*x + y*y) - proc sinh*[T: float32|float64](x: T): T = return (exp(x)-exp(-x))*0.5 - proc sin*(x: float32): float32 {.importc: "Math.sin", nodecl.} - proc sin*(x: float64): float64 {.importc: "Math.sin", nodecl.} - proc tan*(x: float32): float32 {.importc: "Math.tan", nodecl.} - proc tan*(x: float64): float64 {.importc: "Math.tan", nodecl.} - proc tanh*[T: float32|float64](x: T): T = - var y = exp(2.0*x) - return (y-1.0)/(y+1.0) + proc `mod`*(x, y: float32): float32 {.importcpp: "# % #".} + proc `mod`*(x, y: float64): float64 {.importcpp: "# % #".} + ## Computes the modulo operation for float operators. proc round*[T: float32|float64](x: T, places: int = 0): T = ## Round a floating point number. @@ -350,6 +424,21 @@ proc round*[T: float32|float64](x: T, places: int = 0): T = var mult = pow(10.0, places.T) result = round0(x*mult)/mult +proc floorDiv*[T: SomeInteger](x, y: T): T = + ## Floor division is conceptually defined as ``floor(x / y)``. + ## This is different from the ``div`` operator, which is defined + ## as ``trunc(x / y)``. That is, ``div`` rounds towards ``0`` and ``floorDiv`` + ## rounds down. + result = x div y + let r = x mod y + if (r > 0 and y < 0) or (r < 0 and y > 0): result.dec 1 + +proc floorMod*[T: SomeNumber](x, y: T): T = + ## Floor modulus is conceptually defined as ``x - (floorDiv(x, y) * y). + ## This proc behaves the same as the ``%`` operator in python. + result = x mod y + if (result > 0 and y < 0) or (result < 0 and y > 0): result += y + when not defined(JS): proc c_frexp*(x: float32, exponent: var int32): float32 {. importc: "frexp", header: "<math.h>".} @@ -364,6 +453,28 @@ when not defined(JS): var exp: int32 result = c_frexp(x, exp) exponent = exp + + when windowsCC89: + # taken from Go-lang Math.Log2 + const ln2 = 0.693147180559945309417232121458176568075500134360255254120680009 + template log2Impl[T](x: T): T = + var exp: int32 + var frac = frexp(x, exp) + # Make sure exact powers of two give an exact answer. + # Don't depend on Log(0.5)*(1/Ln2)+exp being exactly exp-1. + if frac == 0.5: return T(exp - 1) + log10(frac)*(1/ln2) + T(exp) + + proc log2*(x: float32): float32 = log2Impl(x) + proc log2*(x: float64): float64 = log2Impl(x) + ## Log2 returns the binary logarithm of x. + ## The special cases are the same as for Log. + + else: + proc log2*(x: float32): float32 {.importc: "log2f", header: "<math.h>".} + proc log2*(x: float64): float64 {.importc: "log2", header: "<math.h>".} + ## Computes the binary logarithm (base 2) of `x` + else: proc frexp*[T: float32|float64](x: T, exponent: var int): T = if x == 0.0: @@ -414,21 +525,12 @@ proc sgn*[T: SomeNumber](x: T): int {.inline.} = ## `NaN`. ord(T(0) < x) - ord(x < T(0)) -proc `mod`*[T: float32|float64](x, y: T): T = - ## Computes the modulo operation for float operators. Equivalent - ## to ``x - y * floor(x/y)``. Note that the remainder will always - ## have the same sign as the divisor. - ## - ## .. code-block:: nim - ## echo (4.0 mod -3.1) # -2.2 - result = if y == 0.0: x else: x - y * (x/y).floor - {.pop.} {.pop.} proc `^`*[T](x: T, y: Natural): T = - ## Computes ``x`` to the power ``y`. ``x`` must be non-negative, use - ## `pow <#pow,float,float>` for negative exponents. + ## Computes ``x`` to the power ``y``. ``x`` must be non-negative, use + ## `pow <#pow,float,float>`_ for negative exponents. when compiles(y >= T(0)): assert y >= T(0) else: @@ -445,26 +547,53 @@ proc `^`*[T](x: T, y: Natural): T = x *= x proc gcd*[T](x, y: T): T = - ## Computes the greatest common divisor of ``x`` and ``y``. + ## Computes the greatest common (positive) divisor of ``x`` and ``y``. ## Note that for floats, the result cannot always be interpreted as ## "greatest decimal `z` such that ``z*N == x and z*M == y`` ## where N and M are positive integers." - var (x,y) = (x,y) + var (x, y) = (x, y) while y != 0: x = x mod y swap x, y abs x +proc gcd*(x, y: SomeInteger): SomeInteger = + ## Computes the greatest common (positive) divisor of ``x`` and ``y``. + ## Using binary GCD (aka Stein's) algorithm. + when x is SomeSignedInt: + var x = abs(x) + else: + var x = x + when y is SomeSignedInt: + var y = abs(y) + else: + var y = y + + if x == 0: + return y + if y == 0: + return x + + let shift = countTrailingZeroBits(x or y) + y = y shr countTrailingZeroBits(y) + while x != 0: + x = x shr countTrailingZeroBits(x) + if y > x: + swap y, x + x -= y + y shl shift + proc lcm*[T](x, y: T): T = ## Computes the least common multiple of ``x`` and ``y``. x div gcd(x, y) * y -when isMainModule and not defined(JS): +when isMainModule and not defined(JS) and not windowsCC89: # Check for no side effect annotation proc mySqrt(num: float): float {.noSideEffect.} = return sqrt(num) # check gamma function + assert(gamma(5.0) == 24.0) # 4! assert($tgamma(5.0) == $24.0) # 4! assert(lgamma(1.0) == 0.0) # ln(1.0) == 0.0 assert(erf(6.0) > erf(5.0)) @@ -474,6 +603,12 @@ when isMainModule: # Function for approximate comparison of floats proc `==~`(x, y: float): bool = (abs(x-y) < 1e-9) + block: # prod + doAssert prod([1, 2, 3, 4]) == 24 + doAssert prod([1.5, 3.4]) == 5.1 + let x: seq[float] = @[] + doAssert prod(x) == 1.0 + block: # round() tests # Round to 0 decimal places doAssert round(54.652) ==~ 55.0 @@ -550,3 +685,44 @@ when isMainModule: assert sgn(Inf) == 1 assert sgn(NaN) == 0 + block: # fac() tests + try: + discard fac(-1) + except AssertionError: + discard + + doAssert fac(0) == 1 + doAssert fac(1) == 1 + doAssert fac(2) == 2 + doAssert fac(3) == 6 + doAssert fac(4) == 24 + + block: # floorMod/floorDiv + doAssert floorDiv(8, 3) == 2 + doAssert floorMod(8, 3) == 2 + + doAssert floorDiv(8, -3) == -3 + doAssert floorMod(8, -3) == -1 + + doAssert floorDiv(-8, 3) == -3 + doAssert floorMod(-8, 3) == 1 + + doAssert floorDiv(-8, -3) == 2 + doAssert floorMod(-8, -3) == -2 + + doAssert floorMod(8.0, -3.0) ==~ -1.0 + doAssert floorMod(-8.5, 3.0) ==~ 0.5 + + block: # log + doAssert log(4.0, 3.0) == ln(4.0) / ln(3.0) + doAssert log2(8.0'f64) == 3.0'f64 + doAssert log2(4.0'f64) == 2.0'f64 + doAssert log2(2.0'f64) == 1.0'f64 + doAssert log2(1.0'f64) == 0.0'f64 + doAssert classify(log2(0.0'f64)) == fcNegInf + + doAssert log2(8.0'f32) == 3.0'f32 + doAssert log2(4.0'f32) == 2.0'f32 + doAssert log2(2.0'f32) == 1.0'f32 + doAssert log2(1.0'f32) == 0.0'f32 + doAssert classify(log2(0.0'f32)) == fcNegInf diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 5c73381ff..9fccd08d4 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -22,7 +22,11 @@ elif defined(posix): else: {.error: "the memfiles module is not supported on your operating system!".} -import os +import os, streams + +proc newEIO(msg: string): ref IOError = + new(result) + result.msg = msg type MemFile* = object ## represents a memory mapped file @@ -38,19 +42,20 @@ type else: handle: cint -{.deprecated: [TMemFile: MemFile].} - proc mapMem*(m: var MemFile, mode: FileMode = fmRead, mappedSize = -1, offset = 0): pointer = ## returns a pointer to a mapped portion of MemFile `m` ## ## ``mappedSize`` of ``-1`` maps to the whole file, and ## ``offset`` must be multiples of the PAGE SIZE of your OS + if mode == fmAppend: + raise newEIO("The append mode is not supported.") + var readonly = mode == fmRead when defined(windows): result = mapViewOfFileEx( m.mapHandle, - if readonly: FILE_MAP_READ else: FILE_MAP_WRITE, + if readonly: FILE_MAP_READ else: FILE_MAP_READ or FILE_MAP_WRITE, int32(offset shr 32), int32(offset and 0xffffffff), if mappedSize == -1: 0 else: mappedSize, @@ -115,6 +120,9 @@ proc open*(filename: string, mode: FileMode = fmRead, ## mm_half = memfiles.open("/tmp/test.mmap", mode = fmReadWrite, mappedSize = 512) # The file can be resized only when write mode is used: + if mode == fmAppend: + raise newEIO("The append mode is not supported.") + assert newFileSize == -1 or mode != fmRead var readonly = mode == fmRead @@ -123,6 +131,10 @@ proc open*(filename: string, mode: FileMode = fmRead, result.size = 0 when defined(windows): + let desiredAccess = GENERIC_READ + let shareMode = FILE_SHARE_READ + let flags = FILE_FLAG_RANDOM_ACCESS + template fail(errCode: OSErrorCode, msg: untyped) = rollback() if result.fHandle != 0: discard closeHandle(result.fHandle) @@ -135,11 +147,11 @@ proc open*(filename: string, mode: FileMode = fmRead, winApiProc( filename, # GENERIC_ALL != (GENERIC_READ or GENERIC_WRITE) - if readonly: GENERIC_READ else: GENERIC_READ or GENERIC_WRITE, - FILE_SHARE_READ, + if readonly: desiredAccess else: desiredAccess or GENERIC_WRITE, + if readonly: shareMode else: shareMode or FILE_SHARE_WRITE, nil, if newFileSize != -1: CREATE_ALWAYS else: OPEN_EXISTING, - if readonly: FILE_ATTRIBUTE_READONLY else: FILE_ATTRIBUTE_TEMPORARY, + if readonly: FILE_ATTRIBUTE_READONLY or flags else: FILE_ATTRIBUTE_NORMAL or flags, 0) when useWinUnicode: @@ -174,7 +186,7 @@ proc open*(filename: string, mode: FileMode = fmRead, result.mem = mapViewOfFileEx( result.mapHandle, - if readonly: FILE_MAP_READ else: FILE_MAP_WRITE, + if readonly: FILE_MAP_READ else: FILE_MAP_READ or FILE_MAP_WRITE, int32(offset shr 32), int32(offset and 0xffffffff), if mappedSize == -1: 0 else: mappedSize, @@ -247,6 +259,28 @@ proc open*(filename: string, mode: FileMode = fmRead, if close(result.handle) == 0: result.handle = -1 +proc flush*(f: var MemFile; attempts: Natural = 3) = + ## Flushes `f`'s buffer for the number of attempts equal to `attempts`. + ## If were errors an exception `OSError` will be raised. + var res = false + var lastErr: OSErrorCode + when defined(windows): + for i in 1..attempts: + res = flushViewOfFile(f.mem, 0) != 0 + if res: + break + lastErr = osLastError() + if lastErr != ERROR_LOCK_VIOLATION.OSErrorCode: + raiseOSError(lastErr) + else: + for i in 1..attempts: + res = msync(f.mem, f.size, MS_SYNC or MS_INVALIDATE) == 0 + if res: + break + lastErr = osLastError() + if lastErr != EBUSY.OSErrorCode: + raiseOSError(lastErr, "error flushing mapping") + proc close*(f: var MemFile) = ## closes the memory mapped file `f`. All changes are written back to the ## file system, if `f` was opened with write access. @@ -288,14 +322,12 @@ type MemSlice* = object ## represent slice of a MemFile for iteration over deli proc `==`*(x, y: MemSlice): bool = ## Compare a pair of MemSlice for strict equality. - proc memcmp(a, b: pointer, n:int):int {.importc: "memcmp",header: "string.h".} - result = (x.size == y.size and memcmp(x.data, y.data, x.size) == 0) + result = (x.size == y.size and equalMem(x.data, y.data, x.size)) proc `$`*(ms: MemSlice): string {.inline.} = ## Return a Nim string built from a MemSlice. var buf = newString(ms.size) copyMem(addr(buf[0]), ms.data, ms.size) - buf[ms.size] = '\0' result = buf iterator memSlices*(mfile: MemFile, delim='\l', eat='\r'): MemSlice {.inline.} = @@ -364,9 +396,9 @@ iterator lines*(mfile: MemFile, buf: var TaintedString, delim='\l', eat='\r'): T ## echo line for ms in memSlices(mfile, delim, eat): - buf.setLen(ms.size) - copyMem(addr(buf[0]), ms.data, ms.size) - buf[ms.size] = '\0' + setLen(buf.string, ms.size) + if ms.size > 0: + copyMem(addr buf[0], ms.data, ms.size) yield buf iterator lines*(mfile: MemFile, delim='\l', eat='\r'): TaintedString {.inline.} = @@ -384,3 +416,68 @@ iterator lines*(mfile: MemFile, delim='\l', eat='\r'): TaintedString {.inline.} var buf = TaintedString(newStringOfCap(80)) for line in lines(mfile, buf, delim, eat): yield buf + +type + MemMapFileStream* = ref MemMapFileStreamObj ## a stream that encapsulates a `MemFile` + MemMapFileStreamObj* = object of Stream + mf: MemFile + mode: FileMode + pos: ByteAddress + +proc mmsClose(s: Stream) = + MemMapFileStream(s).pos = -1 + close(MemMapFileStream(s).mf) + +proc mmsFlush(s: Stream) = flush(MemMapFileStream(s).mf) + +proc mmsAtEnd(s: Stream): bool = (MemMapFileStream(s).pos >= MemMapFileStream(s).mf.size) or + (MemMapFileStream(s).pos < 0) + +proc mmsSetPosition(s: Stream, pos: int) = + if pos > MemMapFileStream(s).mf.size or pos < 0: + raise newEIO("cannot set pos in stream") + MemMapFileStream(s).pos = pos + +proc mmsGetPosition(s: Stream): int = MemMapFileStream(s).pos + +proc mmsPeekData(s: Stream, buffer: pointer, bufLen: int): int = + let startAddress = cast[ByteAddress](MemMapFileStream(s).mf.mem) + let p = cast[ByteAddress](MemMapFileStream(s).pos) + let l = min(bufLen, MemMapFileStream(s).mf.size - p) + moveMem(buffer, cast[pointer](startAddress + p), l) + result = l + +proc mmsReadData(s: Stream, buffer: pointer, bufLen: int): int = + result = mmsPeekData(s, buffer, bufLen) + inc(MemMapFileStream(s).pos, result) + +proc mmsWriteData(s: Stream, buffer: pointer, bufLen: int) = + if MemMapFileStream(s).mode == fmRead: + raise newEIO("cannot write to read-only stream") + let size = MemMapFileStream(s).mf.size + if MemMapFileStream(s).pos + bufLen > size: + raise newEIO("cannot write to stream") + let p = cast[ByteAddress](MemMapFileStream(s).mf.mem) + + cast[ByteAddress](MemMapFileStream(s).pos) + moveMem(cast[pointer](p), buffer, bufLen) + inc(MemMapFileStream(s).pos, bufLen) + +proc newMemMapFileStream*(filename: string, mode: FileMode = fmRead, fileSize: int = -1): + MemMapFileStream = + ## creates a new stream from the file named `filename` with the mode `mode`. + ## Raises ## `EOS` if the file cannot be opened. See the `system + ## <system.html>`_ module for a list of available FileMode enums. + ## ``fileSize`` can only be set if the file does not exist and is opened + ## with write access (e.g., with fmReadWrite). + var mf: MemFile = open(filename, mode, newFileSize = fileSize) + new(result) + result.mode = mode + result.mf = mf + result.closeImpl = mmsClose + result.atEndImpl = mmsAtEnd + result.setPositionImpl = mmsSetPosition + result.getPositionImpl = mmsGetPosition + result.readDataImpl = mmsReadData + result.peekDataImpl = mmsPeekData + result.writeDataImpl = mmsWriteData + result.flushImpl = mmsFlush diff --git a/lib/pure/mersenne.nim b/lib/pure/mersenne.nim index 6ac0c4e56..b2227f114 100644 --- a/lib/pure/mersenne.nim +++ b/lib/pure/mersenne.nim @@ -12,8 +12,6 @@ type mt: array[0..623, uint32] index: int -{.deprecated: [TMersenneTwister: MersenneTwister].} - proc newMersenneTwister*(seed: uint32): MersenneTwister = result.index = 0 result.mt[0] = seed diff --git a/lib/pure/mimetypes.nim b/lib/pure/mimetypes.nim index b397ef47b..8f5f3a183 100644 --- a/lib/pure/mimetypes.nim +++ b/lib/pure/mimetypes.nim @@ -13,8 +13,6 @@ type MimeDB* = object mimes: StringTableRef -{.deprecated: [TMimeDB: MimeDB].} - const mimes* = { "ez": "application/andrew-inset", "anx": "application/annodex", @@ -233,6 +231,7 @@ const mimes* = { "xcf": "application/x-xcf", "fig": "application/x-xfig", "xpi": "application/x-xpinstall", + "wasm": "application/wasm", "amr": "audio/amr", "awb": "audio/amr-wb", "amr": "audio/amr", diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim index 280c4e927..d5fb0f89b 100644 --- a/lib/pure/nativesockets.nim +++ b/lib/pure/nativesockets.nim @@ -31,7 +31,7 @@ else: export Sockaddr_storage, Sockaddr_un, Sockaddr_un_path_length export SocketHandle, Sockaddr_in, Addrinfo, INADDR_ANY, SockAddr, SockLen, - Sockaddr_in6, + Sockaddr_in6, Sockaddr_storage, inet_ntoa, recv, `==`, connect, send, accept, recvfrom, sendto, freeAddrInfo @@ -85,9 +85,6 @@ type length*: int addrList*: seq[string] -{.deprecated: [TPort: Port, TDomain: Domain, TType: SockType, - TProtocol: Protocol, TServent: Servent, THostent: Hostent].} - when useWinVersion: let osInvalidSocket* = winlean.INVALID_SOCKET @@ -251,9 +248,10 @@ proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET, hints.ai_socktype = toInt(sockType) hints.ai_protocol = toInt(protocol) # OpenBSD doesn't support AI_V4MAPPED and doesn't define the macro AI_V4MAPPED. - # FreeBSD doesn't support AI_V4MAPPED but defines the macro. + # FreeBSD, Haiku don't support AI_V4MAPPED but defines the macro. # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198092 - when not defined(freebsd) and not defined(openbsd) and not defined(netbsd) and not defined(android): + # https://dev.haiku-os.org/ticket/14323 + when not defined(freebsd) and not defined(openbsd) and not defined(netbsd) and not defined(android) and not defined(haiku): if domain == AF_INET6: hints.ai_flags = AI_V4MAPPED var gaiResult = getaddrinfo(address, $port, addr(hints), result) @@ -395,7 +393,15 @@ proc getHostByAddr*(ip: string): Hostent {.tags: [ReadIOEffect].} = result.addrtype = AF_INET6 else: raiseOSError(osLastError(), "unknown h_addrtype") - result.addrList = cstringArrayToSeq(s.h_addr_list) + if result.addrtype == AF_INET: + result.addrlist = @[] + var i = 0 + while not isNil(s.h_addrlist[i]): + var inaddr_ptr = cast[ptr InAddr](s.h_addr_list[i]) + result.addrlist.add($inet_ntoa(inaddr_ptr[])) + inc(i) + else: + result.addrList = cstringArrayToSeq(s.h_addr_list) result.length = int(s.h_length) proc getHostByName*(name: string): Hostent {.tags: [ReadIOEffect].} = @@ -416,7 +422,15 @@ proc getHostByName*(name: string): Hostent {.tags: [ReadIOEffect].} = result.addrtype = AF_INET6 else: raiseOSError(osLastError(), "unknown h_addrtype") - result.addrList = cstringArrayToSeq(s.h_addr_list) + if result.addrtype == AF_INET: + result.addrlist = @[] + var i = 0 + while not isNil(s.h_addrlist[i]): + var inaddr_ptr = cast[ptr InAddr](s.h_addr_list[i]) + result.addrlist.add($inet_ntoa(inaddr_ptr[])) + inc(i) + else: + result.addrList = cstringArrayToSeq(s.h_addr_list) result.length = int(s.h_length) proc getHostname*(): string {.tags: [ReadIOEffect].} = @@ -600,8 +614,12 @@ proc setBlocking*(s: SocketHandle, blocking: bool) = proc timeValFromMilliseconds(timeout = 500): Timeval = if timeout != -1: var seconds = timeout div 1000 - result.tv_sec = seconds.int32 - result.tv_usec = ((timeout - seconds * 1000) * 1000).int32 + when useWinVersion: + result.tv_sec = seconds.int32 + result.tv_usec = ((timeout - seconds * 1000) * 1000).int32 + else: + result.tv_sec = seconds.Time + result.tv_usec = ((timeout - seconds * 1000) * 1000).Suseconds proc createFdSet(fd: var TFdSet, s: seq[SocketHandle], m: var int) = FD_ZERO(fd) @@ -620,7 +638,7 @@ proc pruneSocketSet(s: var seq[SocketHandle], fd: var TFdSet) = inc(i) setLen(s, L) -proc select*(readfds: var seq[SocketHandle], timeout = 500): int {.deprecated.} = +proc select*(readfds: var seq[SocketHandle], timeout = 500): int {.deprecated: "use selectRead instead".} = ## When a socket in ``readfds`` is ready to be read from then a non-zero ## value will be returned specifying the count of the sockets which can be ## read from. The sockets which can be read from will also be removed diff --git a/lib/pure/net.nim b/lib/pure/net.nim index af9eea51a..a60137dab 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -41,7 +41,7 @@ ## immediately. ## ## .. code-block:: Nim -## var socket = newSocket() +## var socket = newSocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) ## socket.sendTo("192.168.0.1", Port(27960), "status\n") ## ## Creating a server @@ -58,17 +58,13 @@ ## You can then begin accepting connections using the ``accept`` procedure. ## ## .. code-block:: Nim -## var client = new Socket +## var client: Socket ## var address = "" ## while true: ## socket.acceptAddr(client, address) ## echo("Client connected from: ", address) -## -## **Note:** The ``client`` variable is initialised with ``new Socket`` **not** -## ``newSocket()``. The difference is that the latter creates a new file -## descriptor. -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated import nativesockets, os, strutils, parseutils, times, sets, options export Port, `$`, `==` export Domain, SockType, Protocol @@ -110,9 +106,6 @@ when defineSsl: serverGetPskFunc: SslServerGetPskFunc clientGetPskFunc: SslClientGetPskFunc - {.deprecated: [ESSL: SSLError, TSSLCVerifyMode: SSLCVerifyMode, - TSSLProtVersion: SSLProtVersion, PSSLContext: SSLContext, - TSSLAcceptResult: SSLAcceptResult].} else: type SslContext* = void # TODO: Workaround #4797. @@ -159,10 +152,6 @@ type Peek, SafeDisconn ## Ensures disconnection exceptions (ECONNRESET, EPIPE etc) are not thrown. -{.deprecated: [TSocketFlags: SocketFlag, ETimeout: TimeoutError, - TReadLineResult: ReadLineResult, TSOBool: SOBool, PSocket: Socket, - TSocketImpl: SocketImpl].} - type IpAddressFamily* {.pure.} = enum ## Describes the type of an IP address IPv6, ## IPv6 address @@ -176,8 +165,6 @@ type of IpAddressFamily.IPv4: address_v4*: array[0..3, uint8] ## Contains the IP address in bytes in ## case of IPv4 -{.deprecated: [TIpAddress: IpAddress].} - proc socketError*(socket: Socket, err: int = -1, async = false, lastError = (-1).OSErrorCode): void {.gcsafe.} @@ -240,7 +227,7 @@ proc newSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM, raiseOSError(osLastError()) result = newSocket(fd, domain, sockType, protocol, buffered) -proc parseIPv4Address(address_str: string): IpAddress = +proc parseIPv4Address(addressStr: string): IpAddress = ## Parses IPv4 adresses ## Raises EInvalidValue on errors var @@ -250,15 +237,15 @@ proc parseIPv4Address(address_str: string): IpAddress = result.family = IpAddressFamily.IPv4 - for i in 0 .. high(address_str): - if address_str[i] in strutils.Digits: # Character is a number + for i in 0 .. high(addressStr): + if addressStr[i] in strutils.Digits: # Character is a number currentByte = currentByte * 10 + - cast[uint16](ord(address_str[i]) - ord('0')) + cast[uint16](ord(addressStr[i]) - ord('0')) if currentByte > 255'u16: raise newException(ValueError, "Invalid IP Address. Value is out of range") seperatorValid = true - elif address_str[i] == '.': # IPv4 address separator + elif addressStr[i] == '.': # IPv4 address separator if not seperatorValid or byteCount >= 3: raise newException(ValueError, "Invalid IP Address. The address consists of too many groups") @@ -274,11 +261,11 @@ proc parseIPv4Address(address_str: string): IpAddress = raise newException(ValueError, "Invalid IP Address") result.address_v4[byteCount] = cast[uint8](currentByte) -proc parseIPv6Address(address_str: string): IpAddress = +proc parseIPv6Address(addressStr: string): IpAddress = ## Parses IPv6 adresses ## Raises EInvalidValue on errors result.family = IpAddressFamily.IPv6 - if address_str.len < 2: + if addressStr.len < 2: raise newException(ValueError, "Invalid IP Address") var @@ -291,7 +278,7 @@ proc parseIPv6Address(address_str: string): IpAddress = v4StartPos = -1 byteCount = 0 - for i,c in address_str: + for i,c in addressStr: if c == ':': if not seperatorValid: raise newException(ValueError, @@ -302,7 +289,7 @@ proc parseIPv6Address(address_str: string): IpAddress = "Invalid IP Address. Address contains more than one \"::\" seperator") dualColonGroup = groupCount seperatorValid = false - elif i != 0 and i != high(address_str): + elif i != 0 and i != high(addressStr): if groupCount >= 8: raise newException(ValueError, "Invalid IP Address. The address consists of too many groups") @@ -312,11 +299,11 @@ proc parseIPv6Address(address_str: string): IpAddress = groupCount.inc() if dualColonGroup != -1: seperatorValid = false elif i == 0: # only valid if address starts with :: - if address_str[1] != ':': + if addressStr[1] != ':': raise newException(ValueError, "Invalid IP Address. Address may not start with \":\"") - else: # i == high(address_str) - only valid if address ends with :: - if address_str[high(address_str)-1] != ':': + else: # i == high(addressStr) - only valid if address ends with :: + if addressStr[high(addressStr)-1] != ':': raise newException(ValueError, "Invalid IP Address. Address may not end with \":\"") lastWasColon = true @@ -354,7 +341,7 @@ proc parseIPv6Address(address_str: string): IpAddress = result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) groupCount.inc() else: # Must parse IPv4 address - for i,c in address_str[v4StartPos..high(address_str)]: + for i,c in addressStr[v4StartPos..high(addressStr)]: if c in strutils.Digits: # Character is a number currentShort = currentShort * 10 + cast[uint32](ord(c) - ord('0')) if currentShort > 255'u32: @@ -395,25 +382,69 @@ proc parseIPv6Address(address_str: string): IpAddress = raise newException(ValueError, "Invalid IP Address. The address consists of too many groups") -proc parseIpAddress*(address_str: string): IpAddress = +proc parseIpAddress*(addressStr: string): IpAddress = ## Parses an IP address ## Raises EInvalidValue on error - if address_str == nil: - raise newException(ValueError, "IP Address string is nil") - if address_str.contains(':'): - return parseIPv6Address(address_str) + if addressStr.len == 0: + raise newException(ValueError, "IP Address string is empty") + if addressStr.contains(':'): + return parseIPv6Address(addressStr) else: - return parseIPv4Address(address_str) + return parseIPv4Address(addressStr) -proc isIpAddress*(address_str: string): bool {.tags: [].} = +proc isIpAddress*(addressStr: string): bool {.tags: [].} = ## Checks if a string is an IP address ## Returns true if it is, false otherwise try: - discard parseIpAddress(address_str) + discard parseIpAddress(addressStr) except ValueError: return false return true +proc toSockAddr*(address: IpAddress, port: Port, sa: var Sockaddr_storage, + sl: var Socklen) = + ## Converts `IpAddress` and `Port` to `SockAddr` and `Socklen` + let port = htons(uint16(port)) + case address.family + of IpAddressFamily.IPv4: + sl = sizeof(Sockaddr_in).Socklen + let s = cast[ptr Sockaddr_in](addr sa) + s.sin_family = type(s.sin_family)(toInt(AF_INET)) + s.sin_port = port + copyMem(addr s.sin_addr, unsafeAddr address.address_v4[0], + sizeof(s.sin_addr)) + of IpAddressFamily.IPv6: + sl = sizeof(Sockaddr_in6).Socklen + let s = cast[ptr Sockaddr_in6](addr sa) + s.sin6_family = type(s.sin6_family)(toInt(AF_INET6)) + s.sin6_port = port + copyMem(addr s.sin6_addr, unsafeAddr address.address_v6[0], + sizeof(s.sin6_addr)) + +proc fromSockAddrAux(sa: ptr Sockaddr_storage, sl: Socklen, + address: var IpAddress, port: var Port) = + if sa.ss_family.int == toInt(AF_INET) and sl == sizeof(Sockaddr_in).Socklen: + address = IpAddress(family: IpAddressFamily.IPv4) + let s = cast[ptr Sockaddr_in](sa) + copyMem(addr address.address_v4[0], addr s.sin_addr, + sizeof(address.address_v4)) + port = ntohs(s.sin_port).Port + elif sa.ss_family.int == toInt(AF_INET6) and + sl == sizeof(Sockaddr_in6).Socklen: + address = IpAddress(family: IpAddressFamily.IPv6) + let s = cast[ptr Sockaddr_in6](sa) + copyMem(addr address.address_v6[0], addr s.sin6_addr, + sizeof(address.address_v6)) + port = ntohs(s.sin6_port).Port + else: + raise newException(ValueError, "Neither IPv4 nor IPv6") + +proc fromSockAddr*(sa: Sockaddr_storage | SockAddr | Sockaddr_in | Sockaddr_in6, + sl: Socklen, address: var IpAddress, port: var Port) {.inline.} = + ## Converts `SockAddr` and `Socklen` to `IpAddress` and `Port`. Raises + ## `ObjectConversionError` in case of invalid `sa` and `sl` arguments. + fromSockAddrAux(cast[ptr Sockaddr_storage](unsafeAddr sa), sl, address, port) + when defineSsl: CRYPTO_malloc_init() doAssert SslLibraryInit() == 1 @@ -552,7 +583,7 @@ when defineSsl: proc pskClientCallback(ssl: SslPtr; hint: cstring; identity: cstring; max_identity_len: cuint; psk: ptr cuchar; max_psk_len: cuint): cuint {.cdecl.} = let ctx = SSLContext(context: ssl.SSL_get_SSL_CTX) - let hintString = if hint == nil: nil else: $hint + let hintString = if hint == nil: "" else: $hint let (identityString, pskString) = (ctx.clientGetPskFunc)(hintString) if psk.len.cuint > max_psk_len: return 0 @@ -622,7 +653,7 @@ when defineSsl: proc wrapConnectedSocket*(ctx: SSLContext, socket: Socket, handshake: SslHandshakeType, - hostname: string = nil) = + hostname: string = "") = ## Wraps a connected socket in an SSL context. This function effectively ## turns ``socket`` into an SSL socket. ## ``hostname`` should be specified so that the client knows which hostname @@ -636,7 +667,7 @@ when defineSsl: wrapSocket(ctx, socket) case handshake of handshakeAsClient: - if not hostname.isNil and not isIpAddress(hostname): + if hostname.len > 0 and not isIpAddress(hostname): # Discard result in case OpenSSL version doesn't support SNI, or we're # not using TLSv1+ discard SSL_set_tlsext_host_name(socket.sslHandle, hostname) @@ -754,16 +785,12 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string, ## The resulting client will inherit any properties of the server socket. For ## example: whether the socket is buffered or not. ## - ## **Note**: ``client`` must be initialised (with ``new``), this function - ## makes no effort to initialise the ``client`` variable. - ## ## The ``accept`` call may result in an error if the connecting socket ## disconnects during the duration of the ``accept``. If the ``SafeDisconn`` ## flag is specified then this error will not be raised and instead ## accept will be called again. - assert(client != nil) - assert client.fd.int <= 0, "Client socket needs to be initialised with " & - "`new`, not `newSocket`." + if client.isNil: + new(client) let ret = accept(server.fd) let sock = ret[0] @@ -775,6 +802,7 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string, else: address = ret[1] client.fd = sock + client.domain = getSockDomain(sock) client.isBuffered = server.isBuffered # Handle SSL. @@ -843,9 +871,6 @@ proc accept*(server: Socket, client: var Socket, ## Equivalent to ``acceptAddr`` but doesn't return the address, only the ## socket. ## - ## **Note**: ``client`` must be initialised (with ``new``), this function - ## makes no effort to initialise the ``client`` variable. - ## ## The ``accept`` call may result in an error if the connecting socket ## disconnects during the duration of the ``accept``. If the ``SafeDisconn`` ## flag is specified then this error will not be raised and instead @@ -932,7 +957,7 @@ when defined(posix) and not defined(nimdoc): raise newException(ValueError, "socket path too long") copyMem(addr result.sun_path, path.cstring, path.len + 1) -when defined(posix): +when defined(posix) or defined(nimdoc): proc connectUnix*(socket: Socket, path: string) = ## Connects to Unix socket on `path`. ## This only works on Unix-style systems: Mac OS X, BSD and Linux @@ -1120,7 +1145,7 @@ proc waitFor(socket: Socket, waited: var float, timeout, size: int, return 1 let sslPending = SSLPending(socket.sslHandle) if sslPending != 0: - return sslPending + return min(sslPending, size) var startTime = epochTime() let selRet = select(socket, timeout - int(waited * 1000.0)) @@ -1303,6 +1328,7 @@ proc recvFrom*(socket: Socket, data: var string, length: int, ## used. Therefore if ``socket`` contains something in its buffer this ## function will make no effort to return it. + assert(socket.protocol != IPPROTO_TCP, "Cannot `recvFrom` on a TCP socket") # TODO: Buffered sockets data.setLen(length) var sockAddress: Sockaddr_in @@ -1372,23 +1398,25 @@ proc trySend*(socket: Socket, data: string): bool {.tags: [WriteIOEffect].} = result = send(socket, cstring(data), data.len) == data.len proc sendTo*(socket: Socket, address: string, port: Port, data: pointer, - size: int, af: Domain = AF_INET, flags = 0'i32): int {. + size: int, af: Domain = AF_INET, flags = 0'i32) {. tags: [WriteIOEffect].} = ## This proc sends ``data`` to the specified ``address``, ## which may be an IP address or a hostname, if a hostname is specified ## this function will try each IP of that hostname. ## + ## If an error occurs an OSError exception will be raised. ## ## **Note:** You may wish to use the high-level version of this function ## which is defined below. ## ## **Note:** This proc is not available for SSL sockets. + assert(socket.protocol != IPPROTO_TCP, "Cannot `sendTo` on a TCP socket") assert(not socket.isClosed, "Cannot `sendTo` on a closed socket") - var aiList = getAddrInfo(address, port, af) - + var aiList = getAddrInfo(address, port, af, socket.sockType, socket.protocol) # try all possibilities: var success = false var it = aiList + var result = 0 while it != nil: result = sendto(socket.fd, data, size.cint, flags.cint, it.ai_addr, it.ai_addrlen.SockLen) @@ -1397,16 +1425,22 @@ proc sendTo*(socket: Socket, address: string, port: Port, data: pointer, break it = it.ai_next + let osError = osLastError() freeAddrInfo(aiList) + if not success: + raiseOSError(osError) + proc sendTo*(socket: Socket, address: string, port: Port, - data: string): int {.tags: [WriteIOEffect].} = + data: string) {.tags: [WriteIOEffect].} = ## This proc sends ``data`` to the specified ``address``, ## which may be an IP address or a hostname, if a hostname is specified ## this function will try each IP of that hostname. ## + ## If an error occurs an OSError exception will be raised. + ## ## This is the high-level version of the above ``sendTo`` function. - result = socket.sendTo(address, port, cstring(data), data.len) + socket.sendTo(address, port, cstring(data), data.len, socket.domain) proc isSsl*(socket: Socket): bool = @@ -1664,6 +1698,9 @@ proc connect*(socket: Socket, address: string, port = Port(0), if selectWrite(s, timeout) != 1: raise newException(TimeoutError, "Call to 'connect' timed out.") else: + let res = getSockOptInt(socket.fd, SOL_SOCKET, SO_ERROR) + if res != 0: + raiseOSError(OSErrorCode(res)) when defineSsl and not defined(nimdoc): if socket.isSSL: socket.fd.setBlocking(true) diff --git a/lib/pure/nimprof.nim b/lib/pure/nimprof.nim index 4289eb049..506c6bfaa 100644 --- a/lib/pure/nimprof.nim +++ b/lib/pure/nimprof.nim @@ -30,7 +30,6 @@ when not declared(system.StackTrace): type StackTrace = object lines: array[0..20, cstring] files: array[0..20, cstring] - {.deprecated: [TStackTrace: StackTrace].} proc `[]`*(st: StackTrace, i: int): cstring = st.lines[i] # We use a simple hash table of bounded size to keep track of the stack traces: @@ -39,7 +38,6 @@ type total: int st: StackTrace ProfileData = array[0..64*1024-1, ptr ProfileEntry] -{.deprecated: [TProfileEntry: ProfileEntry, TProfileData: ProfileData].} proc `==`(a, b: StackTrace): bool = for i in 0 .. high(a.lines): diff --git a/lib/pure/oids.nim b/lib/pure/oids.nim index 427a68964..d6369b5f9 100644 --- a/lib/pure/oids.nim +++ b/lib/pure/oids.nim @@ -23,11 +23,9 @@ type fuzz: int32 ## count: int32 ## -{.deprecated: [Toid: Oid].} - proc `==`*(oid1: Oid, oid2: Oid): bool = - ## Compare two Mongo Object IDs for equality - return (oid1.time == oid2.time) and (oid1.fuzz == oid2.fuzz) and (oid1.count == oid2.count) + ## Compare two Mongo Object IDs for equality + return (oid1.time == oid2.time) and (oid1.fuzz == oid2.fuzz) and (oid1.count == oid2.count) proc hexbyte*(hex: char): int = case hex @@ -71,7 +69,7 @@ proc genOid*(): Oid = proc rand(): cint {.importc: "rand", header: "<stdlib.h>", nodecl.} proc srand(seed: cint) {.importc: "srand", header: "<stdlib.h>", nodecl.} - var t = getTime().int32 + var t = getTime().toUnix.int32 var i = int32(atomicInc(incr)) diff --git a/lib/pure/options.nim b/lib/pure/options.nim index 6d2869bff..12e38d8b5 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -70,26 +70,55 @@ import typetraits type + SomePointer = ref | ptr | pointer + +type Option*[T] = object ## An optional type that stores its value and state separately in a boolean. - val: T - has: bool + when T is SomePointer: + val: T + else: + val: T + has: bool + UnpackError* = ref object of ValueError proc some*[T](val: T): Option[T] = ## Returns a ``Option`` that has this value. - result.has = true + when T is SomePointer: + assert val != nil + result.val = val + else: + result.has = true + result.val = val + +proc option*[T](val: T): Option[T] = + ## Can be used to convert a pointer type to an option type. It + ## converts ``nil`` to the none-option. result.val = val + when T isnot SomePointer: + result.has = true proc none*(T: typedesc): Option[T] = - ## Returns a ``Option`` for this type that has no value. - result.has = false + ## Returns an ``Option`` for this type that has no value. + # the default is the none type + discard -proc isSome*[T](self: Option[T]): bool = - self.has +proc none*[T]: Option[T] = + ## Alias for ``none(T)``. + none(T) -proc isNone*[T](self: Option[T]): bool = - not self.has +proc isSome*[T](self: Option[T]): bool {.inline.} = + when T is SomePointer: + self.val != nil + else: + self.has + +proc isNone*[T](self: Option[T]): bool {.inline.} = + when T is SomePointer: + self.val == nil + else: + not self.has proc unsafeGet*[T](self: Option[T]): T = ## Returns the value of a ``some``. Behavior is undefined for ``none``. @@ -100,32 +129,39 @@ proc get*[T](self: Option[T]): T = ## Returns contents of the Option. If it is none, then an exception is ## thrown. if self.isNone: - raise UnpackError(msg : "Can't obtain a value from a `none`") + raise UnpackError(msg: "Can't obtain a value from a `none`") self.val proc get*[T](self: Option[T], otherwise: T): T = ## Returns the contents of this option or `otherwise` if the option is none. - if self.has: + if self.isSome: self.val else: otherwise +proc get*[T](self: var Option[T]): var T = + ## Returns contents of the Option. If it is none, then an exception is + ## thrown. + if self.isNone: + raise UnpackError(msg: "Can't obtain a value from a `none`") + return self.val + proc map*[T](self: Option[T], callback: proc (input: T)) = ## Applies a callback to the value in this Option - if self.has: + if self.isSome: callback(self.val) proc map*[T, R](self: Option[T], callback: proc (input: T): R): Option[R] = ## Applies a callback to the value in this Option and returns an option - ## containing the new value. If this option is None, None will be returned. - if self.has: - some[R](callback(self.val)) + ## containing the new value. If this option is None, None will be returned + if self.isSome: + some[R]( callback(self.val) ) else: none(R) proc flatten*[A](self: Option[Option[A]]): Option[A] = ## Remove one level of structure in a nested Option. - if self.has: + if self.isSome: self.val else: none(A) @@ -142,7 +178,7 @@ proc filter*[T](self: Option[T], callback: proc (input: T): bool): Option[T] = ## Applies a callback to the value in this Option. If the callback returns ## `true`, the option is returned as a Some. If it returns false, it is ## returned as a None. - if self.has and not callback(self.val): + if self.isSome and not callback(self.val): none(T) else: self @@ -150,17 +186,19 @@ proc filter*[T](self: Option[T], callback: proc (input: T): bool): Option[T] = proc `==`*(a, b: Option): bool = ## Returns ``true`` if both ``Option``s are ``none``, ## or if they have equal values - (a.has and b.has and a.val == b.val) or (not a.has and not b.has) + (a.isSome and b.isSome and a.val == b.val) or (not a.isSome and not b.isSome) proc `$`*[T](self: Option[T]): string = ## Get the string representation of this option. If the option has a value, ## the result will be `Some(x)` where `x` is the string representation of the contained value. - ## If the option does not have a value, the result will be `None[T]` where `T` is the name of + ## If the option does not have a value, the result will be `None[T]` where `T` is the name of ## the type contained in the option. - if self.has: - "Some(" & $self.val & ")" + if self.isSome: + result = "Some(" + result.addQuoted self.val + result.add ")" else: - "None[" & T.name & "]" + result = "None[" & name(T) & "]" when isMainModule: import unittest, sequtils @@ -211,7 +249,7 @@ when isMainModule: check(stringNone.get("Correct") == "Correct") test "$": - check($(some("Correct")) == "Some(Correct)") + check($(some("Correct")) == "Some(\"Correct\")") check($(stringNone) == "None[string]") test "map with a void result": @@ -256,3 +294,30 @@ when isMainModule: check(some(1).flatMap(maybeToString).flatMap(maybeExclaim) == some("1!")) check(some(0).flatMap(maybeToString).flatMap(maybeExclaim) == none(string)) + + test "SomePointer": + var intref: ref int + check(option(intref).isNone) + intref.new + check(option(intref).isSome) + + let tmp = option(intref) + check(sizeof(tmp) == sizeof(ptr int)) + + test "none[T]": + check(none[int]().isNone) + check(none(int) == none[int]()) + + test "$ on typed with .name": + type Named = object + name: string + + let nobody = none(Named) + check($nobody == "None[Named]") + + test "$ on type with name()": + type Person = object + myname: string + + let noperson = none(Person) + check($noperson == "None[Person]") diff --git a/lib/pure/os.nim b/lib/pure/os.nim index f8936f549..2b3cf5142 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -10,7 +10,7 @@ ## This module contains basic operating system facilities like ## retrieving environment variables, reading command line arguments, ## working with directories, running shell commands, etc. -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated {.push debugger: off.} @@ -23,6 +23,10 @@ when defined(windows): import winlean elif defined(posix): import posix + + proc toTime(ts: Timespec): times.Time {.inline.} = + result = initTime(ts.tv_sec.int64, ts.tv_nsec.int) + else: {.error: "OS module not ported to your operating system!".} @@ -70,7 +74,8 @@ when defined(windows): proc existsFile*(filename: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect].} = - ## Returns true if the file exists, false otherwise. + ## Returns true if `filename` exists and is a regular file or symlink. + ## (directories, device files, named pipes and sockets return false) when defined(windows): when useWinUnicode: wrapUnary(a, getFileAttributesW, filename) @@ -139,6 +144,7 @@ proc findExe*(exe: string, followSymlinks: bool = true; ## is added the `ExeExts <#ExeExts>`_ file extensions if it has none. ## If the system supports symlinks it also resolves them until it ## meets the actual file. This behavior can be disabled if desired. + if exe.len == 0: return template checkCurrentDir() = for ext in extensions: result = addFileExt(exe, ext) @@ -149,6 +155,7 @@ proc findExe*(exe: string, followSymlinks: bool = true; checkCurrentDir() let path = string(getEnv("PATH")) for candidate in split(path, PathSep): + if candidate.len == 0: continue when defined(windows): var x = (if candidate[0] == '"' and candidate[^1] == '"': substr(candidate, 1, candidate.len-2) else: candidate) / @@ -183,12 +190,12 @@ proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1".} when defined(posix): var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) - return fromUnix(res.st_mtime.int64) + result = res.st_mtim.toTime else: var f: WIN32_FIND_DATA var h = findFirstFile(file, f) if h == -1'i32: raiseOSError(osLastError()) - result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftLastWriteTime)).int64) + result = fromWinTime(rdFileTime(f.ftLastWriteTime)) findClose(h) proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1".} = @@ -196,12 +203,12 @@ proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1".} = when defined(posix): var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) - return fromUnix(res.st_atime.int64) + result = res.st_atim.toTime else: var f: WIN32_FIND_DATA var h = findFirstFile(file, f) if h == -1'i32: raiseOSError(osLastError()) - result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftLastAccessTime)).int64) + result = fromWinTime(rdFileTime(f.ftLastAccessTime)) findClose(h) proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1".} = @@ -213,22 +220,25 @@ proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1".} = when defined(posix): var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) - return fromUnix(res.st_ctime.int64) + result = res.st_ctim.toTime else: var f: WIN32_FIND_DATA var h = findFirstFile(file, f) if h == -1'i32: raiseOSError(osLastError()) - result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftCreationTime)).int64) + result = fromWinTime(rdFileTime(f.ftCreationTime)) findClose(h) proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} = ## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s ## modification time is later than `b`'s. when defined(posix): - result = getLastModificationTime(a) - getLastModificationTime(b) >= 0 - # Posix's resolution sucks so, we use '>=' for posix. + # If we don't have access to nanosecond resolution, use '>=' + when not StatHasNanoseconds: + result = getLastModificationTime(a) >= getLastModificationTime(b) + else: + result = getLastModificationTime(a) > getLastModificationTime(b) else: - result = getLastModificationTime(a) - getLastModificationTime(b) > 0 + result = getLastModificationTime(a) > getLastModificationTime(b) proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} = ## Returns the `current working directory`:idx:. @@ -286,10 +296,30 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [].} = else: if chdir(newDir) != 0'i32: raiseOSError(osLastError()) +proc absolutePath*(path: string, root = getCurrentDir()): string = + ## Returns the absolute path of `path`, rooted at `root` (which must be absolute) + ## if `path` is absolute, return it, ignoring `root` + runnableExamples: + doAssert absolutePath("a") == getCurrentDir() / "a" + if isAbsolute(path): path + else: + if not root.isAbsolute: + raise newException(ValueError, "The specified root is not absolute: " & root) + joinPath(root, path) + +when isMainModule: + doAssertRaises(ValueError): discard absolutePath("a", "b") + doAssert absolutePath("a") == getCurrentDir() / "a" + doAssert absolutePath("a", "/b") == "/b" / "a" + when defined(Posix): + doAssert absolutePath("a", "/b/") == "/b" / "a" + doAssert absolutePath("a", "/b/c") == "/b/c" / "a" + doAssert absolutePath("/a", "b/") == "/a" + proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", tags: [ReadDirEffect].} = - ## Returns the full (`absolute`:idx:) path of the file `filename`, - ## raises OSError in case of an error. + ## Returns the full (`absolute`:idx:) path of an existing file `filename`, + ## raises OSError in case of an error. Follows symlinks. when defined(windows): var bufsize = MAX_PATH.int32 when useWinUnicode: @@ -328,21 +358,63 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", result = $r c_free(cast[pointer](r)) +proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [].} = + ## Normalize a path. + ## + ## Consecutive directory separators are collapsed, including an initial double slash. + ## + ## On relative paths, double dot (..) sequences are collapsed if possible. + ## On absolute paths they are always collapsed. + ## + ## Warning: URL-encoded and Unicode attempts at directory traversal are not detected. + ## Triple dot is not handled. + let isAbs = isAbsolute(path) + var stack: seq[string] = @[] + for p in split(path, {DirSep}): + case p + of "", ".": + continue + of "..": + if stack.len == 0: + if isAbs: + discard # collapse all double dots on absoluta paths + else: + stack.add(p) + elif stack[^1] == "..": + stack.add(p) + else: + discard stack.pop() + else: + stack.add(p) + + if isAbs: + path = DirSep & join(stack, $DirSep) + elif stack.len > 0: + path = join(stack, $DirSep) + else: + path = "." + +proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [].} = + ## Returns a normalized path for the current OS. See `<#normalizePath>`_ + result = path + normalizePath(result) + when defined(Windows): - proc openHandle(path: string, followSymlink=true): Handle = + proc openHandle(path: string, followSymlink=true, writeAccess=false): Handle = var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL if not followSymlink: flags = flags or FILE_FLAG_OPEN_REPARSE_POINT + let access = if writeAccess: GENERIC_WRITE else: 0'i32 when useWinUnicode: result = createFileW( - newWideCString(path), 0'i32, + newWideCString(path), access, FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, flags, 0 ) else: result = createFileA( - path, 0'i32, + path, access, FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, flags, 0 ) @@ -430,8 +502,6 @@ type fpOthersWrite, ## write access for others fpOthersRead ## read access for others -{.deprecated: [TFilePermission: FilePermission].} - proc getFilePermissions*(filename: string): set[FilePermission] {. rtl, extern: "nos$1", tags: [ReadDirEffect].} = ## retrieves file permissions for `filename`. `OSError` is raised in case of @@ -545,7 +615,10 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1", when not declared(ENOENT) and not defined(Windows): when NoFakeVars: - const ENOENT = cint(2) # 2 on most systems including Solaris + when not defined(haiku): + const ENOENT = cint(2) # 2 on most systems including Solaris + else: + const ENOENT = cint(-2147459069) else: var ENOENT {.importc, header: "<errno.h>".}: cint @@ -615,6 +688,7 @@ proc tryMoveFSObject(source, dest: string): bool = proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", tags: [ReadIOEffect, WriteIOEffect].} = ## Moves a file from `source` to `dest`. If this fails, `OSError` is raised. + ## Can be used to `rename files`:idx: if not tryMoveFSObject(source, dest): when not defined(windows): # Fallback to copy & del @@ -728,8 +802,6 @@ type pcDir, ## path refers to a directory pcLinkToDir ## path refers to a symbolic link to a directory -{.deprecated: [TPathComponent: PathComponent].} - when defined(posix): proc getSymlinkFileKind(path: string): PathComponent = @@ -811,7 +883,8 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: y = dir / y var k = pcFile - when defined(linux) or defined(macosx) or defined(bsd) or defined(genode): + when defined(linux) or defined(macosx) or + defined(bsd) or defined(genode) or defined(nintendoswitch): if x.d_type != DT_UNKNOWN: if x.d_type == DT_DIR: k = pcDir if x.d_type == DT_LNK: @@ -901,7 +974,15 @@ proc rawCreateDir(dir: string): bool = elif errno in {EEXIST, ENOSYS}: result = false else: - raiseOSError(osLastError()) + raiseOSError(osLastError(), dir) + elif defined(haiku): + let res = mkdir(dir, 0o777) + if res == 0'i32: + result = true + elif errno == EEXIST or errno == EROFS: + result = false + else: + raiseOSError(osLastError(), dir) elif defined(posix): let res = mkdir(dir, 0o777) if res == 0'i32: @@ -909,8 +990,8 @@ proc rawCreateDir(dir: string): bool = elif errno == EEXIST: result = false else: - echo res - raiseOSError(osLastError()) + #echo res + raiseOSError(osLastError(), dir) else: when useWinUnicode: wrapUnary(res, createDirectoryW, dir) @@ -922,9 +1003,10 @@ proc rawCreateDir(dir: string): bool = elif getLastError() == 183'i32: result = false else: - raiseOSError(osLastError()) + raiseOSError(osLastError(), dir) -proc existsOrCreateDir*(dir: string): bool = +proc existsOrCreateDir*(dir: string): bool {.rtl, extern: "nos$1", + tags: [WriteDirEffect, ReadDirEffect].} = ## Check if a `directory`:idx: `dir` exists, and create it otherwise. ## ## Does not create parent directories (fails if parent does not exist). @@ -934,7 +1016,7 @@ proc existsOrCreateDir*(dir: string): bool = if result: # path already exists - need to check that it is indeed a directory if not existsDir(dir): - raise newException(IOError, "Failed to create the directory") + raise newException(IOError, "Failed to create '" & dir & "'") proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect, ReadDirEffect].} = @@ -1057,18 +1139,17 @@ proc parseCmdLine*(c: string): seq[string] {. while true: setLen(a, 0) # eat all delimiting whitespace - while c[i] == ' ' or c[i] == '\t' or c[i] == '\l' or c[i] == '\r' : inc(i) + while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i) + if i >= c.len: break when defined(windows): # parse a single argument according to the above rules: - if c[i] == '\0': break var inQuote = false - while true: + while i < c.len: case c[i] - of '\0': break of '\\': var j = i - while c[j] == '\\': inc(j) - if c[j] == '"': + while j < c.len and c[j] == '\\': inc(j) + if j < c.len and c[j] == '"': for k in 1..(j-i) div 2: a.add('\\') if (j-i) mod 2 == 0: i = j @@ -1081,7 +1162,7 @@ proc parseCmdLine*(c: string): seq[string] {. of '"': inc(i) if not inQuote: inQuote = true - elif c[i] == '"': + elif i < c.len and c[i] == '"': a.add(c[i]) inc(i) else: @@ -1099,13 +1180,12 @@ proc parseCmdLine*(c: string): seq[string] {. of '\'', '\"': var delim = c[i] inc(i) # skip ' or " - while c[i] != '\0' and c[i] != delim: + while i < c.len and c[i] != delim: add a, c[i] inc(i) - if c[i] != '\0': inc(i) - of '\0': break + if i < c.len: inc(i) else: - while c[i] > ' ': + while i < c.len and c[i] > ' ': add(a, c[i]) inc(i) add(result, a) @@ -1263,22 +1343,40 @@ elif defined(windows): # is always the same -- independent of the used C compiler. var ownArgv {.threadvar.}: seq[string] + ownParsedArgv {.threadvar.}: bool proc paramCount*(): int {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = # Docstring in nimdoc block. - if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLine()) + if not ownParsedArgv: + ownArgv = parseCmdLine($getCommandLine()) + ownParsedArgv = true result = ownArgv.len-1 proc paramStr*(i: int): TaintedString {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = # Docstring in nimdoc block. - if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLine()) + if not ownParsedArgv: + ownArgv = parseCmdLine($getCommandLine()) + ownParsedArgv = true if i < ownArgv.len and i >= 0: return TaintedString(ownArgv[i]) raise newException(IndexError, "invalid index") +elif defined(nintendoswitch): + proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} = + raise newException(OSError, "paramStr is not implemented on Nintendo Switch") + + proc paramCount*(): int {.tags: [ReadIOEffect].} = + raise newException(OSError, "paramCount is not implemented on Nintendo Switch") + +elif defined(genode): + proc paramStr*(i: int): TaintedString = + raise newException(OSError, "paramStr is not implemented on Genode") + + proc paramCount*(): int = + raise newException(OSError, "paramCount is not implemented on Genode") + elif not defined(createNimRtl) and - not(defined(posix) and appType == "lib") and - not defined(genode): + not(defined(posix) and appType == "lib"): # On Posix, there is no portable way to get the command line from a DLL. var cmdCount {.importc: "cmdCount".}: cint @@ -1432,26 +1530,14 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = result = getApplAux("/proc/self/exe") elif defined(solaris): result = getApplAux("/proc/" & $getpid() & "/path/a.out") - elif defined(genode): - raiseOSError("POSIX command line not supported") + elif defined(genode) or defined(nintendoswitch): + raiseOSError(OSErrorCode(-1), "POSIX command line not supported") elif defined(freebsd) or defined(dragonfly): result = getApplFreebsd() # little heuristic that may work on other POSIX-like systems: if result.len == 0: result = getApplHeuristic() -proc getApplicationFilename*(): string {.rtl, extern: "nos$1", deprecated.} = - ## Returns the filename of the application's executable. - ## **Deprecated since version 0.8.12**: use ``getAppFilename`` - ## instead. - result = getAppFilename() - -proc getApplicationDir*(): string {.rtl, extern: "nos$1", deprecated.} = - ## Returns the directory of the application's executable. - ## **Deprecated since version 0.8.12**: use ``getAppDir`` - ## instead. - result = splitFile(getAppFilename()).dir - proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = ## Returns the directory of the application's executable. result = splitFile(getAppFilename()).dir @@ -1506,19 +1592,17 @@ type template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = ## Transforms the native file info structure into the one nim uses. - ## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows, + ## 'rawInfo' is either a 'BY_HANDLE_FILE_INFORMATION' structure on Windows, ## or a 'Stat' structure on posix when defined(Windows): - template toTime(e: FILETIME): untyped {.gensym.} = - fromUnix(winTimeToUnixTime(rdFileTime(e)).int64) # local templates default to bind semantics template merge(a, b): untyped = a or (b shl 32) formalInfo.id.device = rawInfo.dwVolumeSerialNumber formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh) formalInfo.size = merge(rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh) formalInfo.linkCount = rawInfo.nNumberOfLinks - formalInfo.lastAccessTime = toTime(rawInfo.ftLastAccessTime) - formalInfo.lastWriteTime = toTime(rawInfo.ftLastWriteTime) - formalInfo.creationTime = toTime(rawInfo.ftCreationTime) + formalInfo.lastAccessTime = fromWinTime(rdFileTime(rawInfo.ftLastAccessTime)) + formalInfo.lastWriteTime = fromWinTime(rdFileTime(rawInfo.ftLastWriteTime)) + formalInfo.creationTime = fromWinTime(rdFileTime(rawInfo.ftCreationTime)) # Retrieve basic permissions if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0'i32: @@ -1534,7 +1618,6 @@ template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32: formalInfo.kind = succ(result.kind) - else: template checkAndIncludeMode(rawMode, formalMode: untyped) = if (rawInfo.st_mode and rawMode) != 0'i32: @@ -1542,9 +1625,9 @@ template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino) formalInfo.size = rawInfo.st_size formalInfo.linkCount = rawInfo.st_Nlink.BiggestInt - formalInfo.lastAccessTime = fromUnix(rawInfo.st_atime.int64) - formalInfo.lastWriteTime = fromUnix(rawInfo.st_mtime.int64) - formalInfo.creationTime = fromUnix(rawInfo.st_ctime.int64) + formalInfo.lastAccessTime = rawInfo.st_atim.toTime + formalInfo.lastWriteTime = rawInfo.st_mtim.toTime + formalInfo.creationTime = rawInfo.st_ctim.toTime result.permissions = {} checkAndIncludeMode(S_IRUSR, fpUserRead) @@ -1652,3 +1735,20 @@ proc isHidden*(path: string): bool = result = (fileName[0] == '.') and (fileName[3] != '.') {.pop.} + +proc setLastModificationTime*(file: string, t: times.Time) = + ## Sets the `file`'s last modification time. `OSError` is raised in case of + ## an error. + when defined(posix): + let unixt = posix.Time(t.toUnix) + let micro = convert(Nanoseconds, Microseconds, t.nanosecond) + var timevals = [Timeval(tv_sec: unixt, tv_usec: micro), + Timeval(tv_sec: unixt, tv_usec: micro)] # [last access, last modification] + if utimes(file, timevals.addr) != 0: raiseOSError(osLastError()) + else: + let h = openHandle(path = file, writeAccess = true) + if h == INVALID_HANDLE_VALUE: raiseOSError(osLastError()) + var ft = t.toWinTime.toFILETIME + let res = setFileTime(h, nil, nil, ft.addr) + discard h.closeHandle + if res == 0'i32: raiseOSError(osLastError()) diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim index 0d638abb9..bc6739dd3 100644 --- a/lib/pure/ospaths.nim +++ b/lib/pure/ospaths.nim @@ -29,11 +29,6 @@ type OSErrorCode* = distinct int32 ## Specifies an OS Error Code. -{.deprecated: [FReadEnv: ReadEnvEffect, FWriteEnv: WriteEnvEffect, - FReadDir: ReadDirEffect, - FWriteDir: WriteDirEffect, - TOSErrorCode: OSErrorCode -].} const doslikeFileSystem* = defined(windows) or defined(OS2) or defined(DOS) @@ -152,7 +147,7 @@ else: # UNIX-like operating system DirSep* = '/' AltSep* = DirSep PathSep* = ':' - FileSystemCaseSensitive* = true + FileSystemCaseSensitive* = when defined(macosx): false else: true ExeExt* = "" ScriptExt* = "" DynlibFormat* = when defined(macosx): "lib$1.dylib" else: "lib$1.so" @@ -191,12 +186,12 @@ proc joinPath*(head, tail: string): string {. if len(head) == 0: result = tail elif head[len(head)-1] in {DirSep, AltSep}: - if tail[0] in {DirSep, AltSep}: + if tail.len > 0 and tail[0] in {DirSep, AltSep}: result = head & substr(tail, 1) else: result = head & tail else: - if tail[0] in {DirSep, AltSep}: + if tail.len > 0 and tail[0] in {DirSep, AltSep}: result = head & tail else: result = head & DirSep & tail @@ -415,6 +410,11 @@ proc cmpPaths*(pathA, pathB: string): int {. ## | 0 iff pathA == pathB ## | < 0 iff pathA < pathB ## | > 0 iff pathA > pathB + runnableExamples: + when defined(macosx): + doAssert cmpPaths("foo", "Foo") == 0 + elif defined(posix): + doAssert cmpPaths("foo", "Foo") > 0 if FileSystemCaseSensitive: result = cmp(pathA, pathB) else: @@ -428,17 +428,52 @@ proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = ## Checks whether a given `path` is absolute. ## ## On Windows, network paths are considered absolute too. + runnableExamples: + doAssert(not "".isAbsolute) + doAssert(not ".".isAbsolute) + when defined(posix): + doAssert "/".isAbsolute + doAssert(not "a/".isAbsolute) + + if len(path) == 0: return false + when doslikeFileSystem: var len = len(path) - result = (len > 0 and path[0] in {'/', '\\'}) or + result = (path[0] in {'/', '\\'}) or (len > 1 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':') elif defined(macos): - result = path.len > 0 and path[0] != ':' + # according to https://perldoc.perl.org/File/Spec/Mac.html `:a` is a relative path + result = path[0] != ':' elif defined(RISCOS): result = path[0] == '$' elif defined(posix): result = path[0] == '/' + +proc normalizePathEnd(path: var string, trailingSep = false) = + ## ensures ``path`` has exactly 0 or 1 trailing `DirSep`, depending on + ## ``trailingSep``, and taking care of edge cases: it preservers whether + ## a path is absolute or relative, and makes sure trailing sep is `DirSep`, + ## not `AltSep`. + if path.len == 0: return + var i = path.len + while i >= 1 and path[i-1] in {DirSep, AltSep}: dec(i) + if trailingSep: + # foo// => foo + path.setLen(i) + # foo => foo/ + path.add DirSep + elif i>0: + # foo// => foo + path.setLen(i) + else: + # // => / (empty case was already taken care of) + path = $DirSep + +proc normalizePathEnd(path: string, trailingSep = false): string = + result = path + result.normalizePathEnd(trailingSep) + proc unixToNativePath*(path: string, drive=""): string {. noSideEffect, rtl, extern: "nos$1".} = ## Converts an UNIX-like path to a native one. @@ -454,6 +489,9 @@ proc unixToNativePath*(path: string, drive=""): string {. when defined(unix): result = path else: + if path.len == 0: + return "" + var start: int if path[0] == '/': # an absolute path @@ -467,17 +505,17 @@ proc unixToNativePath*(path: string, drive=""): string {. else: result = $DirSep start = 1 - elif path[0] == '.' and path[1] == '/': + elif path[0] == '.' and (path.len == 1 or path[1] == '/'): # current directory result = $CurDir - start = 2 + start = when doslikeFileSystem: 1 else: 2 else: result = "" start = 0 var i = start while i < len(path): # ../../../ --> :::: - if path[i] == '.' and path[i+1] == '.' and path[i+2] == '/': + if i+2 < path.len and path[i] == '.' and path[i+1] == '.' and path[i+2] == '/': # parent directory when defined(macos): if result[high(result)] == ':': @@ -512,15 +550,17 @@ proc getConfigDir*(): string {.rtl, extern: "nos$1", ## Returns the config directory of the current user for applications. ## ## On non-Windows OSs, this proc conforms to the XDG Base Directory - ## spec. Thus, this proc returns the value of the XDG_CONFIG_DIR environment + ## spec. Thus, this proc returns the value of the XDG_CONFIG_HOME environment ## variable if it is set, and returns the default configuration directory, ## "~/.config/", otherwise. ## ## An OS-dependent trailing slash is always present at the end of the - ## returned string; `\\` on Windows and `/` on all other OSs. - when defined(windows): return string(getEnv("APPDATA")) & "\\" - elif getEnv("XDG_CONFIG_DIR"): return string(getEnv("XDG_CONFIG_DIR")) & "/" - else: return string(getEnv("HOME")) & "/.config/" + ## returned string; `\` on Windows and `/` on all other OSs. + when defined(windows): + result = getEnv("APPDATA").string + else: + result = getEnv("XDG_CONFIG_HOME", getEnv("HOME").string / ".config").string + result.normalizePathEnd(trailingSep = true) proc getTempDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect, ReadIOEffect].} = @@ -540,25 +580,25 @@ proc getTempDir*(): string {.rtl, extern: "nos$1", proc expandTilde*(path: string): string {. tags: [ReadEnvEffect, ReadIOEffect].} = - ## Expands a path starting with ``~/`` to a full path. - ## - ## If `path` starts with the tilde character and is followed by `/` or `\\` - ## this proc will return the reminder of the path appended to the result of - ## the getHomeDir() proc, otherwise the input path will be returned without - ## modification. + ## Expands ``~`` or a path starting with ``~/`` to a full path, replacing + ## ``~`` with ``getHomeDir()`` (otherwise returns ``path`` unmodified). ## - ## The behaviour of this proc is the same on the Windows platform despite - ## not having this convention. Example: - ## - ## .. code-block:: nim - ## let configFile = expandTilde("~" / "appname.cfg") - ## echo configFile - ## # --> C:\Users\amber\appname.cfg - if len(path) > 1 and path[0] == '~' and (path[1] == '/' or path[1] == '\\'): + ## Windows: this is still supported despite Windows platform not having this + ## convention; also, both ``~/`` and ``~\`` are handled. + runnableExamples: + doAssert expandTilde("~" / "appname.cfg") == getHomeDir() / "appname.cfg" + if len(path) == 0 or path[0] != '~': + result = path + elif len(path) == 1: + result = getHomeDir() + elif (path[1] in {DirSep, AltSep}): result = getHomeDir() / path.substr(2) else: + # TODO: handle `~bob` and `~bob/` which means home of bob result = path +# TODO: consider whether quoteShellPosix, quoteShellWindows, quoteShell, quoteShellCommand +# belong in `strutils` instead; they are not specific to paths proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = ## Quote s, so it can be safely passed to Windows API. ## Based on Python's subprocess.list2cmdline @@ -602,7 +642,7 @@ proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} else: return "'" & s.replace("'", "'\"'\"'") & "'" -when defined(windows) or defined(posix): +when defined(windows) or defined(posix) or defined(nintendoswitch): proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = ## Quote ``s``, so it can be safely passed to shell. when defined(windows): @@ -610,6 +650,18 @@ when defined(windows) or defined(posix): else: return quoteShellPosix(s) + proc quoteShellCommand*(args: openArray[string]): string = + ## Concatenates and quotes shell arguments `args` + runnableExamples: + when defined(posix): + assert quoteShellCommand(["aaa", "", "c d"]) == "aaa '' 'c d'" + when defined(windows): + assert quoteShellCommand(["aaa", "", "c d"]) == "aaa \"\" \"c d\"" + # can't use `map` pending https://github.com/nim-lang/Nim/issues/8303 + for i in 0..<args.len: + if i > 0: result.add " " + result.add quoteShell(args[i]) + when isMainModule: assert quoteShellWindows("aaa") == "aaa" assert quoteShellWindows("aaa\"") == "aaa\\\"" @@ -622,3 +674,24 @@ when isMainModule: when defined(posix): assert quoteShell("") == "''" + + block normalizePathEndTest: + # handle edge cases correctly: shouldn't affect whether path is + # absolute/relative + doAssert "".normalizePathEnd(true) == "" + doAssert "".normalizePathEnd(false) == "" + doAssert "/".normalizePathEnd(true) == $DirSep + doAssert "/".normalizePathEnd(false) == $DirSep + + when defined(posix): + doAssert "//".normalizePathEnd(false) == "/" + doAssert "foo.bar//".normalizePathEnd == "foo.bar" + doAssert "bar//".normalizePathEnd(trailingSep = true) == "bar/" + when defined(Windows): + doAssert r"C:\foo\\".normalizePathEnd == r"C:\foo" + doAssert r"C:\foo".normalizePathEnd(trailingSep = true) == r"C:\foo\" + # this one is controversial: we could argue for returning `D:\` instead, + # but this is simplest. + doAssert r"D:\".normalizePathEnd == r"D:" + doAssert r"E:/".normalizePathEnd(trailingSep = true) == r"E:\" + doAssert "/".normalizePathEnd == r"\" diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index c1c727fc6..faeb01407 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -61,9 +61,6 @@ type Process* = ref ProcessObj ## represents an operating system process -{.deprecated: [TProcess: ProcessObj, PProcess: Process, - TProcessOption: ProcessOption].} - const poUseShell* {.deprecated.} = poUsePath ## Deprecated alias for poUsePath. @@ -373,17 +370,15 @@ template streamAccess(p) = when defined(Windows) and not defined(useNimRtl): # We need to implement a handle stream for Windows: type - PFileHandleStream = ref FileHandleStream - FileHandleStream = object of StreamObj + FileHandleStream = ref object of StreamObj handle: Handle atTheEnd: bool - {.deprecated: [TFileHandleStream: FileHandleStream].} proc hsClose(s: Stream) = discard # nothing to do here - proc hsAtEnd(s: Stream): bool = return PFileHandleStream(s).atTheEnd + proc hsAtEnd(s: Stream): bool = return FileHandleStream(s).atTheEnd proc hsReadData(s: Stream, buffer: pointer, bufLen: int): int = - var s = PFileHandleStream(s) + var s = FileHandleStream(s) if s.atTheEnd: return 0 var br: int32 var a = winlean.readFile(s.handle, buffer, bufLen.cint, addr br, nil) @@ -395,13 +390,13 @@ when defined(Windows) and not defined(useNimRtl): result = br proc hsWriteData(s: Stream, buffer: pointer, bufLen: int) = - var s = PFileHandleStream(s) + var s = FileHandleStream(s) var bytesWritten: int32 var a = winlean.writeFile(s.handle, buffer, bufLen.cint, addr bytesWritten, nil) if a == 0: raiseOSError(osLastError()) - proc newFileHandleStream(handle: Handle): PFileHandleStream = + proc newFileHandleStream(handle: Handle): FileHandleStream = new(result) result.handle = handle result.closeImpl = hsClose @@ -494,7 +489,7 @@ when defined(Windows) and not defined(useNimRtl): sa.nLength = sizeof(SECURITY_ATTRIBUTES).cint sa.lpSecurityDescriptor = nil sa.bInheritHandle = 1 - if createPipe(rdHandle, wrHandle, sa, 1024) == 0'i32: + if createPipe(rdHandle, wrHandle, sa, 0) == 0'i32: raiseOSError(osLastError()) proc fileClose(h: Handle) {.inline.} = @@ -524,6 +519,12 @@ when defined(Windows) and not defined(useNimRtl): he = ho else: createPipeHandles(he, si.hStdError) + if setHandleInformation(he, DWORD(1), DWORD(0)) == 0'i32: + raiseOsError(osLastError()) + if setHandleInformation(hi, DWORD(1), DWORD(0)) == 0'i32: + raiseOsError(osLastError()) + if setHandleInformation(ho, DWORD(1), DWORD(0)) == 0'i32: + raiseOsError(osLastError()) else: createAllPipeHandles(si, hi, ho, he, cast[int](result)) result.inHandle = FileHandle(hi) @@ -746,14 +747,14 @@ elif not defined(useNimRtl): copyMem(result[i], addr(x[0]), x.len+1) inc(i) - type StartProcessData = object - sysCommand: string - sysArgs: cstringArray - sysEnv: cstringArray - workingDir: cstring - pStdin, pStdout, pStderr, pErrorPipe: array[0..1, cint] - options: set[ProcessOption] - {.deprecated: [TStartProcessData: StartProcessData].} + type + StartProcessData = object + sysCommand: string + sysArgs: cstringArray + sysEnv: cstringArray + workingDir: cstring + pStdin, pStdout, pStderr, pErrorPipe: array[0..1, cint] + options: set[ProcessOption] const useProcessAuxSpawn = declared(posix_spawn) and not defined(useFork) and not defined(useClone) and not defined(linux) @@ -831,7 +832,7 @@ elif not defined(useNimRtl): # Parent process. Copy process information. if poEchoCmd in options: - echo(command, " ", join(args, " ")) + when declared(echo): echo(command, " ", join(args, " ")) result.id = pid result.exitFlag = false @@ -884,7 +885,7 @@ elif not defined(useNimRtl): chck posix_spawn_file_actions_addclose(fops, data.pStdout[readIdx]) chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], writeIdx) chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx]) - if (poStdErrToStdOut in data.options): + if poStdErrToStdOut in data.options: chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 2) else: chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2) @@ -995,13 +996,21 @@ elif not defined(useNimRtl): {.pop} proc close(p: Process) = - if p.inStream != nil: close(p.inStream) - if p.outStream != nil: close(p.outStream) - if p.errStream != nil: close(p.errStream) if poParentStreams notin p.options: - discard close(p.inHandle) - discard close(p.outHandle) - discard close(p.errHandle) + if p.inStream != nil: + close(p.inStream) + else: + discard close(p.inHandle) + + if p.outStream != nil: + close(p.outStream) + else: + discard close(p.outHandle) + + if p.errStream != nil: + close(p.errStream) + else: + discard close(p.errHandle) proc suspend(p: Process) = if kill(p.id, SIGSTOP) != 0'i32: raiseOsError(osLastError()) @@ -1275,7 +1284,7 @@ elif not defined(useNimRtl): proc select(readfds: var seq[Process], timeout = 500): int = var tv: Timeval - tv.tv_sec = 0 + tv.tv_sec = posix.Time(0) tv.tv_usec = timeout * 1000 var rd: TFdSet diff --git a/lib/pure/parsecfg.nim b/lib/pure/parsecfg.nim index 2a5dbc8f8..b991dd57f 100644 --- a/lib/pure/parsecfg.nim +++ b/lib/pure/parsecfg.nim @@ -17,12 +17,37 @@ ## ## .. include:: ../../doc/mytest.cfg ## :literal: -## The file ``examples/parsecfgex.nim`` demonstrates how to use the -## configuration file parser: -## -## .. code-block:: nim -## :file: ../../examples/parsecfgex.nim ## + +##[ Here is an example of how to use the configuration file parser: + +.. code-block:: nim + + import + os, parsecfg, strutils, streams + + var f = newFileStream(paramStr(1), fmRead) + if f != nil: + var p: CfgParser + open(p, f, paramStr(1)) + while true: + var e = next(p) + case e.kind + of cfgEof: break + of cfgSectionStart: ## a ``[section]`` has been parsed + echo("new section: " & e.section) + of cfgKeyValuePair: + echo("key-value-pair: " & e.key & ": " & e.value) + of cfgOption: + echo("command: " & e.key & ": " & e.value) + of cfgError: + echo(e.msg) + close(p) + else: + echo("cannot open: " & paramStr(1)) + +]## + ## Examples ## -------- ## @@ -125,9 +150,6 @@ type tok: Token filename: string -{.deprecated: [TCfgEventKind: CfgEventKind, TCfgEvent: CfgEvent, - TTokKind: TokKind, TToken: Token, TCfgParser: CfgParser].} - # implementation const diff --git a/lib/pure/parsecsv.nim b/lib/pure/parsecsv.nim index 358ce829d..796114d37 100644 --- a/lib/pure/parsecsv.nim +++ b/lib/pure/parsecsv.nim @@ -66,8 +66,6 @@ type CsvError* = object of IOError ## exception that is raised if ## a parsing error occurs -{.deprecated: [TCsvRow: CsvRow, TCsvParser: CsvParser, EInvalidCsv: CsvError].} - proc raiseEInvalidCsv(filename: string, line, col: int, msg: string) {.noreturn.} = var e: ref CsvError diff --git a/lib/pure/parsejson.nim b/lib/pure/parsejson.nim new file mode 100644 index 000000000..9c53af6a6 --- /dev/null +++ b/lib/pure/parsejson.nim @@ -0,0 +1,535 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2018 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements a json parser. It is used +## and exported by the ``json`` standard library +## module, but can also be used in its own right. + +import + strutils, lexbase, streams, unicode + +type + JsonEventKind* = enum ## enumeration of all events that may occur when parsing + jsonError, ## an error occurred during parsing + jsonEof, ## end of file reached + jsonString, ## a string literal + jsonInt, ## an integer literal + jsonFloat, ## a float literal + jsonTrue, ## the value ``true`` + jsonFalse, ## the value ``false`` + jsonNull, ## the value ``null`` + jsonObjectStart, ## start of an object: the ``{`` token + jsonObjectEnd, ## end of an object: the ``}`` token + jsonArrayStart, ## start of an array: the ``[`` token + jsonArrayEnd ## start of an array: the ``]`` token + + TokKind* = enum # must be synchronized with TJsonEventKind! + tkError, + tkEof, + tkString, + tkInt, + tkFloat, + tkTrue, + tkFalse, + tkNull, + tkCurlyLe, + tkCurlyRi, + tkBracketLe, + tkBracketRi, + tkColon, + tkComma + + JsonError* = enum ## enumeration that lists all errors that can occur + errNone, ## no error + errInvalidToken, ## invalid token + errStringExpected, ## string expected + errColonExpected, ## ``:`` expected + errCommaExpected, ## ``,`` expected + errBracketRiExpected, ## ``]`` expected + errCurlyRiExpected, ## ``}`` expected + errQuoteExpected, ## ``"`` or ``'`` expected + errEOC_Expected, ## ``*/`` expected + errEofExpected, ## EOF expected + errExprExpected ## expr expected + + ParserState = enum + stateEof, stateStart, stateObject, stateArray, stateExpectArrayComma, + stateExpectObjectComma, stateExpectColon, stateExpectValue + + JsonParser* = object of BaseLexer ## the parser object. + a*: string + tok*: TokKind + kind: JsonEventKind + err: JsonError + state: seq[ParserState] + filename: string + rawStringLiterals: bool + + JsonKindError* = object of ValueError ## raised by the ``to`` macro if the + ## JSON kind is incorrect. + JsonParsingError* = object of ValueError ## is raised for a JSON error + +const + errorMessages*: array[JsonError, string] = [ + "no error", + "invalid token", + "string expected", + "':' expected", + "',' expected", + "']' expected", + "'}' expected", + "'\"' or \"'\" expected", + "'*/' expected", + "EOF expected", + "expression expected" + ] + tokToStr: array[TokKind, string] = [ + "invalid token", + "EOF", + "string literal", + "int literal", + "float literal", + "true", + "false", + "null", + "{", "}", "[", "]", ":", "," + ] + +proc open*(my: var JsonParser, input: Stream, filename: string; + rawStringLiterals = false) = + ## initializes the parser with an input stream. `Filename` is only used + ## for nice error messages. If `rawStringLiterals` is true, string literals + ## are kepts with their surrounding quotes and escape sequences in them are + ## left untouched too. + lexbase.open(my, input) + my.filename = filename + my.state = @[stateStart] + my.kind = jsonError + my.a = "" + my.rawStringLiterals = rawStringLiterals + +proc close*(my: var JsonParser) {.inline.} = + ## closes the parser `my` and its associated input stream. + lexbase.close(my) + +proc str*(my: JsonParser): string {.inline.} = + ## returns the character data for the events: ``jsonInt``, ``jsonFloat``, + ## ``jsonString`` + assert(my.kind in {jsonInt, jsonFloat, jsonString}) + return my.a + +proc getInt*(my: JsonParser): BiggestInt {.inline.} = + ## returns the number for the event: ``jsonInt`` + assert(my.kind == jsonInt) + return parseBiggestInt(my.a) + +proc getFloat*(my: JsonParser): float {.inline.} = + ## returns the number for the event: ``jsonFloat`` + assert(my.kind == jsonFloat) + return parseFloat(my.a) + +proc kind*(my: JsonParser): JsonEventKind {.inline.} = + ## returns the current event type for the JSON parser + return my.kind + +proc getColumn*(my: JsonParser): int {.inline.} = + ## get the current column the parser has arrived at. + result = getColNumber(my, my.bufpos) + +proc getLine*(my: JsonParser): int {.inline.} = + ## get the current line the parser has arrived at. + result = my.lineNumber + +proc getFilename*(my: JsonParser): string {.inline.} = + ## get the filename of the file that the parser processes. + result = my.filename + +proc errorMsg*(my: JsonParser): string = + ## returns a helpful error message for the event ``jsonError`` + assert(my.kind == jsonError) + result = "$1($2, $3) Error: $4" % [ + my.filename, $getLine(my), $getColumn(my), errorMessages[my.err]] + +proc errorMsgExpected*(my: JsonParser, e: string): string = + ## returns an error message "`e` expected" in the same format as the + ## other error messages + result = "$1($2, $3) Error: $4" % [ + my.filename, $getLine(my), $getColumn(my), e & " expected"] + +proc handleHexChar(c: char, x: var int): bool = + result = true # Success + case c + of '0'..'9': x = (x shl 4) or (ord(c) - ord('0')) + of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10) + of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10) + else: result = false # error + +proc parseEscapedUTF16*(buf: cstring, pos: var int): int = + result = 0 + #UTF-16 escape is always 4 bytes. + for _ in 0..3: + if handleHexChar(buf[pos], result): + inc(pos) + else: + return -1 + +proc parseString(my: var JsonParser): TokKind = + result = tkString + var pos = my.bufpos + 1 + var buf = my.buf + if my.rawStringLiterals: + add(my.a, '"') + while true: + case buf[pos] + of '\0': + my.err = errQuoteExpected + result = tkError + break + of '"': + if my.rawStringLiterals: + add(my.a, '"') + inc(pos) + break + of '\\': + if my.rawStringLiterals: + add(my.a, '\\') + case buf[pos+1] + of '\\', '"', '\'', '/': + add(my.a, buf[pos+1]) + inc(pos, 2) + of 'b': + add(my.a, '\b') + inc(pos, 2) + of 'f': + add(my.a, '\f') + inc(pos, 2) + of 'n': + add(my.a, '\L') + inc(pos, 2) + of 'r': + add(my.a, '\C') + inc(pos, 2) + of 't': + add(my.a, '\t') + inc(pos, 2) + of 'u': + if my.rawStringLiterals: + add(my.a, 'u') + inc(pos, 2) + var pos2 = pos + var r = parseEscapedUTF16(buf, pos) + if r < 0: + my.err = errInvalidToken + break + # Deal with surrogates + if (r and 0xfc00) == 0xd800: + if buf[pos] != '\\' or buf[pos+1] != 'u': + my.err = errInvalidToken + break + inc(pos, 2) + var s = parseEscapedUTF16(buf, pos) + if (s and 0xfc00) == 0xdc00 and s > 0: + r = 0x10000 + (((r - 0xd800) shl 10) or (s - 0xdc00)) + else: + my.err = errInvalidToken + break + if my.rawStringLiterals: + let length = pos - pos2 + for i in 1 .. length: + if buf[pos2] in {'0'..'9', 'A'..'F', 'a'..'f'}: + add(my.a, buf[pos2]) + inc pos2 + else: + break + else: + add(my.a, toUTF8(Rune(r))) + else: + # don't bother with the error + add(my.a, buf[pos]) + inc(pos) + of '\c': + pos = lexbase.handleCR(my, pos) + buf = my.buf + add(my.a, '\c') + of '\L': + pos = lexbase.handleLF(my, pos) + buf = my.buf + add(my.a, '\L') + else: + add(my.a, buf[pos]) + inc(pos) + my.bufpos = pos # store back + +proc skip(my: var JsonParser) = + var pos = my.bufpos + var buf = my.buf + while true: + case buf[pos] + of '/': + if buf[pos+1] == '/': + # skip line comment: + inc(pos, 2) + while true: + case buf[pos] + of '\0': + break + of '\c': + pos = lexbase.handleCR(my, pos) + buf = my.buf + break + of '\L': + pos = lexbase.handleLF(my, pos) + buf = my.buf + break + else: + inc(pos) + elif buf[pos+1] == '*': + # skip long comment: + inc(pos, 2) + while true: + case buf[pos] + of '\0': + my.err = errEOC_Expected + break + of '\c': + pos = lexbase.handleCR(my, pos) + buf = my.buf + of '\L': + pos = lexbase.handleLF(my, pos) + buf = my.buf + of '*': + inc(pos) + if buf[pos] == '/': + inc(pos) + break + else: + inc(pos) + else: + break + of ' ', '\t': + inc(pos) + of '\c': + pos = lexbase.handleCR(my, pos) + buf = my.buf + of '\L': + pos = lexbase.handleLF(my, pos) + buf = my.buf + else: + break + my.bufpos = pos + +proc parseNumber(my: var JsonParser) = + var pos = my.bufpos + var buf = my.buf + if buf[pos] == '-': + add(my.a, '-') + inc(pos) + if buf[pos] == '.': + add(my.a, "0.") + inc(pos) + else: + while buf[pos] in Digits: + add(my.a, buf[pos]) + inc(pos) + if buf[pos] == '.': + add(my.a, '.') + inc(pos) + # digits after the dot: + while buf[pos] in Digits: + add(my.a, buf[pos]) + inc(pos) + if buf[pos] in {'E', 'e'}: + add(my.a, buf[pos]) + inc(pos) + if buf[pos] in {'+', '-'}: + add(my.a, buf[pos]) + inc(pos) + while buf[pos] in Digits: + add(my.a, buf[pos]) + inc(pos) + my.bufpos = pos + +proc parseName(my: var JsonParser) = + var pos = my.bufpos + var buf = my.buf + if buf[pos] in IdentStartChars: + while buf[pos] in IdentChars: + add(my.a, buf[pos]) + inc(pos) + my.bufpos = pos + +proc getTok*(my: var JsonParser): TokKind = + setLen(my.a, 0) + skip(my) # skip whitespace, comments + case my.buf[my.bufpos] + of '-', '.', '0'..'9': + parseNumber(my) + if {'.', 'e', 'E'} in my.a: + result = tkFloat + else: + result = tkInt + of '"': + result = parseString(my) + of '[': + inc(my.bufpos) + result = tkBracketLe + of '{': + inc(my.bufpos) + result = tkCurlyLe + of ']': + inc(my.bufpos) + result = tkBracketRi + of '}': + inc(my.bufpos) + result = tkCurlyRi + of ',': + inc(my.bufpos) + result = tkComma + of ':': + inc(my.bufpos) + result = tkColon + of '\0': + result = tkEof + of 'a'..'z', 'A'..'Z', '_': + parseName(my) + case my.a + of "null": result = tkNull + of "true": result = tkTrue + of "false": result = tkFalse + else: result = tkError + else: + inc(my.bufpos) + result = tkError + my.tok = result + + +proc next*(my: var JsonParser) = + ## retrieves the first/next event. This controls the parser. + var tk = getTok(my) + var i = my.state.len-1 + # the following code is a state machine. If we had proper coroutines, + # the code could be much simpler. + case my.state[i] + of stateEof: + if tk == tkEof: + my.kind = jsonEof + else: + my.kind = jsonError + my.err = errEofExpected + of stateStart: + # tokens allowed? + case tk + of tkString, tkInt, tkFloat, tkTrue, tkFalse, tkNull: + my.state[i] = stateEof # expect EOF next! + my.kind = JsonEventKind(ord(tk)) + of tkBracketLe: + my.state.add(stateArray) # we expect any + my.kind = jsonArrayStart + of tkCurlyLe: + my.state.add(stateObject) + my.kind = jsonObjectStart + of tkEof: + my.kind = jsonEof + else: + my.kind = jsonError + my.err = errEofExpected + of stateObject: + case tk + of tkString, tkInt, tkFloat, tkTrue, tkFalse, tkNull: + my.state.add(stateExpectColon) + my.kind = JsonEventKind(ord(tk)) + of tkBracketLe: + my.state.add(stateExpectColon) + my.state.add(stateArray) + my.kind = jsonArrayStart + of tkCurlyLe: + my.state.add(stateExpectColon) + my.state.add(stateObject) + my.kind = jsonObjectStart + of tkCurlyRi: + my.kind = jsonObjectEnd + discard my.state.pop() + else: + my.kind = jsonError + my.err = errCurlyRiExpected + of stateArray: + case tk + of tkString, tkInt, tkFloat, tkTrue, tkFalse, tkNull: + my.state.add(stateExpectArrayComma) # expect value next! + my.kind = JsonEventKind(ord(tk)) + of tkBracketLe: + my.state.add(stateExpectArrayComma) + my.state.add(stateArray) + my.kind = jsonArrayStart + of tkCurlyLe: + my.state.add(stateExpectArrayComma) + my.state.add(stateObject) + my.kind = jsonObjectStart + of tkBracketRi: + my.kind = jsonArrayEnd + discard my.state.pop() + else: + my.kind = jsonError + my.err = errBracketRiExpected + of stateExpectArrayComma: + case tk + of tkComma: + discard my.state.pop() + next(my) + of tkBracketRi: + my.kind = jsonArrayEnd + discard my.state.pop() # pop stateExpectArrayComma + discard my.state.pop() # pop stateArray + else: + my.kind = jsonError + my.err = errBracketRiExpected + of stateExpectObjectComma: + case tk + of tkComma: + discard my.state.pop() + next(my) + of tkCurlyRi: + my.kind = jsonObjectEnd + discard my.state.pop() # pop stateExpectObjectComma + discard my.state.pop() # pop stateObject + else: + my.kind = jsonError + my.err = errCurlyRiExpected + of stateExpectColon: + case tk + of tkColon: + my.state[i] = stateExpectValue + next(my) + else: + my.kind = jsonError + my.err = errColonExpected + of stateExpectValue: + case tk + of tkString, tkInt, tkFloat, tkTrue, tkFalse, tkNull: + my.state[i] = stateExpectObjectComma + my.kind = JsonEventKind(ord(tk)) + of tkBracketLe: + my.state[i] = stateExpectObjectComma + my.state.add(stateArray) + my.kind = jsonArrayStart + of tkCurlyLe: + my.state[i] = stateExpectObjectComma + my.state.add(stateObject) + my.kind = jsonObjectStart + else: + my.kind = jsonError + my.err = errExprExpected + +proc raiseParseErr*(p: JsonParser, msg: string) {.noinline, noreturn.} = + ## raises an `EJsonParsingError` exception. + raise newException(JsonParsingError, errorMsgExpected(p, msg)) + +proc eat*(p: var JsonParser, tok: TokKind) = + if p.tok == tok: discard getTok(p) + else: raiseParseErr(p, tokToStr[tok]) diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index 23568edb9..c91134738 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -11,11 +11,23 @@ ## It supports one convenience iterator over all command line options and some ## lower-level features. ## -## Supported syntax: +## Supported syntax with default empty ``shortNoVal``/``longNoVal``: ## ## 1. short options - ``-abcd``, where a, b, c, d are names ## 2. long option - ``--foo:bar``, ``--foo=bar`` or ``--foo`` ## 3. argument - everything else +## +## When ``shortNoVal``/``longNoVal`` are non-empty then the ':' and '=' above +## are still accepted, but become optional. Note that these option key sets +## must be updated along with the set of option keys taking no value, but +## keys which do take values need no special updates as their set evolves. +## +## When option values begin with ':' or '=' they need to be doubled up (as in +## ``--delim::``) or alternated (as in ``--delim=:``). +## +## The common ``--`` non-option argument delimiter appears as an empty string +## long option key. ``OptParser.cmd``, ``OptParser.pos``, and +## ``os.parseCmdLine`` may be used to complete parsing in that case. {.push debugger: off.} @@ -32,37 +44,41 @@ type cmdShortOption ## a short option ``-c`` detected OptParser* = object of RootObj ## this object implements the command line parser - cmd: string - pos: int + cmd*: string # cmd,pos exported so caller can catch "--" as.. + pos*: int # ..empty key or subcmd cmdArg & handle specially inShortState: bool + shortNoVal: set[char] + longNoVal: seq[string] + cmds: seq[string] + idx: int kind*: CmdLineKind ## the dected command line token key*, val*: TaintedString ## key and value pair; ``key`` is the option ## or the argument, ``value`` is not "" if ## the option was given a value -{.deprecated: [TCmdLineKind: CmdLineKind, TOptParser: OptParser].} - proc parseWord(s: string, i: int, w: var string, - delim: set[char] = {'\x09', ' ', '\0'}): int = + delim: set[char] = {'\t', ' '}): int = result = i - if s[result] == '\"': + if result < s.len and s[result] == '\"': inc(result) - while not (s[result] in {'\0', '\"'}): + while result < s.len: + if s[result] == '"': + inc result + break add(w, s[result]) inc(result) - if s[result] == '\"': inc(result) else: - while not (s[result] in delim): + while result < s.len and s[result] notin delim: add(w, s[result]) inc(result) when declared(os.paramCount): proc quote(s: string): string = - if find(s, {' ', '\t'}) >= 0 and s[0] != '"': + if find(s, {' ', '\t'}) >= 0 and s.len > 0 and s[0] != '"': if s[0] == '-': result = newStringOfCap(s.len) - var i = parseWord(s, 0, result, {'\0', ' ', '\x09', ':', '='}) - if s[i] in {':','='}: + var i = parseWord(s, 0, result, {' ', '\t', ':', '='}) + if i < s.len and s[i] in {':','='}: result.add s[i] inc i result.add '"' @@ -78,83 +94,157 @@ when declared(os.paramCount): # we cannot provide this for NimRtl creation on Posix, because we can't # access the command line arguments then! - proc initOptParser*(cmdline = ""): OptParser = + proc initOptParser*(cmdline = "", shortNoVal: set[char]={}, + longNoVal: seq[string] = @[]): OptParser = ## inits the option parser. If ``cmdline == ""``, the real command line - ## (as provided by the ``OS`` module) is taken. + ## (as provided by the ``OS`` module) is taken. If ``shortNoVal`` is + ## provided command users do not need to delimit short option keys and + ## values with a ':' or '='. If ``longNoVal`` is provided command users do + ## not need to delimit long option keys and values with a ':' or '=' + ## (though they still need at least a space). In both cases, ':' or '=' + ## may still be used if desired. They just become optional. result.pos = 0 + result.idx = 0 result.inShortState = false + result.shortNoVal = shortNoVal + result.longNoVal = longNoVal if cmdline != "": result.cmd = cmdline + result.cmds = parseCmdLine(cmdline) else: result.cmd = "" + result.cmds = newSeq[string](paramCount()) + for i in countup(1, paramCount()): + result.cmds[i-1] = paramStr(i).string + result.cmd.add quote(result.cmds[i-1]) + result.cmd.add ' ' + + result.kind = cmdEnd + result.key = TaintedString"" + result.val = TaintedString"" + + proc initOptParser*(cmdline: seq[TaintedString], shortNoVal: set[char]={}, + longNoVal: seq[string] = @[]): OptParser = + ## inits the option parser. If ``cmdline.len == 0``, the real command line + ## (as provided by the ``OS`` module) is taken. ``shortNoVal`` and + ## ``longNoVal`` behavior is the same as for ``initOptParser(string,...)``. + result.pos = 0 + result.idx = 0 + result.inShortState = false + result.shortNoVal = shortNoVal + result.longNoVal = longNoVal + result.cmd = "" + if cmdline.len != 0: + result.cmds = newSeq[string](cmdline.len) + for i in 0..<cmdline.len: + result.cmds[i] = cmdline[i].string + result.cmd.add quote(cmdline[i].string) + result.cmd.add ' ' + else: + result.cmds = newSeq[string](paramCount()) for i in countup(1, paramCount()): - result.cmd.add quote(paramStr(i).string) + result.cmds[i-1] = paramStr(i).string + result.cmd.add quote(result.cmds[i-1]) result.cmd.add ' ' result.kind = cmdEnd result.key = TaintedString"" result.val = TaintedString"" -proc handleShortOption(p: var OptParser) = +proc handleShortOption(p: var OptParser; cmd: string) = var i = p.pos p.kind = cmdShortOption - add(p.key.string, p.cmd[i]) + add(p.key.string, cmd[i]) inc(i) p.inShortState = true - while p.cmd[i] in {'\x09', ' '}: + while i < cmd.len and cmd[i] in {'\t', ' '}: inc(i) p.inShortState = false - if p.cmd[i] in {':', '='}: - inc(i) + if i < cmd.len and cmd[i] in {':', '='} or + card(p.shortNoVal) > 0 and p.key.string[0] notin p.shortNoVal: + if i < cmd.len and cmd[i] in {':', '='}: + inc(i) p.inShortState = false - while p.cmd[i] in {'\x09', ' '}: inc(i) - i = parseWord(p.cmd, i, p.val.string) - if p.cmd[i] == '\0': p.inShortState = false - p.pos = i + while i < cmd.len and cmd[i] in {'\t', ' '}: inc(i) + p.val = TaintedString substr(cmd, i) + p.pos = 0 + inc p.idx + else: + p.pos = i + if i >= cmd.len: + p.inShortState = false + p.pos = 0 + inc p.idx proc next*(p: var OptParser) {.rtl, extern: "npo$1".} = ## parses the first or next option; ``p.kind`` describes what token has been ## parsed. ``p.key`` and ``p.val`` are set accordingly. + if p.idx >= p.cmds.len: + p.kind = cmdEnd + return + var i = p.pos - while p.cmd[i] in {'\x09', ' '}: inc(i) + while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i) p.pos = i setLen(p.key.string, 0) setLen(p.val.string, 0) if p.inShortState: - handleShortOption(p) - return - case p.cmd[i] - of '\0': - p.kind = cmdEnd - of '-': + p.inShortState = false + if i >= p.cmds[p.idx].len: + inc(p.idx) + p.pos = 0 + if p.idx >= p.cmds.len: + p.kind = cmdEnd + return + else: + handleShortOption(p, p.cmds[p.idx]) + return + + if i < p.cmds[p.idx].len and p.cmds[p.idx][i] == '-': inc(i) - if p.cmd[i] == '-': - p.kind = cmdLongoption + if i < p.cmds[p.idx].len and p.cmds[p.idx][i] == '-': + p.kind = cmdLongOption inc(i) - i = parseWord(p.cmd, i, p.key.string, {'\0', ' ', '\x09', ':', '='}) - while p.cmd[i] in {'\x09', ' '}: inc(i) - if p.cmd[i] in {':', '='}: + i = parseWord(p.cmds[p.idx], i, p.key.string, {' ', '\t', ':', '='}) + while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i) + if i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {':', '='}: inc(i) - while p.cmd[i] in {'\x09', ' '}: inc(i) - p.pos = parseWord(p.cmd, i, p.val.string) + while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i) + # if we're at the end, use the next command line option: + if i >= p.cmds[p.idx].len and p.idx < p.cmds.len: + inc p.idx + i = 0 + p.val = TaintedString p.cmds[p.idx].substr(i) + elif len(p.longNoVal) > 0 and p.key.string notin p.longNoVal and p.idx+1 < p.cmds.len: + p.val = TaintedString p.cmds[p.idx+1] + inc p.idx else: - p.pos = i + p.val = TaintedString"" + inc p.idx + p.pos = 0 else: p.pos = i - handleShortOption(p) + handleShortOption(p, p.cmds[p.idx]) else: p.kind = cmdArgument - p.pos = parseWord(p.cmd, i, p.key.string) + p.key = TaintedString p.cmds[p.idx] + inc p.idx + p.pos = 0 -proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo$1".} = - ## retrieves the rest of the command line that has not been parsed yet. - result = strip(substr(p.cmd, p.pos, len(p.cmd) - 1)).TaintedString +when declared(os.paramCount): + proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo$1".} = + ## retrieves the rest of the command line that has not been parsed yet. + var res = "" + for i in p.idx..<p.cmds.len: + if i > p.idx: res.add ' ' + res.add quote(p.cmds[i]) + result = res.TaintedString iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedString] = ## This is an convenience iterator for iterating over the given OptParser object. ## Example: ## ## .. code-block:: nim - ## var p = initOptParser("--left --debug:3 -l=4 -r:2") + ## var p = initOptParser("--left --debug:3 -l -r:2") ## for kind, key, val in p.getopt(): ## case kind ## of cmdArgument: @@ -168,23 +258,37 @@ iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedSt ## # no filename has been given, so we show the help: ## writeHelp() p.pos = 0 + p.idx = 0 while true: next(p) if p.kind == cmdEnd: break yield (p.kind, p.key, p.val) when declared(initOptParser): - iterator getopt*(): tuple[kind: CmdLineKind, key, val: TaintedString] = - ## This is an convenience iterator for iterating over the command line arguments. - ## This create a new OptParser object. - ## See above for a more detailed example + iterator getopt*(cmdline: seq[TaintedString] = commandLineParams(), + shortNoVal: set[char]={}, longNoVal: seq[string] = @[]): + tuple[kind: CmdLineKind, key, val: TaintedString] = + ## This is an convenience iterator for iterating over command line arguments. + ## This creates a new OptParser. See the above ``getopt(var OptParser)`` + ## example for using default empty ``NoVal`` parameters. This example is + ## for the same option keys as that example but here option key-value + ## separators become optional for command users: ## ## .. code-block:: nim - ## for kind, key, val in getopt(): - ## # this will iterate over all arguments passed to the cmdline. - ## continue + ## for kind, key, val in getopt(shortNoVal = { 'l' }, + ## longNoVal = @[ "left" ]): + ## case kind + ## of cmdArgument: + ## filename = key + ## of cmdLongOption, cmdShortOption: + ## case key + ## of "help", "h": writeHelp() + ## of "version", "v": writeVersion() + ## of cmdEnd: assert(false) # cannot happen + ## if filename == "": + ## writeHelp() ## - var p = initOptParser() + var p = initOptParser(cmdline, shortNoVal=shortNoVal, longNoVal=longNoVal) while true: next(p) if p.kind == cmdEnd: break diff --git a/lib/pure/parseopt2.nim b/lib/pure/parseopt2.nim index a2ff9bf0c..51a70b6d1 100644 --- a/lib/pure/parseopt2.nim +++ b/lib/pure/parseopt2.nim @@ -17,6 +17,7 @@ ## 2. long option - ``--foo:bar``, ``--foo=bar`` or ``--foo`` ## 3. argument - everything else +{.deprecated: "Use the 'parseopt' module instead".} {.push debugger: off.} include "system/inclrtl" @@ -40,15 +41,13 @@ type ## or the argument, ``value`` is not "" if ## the option was given a value -{.deprecated: [TCmdLineKind: CmdLineKind, TOptParser: OptParser].} - proc initOptParser*(cmdline: seq[string]): OptParser {.rtl.} = ## Initalizes option parses with cmdline. cmdline should not contain ## argument 0 - program name. - ## If cmdline == nil default to current command line arguments. + ## If cmdline.len == 0 default to current command line arguments. result.remainingShortOptions = "" when not defined(createNimRtl): - if cmdline == nil: + if cmdline.len == 0: result.cmd = commandLineParams() return else: @@ -61,7 +60,7 @@ proc initOptParser*(cmdline: string): OptParser {.rtl, deprecated.} = ## and calls initOptParser(openarray[string]) ## Do not use. if cmdline == "": # backward compatibility - return initOptParser(seq[string](nil)) + return initOptParser(@[]) else: return initOptParser(cmdline.split) @@ -121,8 +120,6 @@ proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo2$1", deprecat type GetoptResult* = tuple[kind: CmdLineKind, key, val: TaintedString] -{.deprecated: [TGetoptResult: GetoptResult].} - iterator getopt*(p: var OptParser): GetoptResult = ## This is an convenience iterator for iterating over the given OptParser object. ## Example: diff --git a/lib/pure/parsesql.nim b/lib/pure/parsesql.nim index ae192ab9a..9aef43c1b 100644 --- a/lib/pure/parsesql.nim +++ b/lib/pure/parsesql.nim @@ -11,7 +11,7 @@ ## parser. It parses PostgreSQL syntax and the SQL ANSI standard. import - hashes, strutils, lexbase, streams + hashes, strutils, lexbase # ------------------- scanner ------------------------------------------------- @@ -45,8 +45,6 @@ type SqlLexer* = object of BaseLexer ## the parser object. filename: string -{.deprecated: [TToken: Token, TSqlLexer: SqlLexer].} - const tokKindToStr: array[TokKind, string] = [ "invalid", "[EOF]", "identifier", "quoted identifier", "string constant", @@ -62,10 +60,6 @@ const "count", ] -proc open(L: var SqlLexer, input: Stream, filename: string) = - lexbase.open(L, input) - L.filename = filename - proc close(L: var SqlLexer) = lexbase.close(L) @@ -496,6 +490,7 @@ type SqlNodeKind* = enum ## kind of SQL abstract syntax tree nkNone, nkIdent, + nkQuotedIdent, nkStringLit, nkBitStringLit, nkHexStringLit, @@ -551,13 +546,18 @@ type nkCreateIndexIfNotExists, nkEnumDef +const + LiteralNodes = { + nkIdent, nkQuotedIdent, nkStringLit, nkBitStringLit, nkHexStringLit, + nkIntegerLit, nkNumericLit + } + type SqlParseError* = object of ValueError ## Invalid SQL encountered SqlNode* = ref SqlNodeObj ## an SQL abstract syntax tree node SqlNodeObj* = object ## an SQL abstract syntax tree node case kind*: SqlNodeKind ## kind of syntax tree - of nkIdent, nkStringLit, nkBitStringLit, nkHexStringLit, - nkIntegerLit, nkNumericLit: + of LiteralNodes: strVal*: string ## AST leaf: the identifier, numeric literal ## string literal, etc. else: @@ -566,21 +566,26 @@ type SqlParser* = object of SqlLexer ## SQL parser object tok: Token + {.deprecated: [EInvalidSql: SqlParseError, PSqlNode: SqlNode, TSqlNode: SqlNodeObj, TSqlParser: SqlParser, TSqlNodeKind: SqlNodeKind].} -proc newNode(k: SqlNodeKind): SqlNode = +proc newNode*(k: SqlNodeKind): SqlNode = new(result) result.kind = k -proc newNode(k: SqlNodeKind, s: string): SqlNode = +proc newNode*(k: SqlNodeKind, s: string): SqlNode = new(result) result.kind = k result.strVal = s +proc newNode*(k: SqlNodeKind, sons: seq[SqlNode]): SqlNode = + new(result) + result.kind = k + result.sons = sons + proc len*(n: SqlNode): int = - if n.kind in {nkIdent, nkStringLit, nkBitStringLit, nkHexStringLit, - nkIntegerLit, nkNumericLit}: + if n.kind in LiteralNodes: result = 0 else: result = n.sons.len @@ -588,7 +593,6 @@ proc len*(n: SqlNode): int = proc `[]`*(n: SqlNode; i: int): SqlNode = n.sons[i] proc add*(father, n: SqlNode) = - if isNil(father.sons): father.sons = @[] add(father.sons, n) proc getTok(p: var SqlParser) = @@ -630,7 +634,7 @@ proc eat(p: var SqlParser, keyw: string) = if isKeyw(p, keyw): getTok(p) else: - sqlError(p, keyw.toUpper() & " expected") + sqlError(p, keyw.toUpperAscii() & " expected") proc opt(p: var SqlParser, kind: TokKind) = if p.tok.kind == kind: getTok(p) @@ -689,7 +693,10 @@ proc parseSelect(p: var SqlParser): SqlNode proc identOrLiteral(p: var SqlParser): SqlNode = case p.tok.kind - of tkIdentifier, tkQuotedIdentifier: + of tkQuotedIdentifier: + result = newNode(nkQuotedIdent, p.tok.literal) + getTok(p) + of tkIdentifier: result = newNode(nkIdent, p.tok.literal) getTok(p) of tkStringConstant, tkEscapeConstant, tkDollarQuotedConstant: @@ -713,11 +720,15 @@ proc identOrLiteral(p: var SqlParser): SqlNode = result.add(parseExpr(p)) eat(p, tkParRi) else: - sqlError(p, "expression expected") - getTok(p) # we must consume a token here to prevend endless loops! + if p.tok.literal == "*": + result = newNode(nkIdent, p.tok.literal) + getTok(p) + else: + sqlError(p, "expression expected") + getTok(p) # we must consume a token here to prevend endless loops! proc primary(p: var SqlParser): SqlNode = - if p.tok.kind == tkOperator or isKeyw(p, "not"): + if (p.tok.kind == tkOperator and (p.tok.literal == "+" or p.tok.literal == "-")) or isKeyw(p, "not"): result = newNode(nkPrefix) result.add(newNode(nkIdent, p.tok.literal)) getTok(p) @@ -762,7 +773,7 @@ proc lowestExprAux(p: var SqlParser, v: var SqlNode, limit: int): int = result = opPred while opPred > limit: node = newNode(nkInfix) - opNode = newNode(nkIdent, p.tok.literal.toLower()) + opNode = newNode(nkIdent, p.tok.literal.toLowerAscii()) getTok(p) result = lowestExprAux(p, v2, opPred) node.add(opNode) @@ -1078,11 +1089,23 @@ proc parseSelect(p: var SqlParser): SqlNode = if p.tok.kind != tkComma: break getTok(p) result.add(g) - if isKeyw(p, "limit"): + if isKeyw(p, "order"): getTok(p) - var l = newNode(nkLimit) - l.add(parseExpr(p)) - result.add(l) + eat(p, "by") + var n = newNode(nkOrder) + while true: + var e = parseExpr(p) + if isKeyw(p, "asc"): + getTok(p) # is default + elif isKeyw(p, "desc"): + getTok(p) + var x = newNode(nkDesc) + x.add(e) + e = x + n.add(e) + if p.tok.kind != tkComma: break + getTok(p) + result.add(n) if isKeyw(p, "having"): var h = newNode(nkHaving) while true: @@ -1099,22 +1122,6 @@ proc parseSelect(p: var SqlParser): SqlNode = elif isKeyw(p, "except"): result.add(newNode(nkExcept)) getTok(p) - if isKeyw(p, "order"): - getTok(p) - eat(p, "by") - var n = newNode(nkOrder) - while true: - var e = parseExpr(p) - if isKeyw(p, "asc"): getTok(p) # is default - elif isKeyw(p, "desc"): - getTok(p) - var x = newNode(nkDesc) - x.add(e) - e = x - n.add(e) - if p.tok.kind != tkComma: break - getTok(p) - result.add(n) if isKeyw(p, "join") or isKeyw(p, "inner") or isKeyw(p, "outer") or isKeyw(p, "cross"): var join = newNode(nkJoin) result.add(join) @@ -1122,12 +1129,17 @@ proc parseSelect(p: var SqlParser): SqlNode = join.add(newNode(nkIdent, "")) getTok(p) else: - join.add(newNode(nkIdent, p.tok.literal.toLower())) + join.add(newNode(nkIdent, p.tok.literal.toLowerAscii())) getTok(p) eat(p, "join") join.add(parseFromItem(p)) eat(p, "on") join.add(parseExpr(p)) + if isKeyw(p, "limit"): + getTok(p) + var l = newNode(nkLimit) + l.add(parseExpr(p)) + result.add(l) proc parseStmt(p: var SqlParser; parent: SqlNode) = if isKeyw(p, "create"): @@ -1161,14 +1173,6 @@ proc parseStmt(p: var SqlParser; parent: SqlNode) = else: sqlError(p, "SELECT, CREATE, UPDATE or DELETE expected") -proc open(p: var SqlParser, input: Stream, filename: string) = - ## opens the parser `p` and assigns the input stream `input` to it. - ## `filename` is only used for error messages. - open(SqlLexer(p), input, filename) - p.tok.kind = tkInvalid - p.tok.literal = "" - getTok(p) - proc parse(p: var SqlParser): SqlNode = ## parses the content of `p`'s input stream and returns the SQL AST. ## Syntax errors raise an `SqlParseError` exception. @@ -1183,24 +1187,6 @@ proc close(p: var SqlParser) = ## closes the parser `p`. The associated input stream is closed too. close(SqlLexer(p)) -proc parseSQL*(input: Stream, filename: string): SqlNode = - ## parses the SQL from `input` into an AST and returns the AST. - ## `filename` is only used for error messages. - ## Syntax errors raise an `SqlParseError` exception. - var p: SqlParser - open(p, input, filename) - try: - result = parse(p) - finally: - close(p) - -proc parseSQL*(input: string, filename=""): SqlNode = - ## parses the SQL from `input` into an AST and returns the AST. - ## `filename` is only used for error messages. - ## Syntax errors raise an `SqlParseError` exception. - parseSQL(newStringStream(input), "") - - type SqlWriter = object indent: int @@ -1218,12 +1204,12 @@ proc add(s: var SqlWriter, thing: string) = proc addKeyw(s: var SqlWriter, thing: string) = var keyw = thing if s.upperCase: - keyw = keyw.toUpper() + keyw = keyw.toUpperAscii() s.add(keyw) proc addIden(s: var SqlWriter, thing: string) = var iden = thing - if iden.toLower() in reservedKeywords: + if iden.toLowerAscii() in reservedKeywords: iden = '"' & iden & '"' s.add(iden) @@ -1251,15 +1237,20 @@ proc addMulti(s: var SqlWriter, n: SqlNode, sep = ',', prefix, suffix: char) = ra(n.sons[i], s) s.add(suffix) +proc quoted(s: string): string = + "\"" & replace(s, "\"", "\"\"") & "\"" + proc ra(n: SqlNode, s: var SqlWriter) = if n == nil: return case n.kind of nkNone: discard of nkIdent: - if allCharsInSet(n.strVal, {'\33'..'\127'}) and n.strVal.toLower() notin reservedKeywords: + if allCharsInSet(n.strVal, {'\33'..'\127'}): s.add(n.strVal) else: - s.add("\"" & replace(n.strVal, "\"", "\"\"") & "\"") + s.add(quoted(n.strVal)) + of nkQuotedIdent: + s.add(quoted(n.strVal)) of nkStringLit: s.add(escape(n.strVal, "'", "'")) of nkBitStringLit: @@ -1361,18 +1352,19 @@ proc ra(n: SqlNode, s: var SqlWriter) = s.addKeyw("select") if n.kind == nkSelectDistinct: s.addKeyw("distinct") - s.addMulti(n.sons[0]) - for i in 1 .. n.len-1: + for i in 0 ..< n.len: ra(n.sons[i], s) of nkSelectColumns: - assert(false) + for i, column in n.sons: + if i > 0: s.add(',') + ra(column, s) of nkSelectPair: ra(n.sons[0], s) if n.sons.len == 2: s.addKeyw("as") ra(n.sons[1], s) of nkFromItemPair: - if n.sons[0].kind == nkIdent: + if n.sons[0].kind in {nkIdent, nkQuotedIdent}: ra(n.sons[0], s) else: assert n.sons[0].kind == nkSelect @@ -1472,3 +1464,35 @@ proc renderSQL*(n: SqlNode, upperCase=false): string = proc `$`*(n: SqlNode): string = ## an alias for `renderSQL`. renderSQL(n) + +when not defined(js): + import streams + + proc open(L: var SqlLexer, input: Stream, filename: string) = + lexbase.open(L, input) + L.filename = filename + + proc open(p: var SqlParser, input: Stream, filename: string) = + ## opens the parser `p` and assigns the input stream `input` to it. + ## `filename` is only used for error messages. + open(SqlLexer(p), input, filename) + p.tok.kind = tkInvalid + p.tok.literal = "" + getTok(p) + + proc parseSQL*(input: Stream, filename: string): SqlNode = + ## parses the SQL from `input` into an AST and returns the AST. + ## `filename` is only used for error messages. + ## Syntax errors raise an `SqlParseError` exception. + var p: SqlParser + open(p, input, filename) + try: + result = parse(p) + finally: + close(p) + + proc parseSQL*(input: string, filename=""): SqlNode = + ## parses the SQL from `input` into an AST and returns the AST. + ## `filename` is only used for error messages. + ## Syntax errors raise an `SqlParseError` exception. + parseSQL(newStringStream(input), "") diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index 57387e62e..e633d8cf7 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -11,7 +11,7 @@ ## ## To unpack raw bytes look at the `streams <streams.html>`_ module. -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated {.push debugger:off .} # the user does not want to trace a part # of the standard library! @@ -47,13 +47,15 @@ proc parseHex*(s: string, number: var int, start = 0; maxLen = 0): int {. ## discard parseHex("0x38", value) ## assert value == -200 ## - ## If 'maxLen==0' the length of the hexadecimal number has no - ## upper bound. Not more than ```maxLen`` characters are parsed. + ## If ``maxLen == 0`` the length of the hexadecimal number has no upper bound. + ## Else no more than ``start + maxLen`` characters are parsed, up to the + ## length of the string. var i = start var foundDigit = false - if s[i] == '0' and (s[i+1] == 'x' or s[i+1] == 'X'): inc(i, 2) - elif s[i] == '#': inc(i) - let last = if maxLen == 0: s.len else: i+maxLen + # get last index based on minimum `start + maxLen` or `s.len` + let last = min(s.len, if maxLen == 0: s.len else: i+maxLen) + if i+1 < last and s[i] == '0' and (s[i+1] in {'x', 'X'}): inc(i, 2) + elif i < last and s[i] == '#': inc(i) while i < last: case s[i] of '_': discard @@ -70,14 +72,20 @@ proc parseHex*(s: string, number: var int, start = 0; maxLen = 0): int {. inc(i) if foundDigit: result = i-start -proc parseOct*(s: string, number: var int, start = 0): int {. +proc parseOct*(s: string, number: var int, start = 0, maxLen = 0): int {. rtl, extern: "npuParseOct", noSideEffect.} = - ## parses an octal number and stores its value in ``number``. Returns + ## Parses an octal number and stores its value in ``number``. Returns ## the number of the parsed characters or 0 in case of an error. + ## + ## If ``maxLen == 0`` the length of the octal number has no upper bound. + ## Else no more than ``start + maxLen`` characters are parsed, up to the + ## length of the string. var i = start var foundDigit = false - if s[i] == '0' and (s[i+1] == 'o' or s[i+1] == 'O'): inc(i, 2) - while true: + # get last index based on minimum `start + maxLen` or `s.len` + let last = min(s.len, if maxLen == 0: s.len else: i+maxLen) + if i+1 < last and s[i] == '0' and (s[i+1] in {'o', 'O'}): inc(i, 2) + while i < last: case s[i] of '_': discard of '0'..'7': @@ -87,14 +95,20 @@ proc parseOct*(s: string, number: var int, start = 0): int {. inc(i) if foundDigit: result = i-start -proc parseBin*(s: string, number: var int, start = 0): int {. +proc parseBin*(s: string, number: var int, start = 0, maxLen = 0): int {. rtl, extern: "npuParseBin", noSideEffect.} = - ## parses an binary number and stores its value in ``number``. Returns + ## Parses an binary number and stores its value in ``number``. Returns ## the number of the parsed characters or 0 in case of an error. + ## + ## If ``maxLen == 0`` the length of the binary number has no upper bound. + ## Else no more than ``start + maxLen`` characters are parsed, up to the + ## length of the string. var i = start var foundDigit = false - if s[i] == '0' and (s[i+1] == 'b' or s[i+1] == 'B'): inc(i, 2) - while true: + # get last index based on minimum `start + maxLen` or `s.len` + let last = min(s.len, if maxLen == 0: s.len else: i+maxLen) + if i+1 < last and s[i] == '0' and (s[i+1] in {'b', 'B'}): inc(i, 2) + while i < last: case s[i] of '_': discard of '0'..'1': @@ -108,9 +122,9 @@ proc parseIdent*(s: string, ident: var string, start = 0): int = ## parses an identifier and stores it in ``ident``. Returns ## the number of the parsed characters or 0 in case of an error. var i = start - if s[i] in IdentStartChars: + if i < s.len and s[i] in IdentStartChars: inc(i) - while s[i] in IdentChars: inc(i) + while i < s.len and s[i] in IdentChars: inc(i) ident = substr(s, start, i-1) result = i-start @@ -119,11 +133,9 @@ proc parseIdent*(s: string, start = 0): string = ## Returns the parsed identifier or an empty string in case of an error. result = "" var i = start - - if s[i] in IdentStartChars: + if i < s.len and s[i] in IdentStartChars: inc(i) - while s[i] in IdentChars: inc(i) - + while i < s.len and s[i] in IdentChars: inc(i) result = substr(s, start, i-1) proc parseToken*(s: string, token: var string, validChars: set[char], @@ -134,24 +146,26 @@ proc parseToken*(s: string, token: var string, validChars: set[char], ## ## **Deprecated since version 0.8.12**: Use ``parseWhile`` instead. var i = start - while s[i] in validChars: inc(i) + while i < s.len and s[i] in validChars: inc(i) result = i-start token = substr(s, start, i-1) proc skipWhitespace*(s: string, start = 0): int {.inline.} = ## skips the whitespace starting at ``s[start]``. Returns the number of ## skipped characters. - while s[start+result] in Whitespace: inc(result) + while start+result < s.len and s[start+result] in Whitespace: inc(result) proc skip*(s, token: string, start = 0): int {.inline.} = ## skips the `token` starting at ``s[start]``. Returns the length of `token` ## or 0 if there was no `token` at ``s[start]``. - while result < token.len and s[result+start] == token[result]: inc(result) + while start+result < s.len and result < token.len and + s[result+start] == token[result]: + inc(result) if result != token.len: result = 0 proc skipIgnoreCase*(s, token: string, start = 0): int = ## same as `skip` but case is ignored for token matching. - while result < token.len and + while start+result < s.len and result < token.len and toLower(s[result+start]) == toLower(token[result]): inc(result) if result != token.len: result = 0 @@ -159,18 +173,18 @@ proc skipUntil*(s: string, until: set[char], start = 0): int {.inline.} = ## Skips all characters until one char from the set `until` is found ## or the end is reached. ## Returns number of characters skipped. - while s[result+start] notin until and s[result+start] != '\0': inc(result) + while start+result < s.len and s[result+start] notin until: inc(result) proc skipUntil*(s: string, until: char, start = 0): int {.inline.} = ## Skips all characters until the char `until` is found ## or the end is reached. ## Returns number of characters skipped. - while s[result+start] != until and s[result+start] != '\0': inc(result) + while start+result < s.len and s[result+start] != until: inc(result) proc skipWhile*(s: string, toSkip: set[char], start = 0): int {.inline.} = ## Skips all characters while one char from the set `token` is found. ## Returns number of characters skipped. - while s[result+start] in toSkip and s[result+start] != '\0': inc(result) + while start+result < s.len and s[result+start] in toSkip: inc(result) proc parseUntil*(s: string, token: var string, until: set[char], start = 0): int {.inline.} = @@ -197,6 +211,9 @@ proc parseUntil*(s: string, token: var string, until: string, ## parses a token and stores it in ``token``. Returns ## the number of the parsed characters or 0 in case of an error. A token ## consists of any character that comes before the `until` token. + if until.len == 0: + token.setLen(0) + return 0 var i = start while i < s.len: if s[i] == until[0]: @@ -214,7 +231,7 @@ proc parseWhile*(s: string, token: var string, validChars: set[char], ## the number of the parsed characters or 0 in case of an error. A token ## consists of the characters in `validChars`. var i = start - while s[i] in validChars: inc(i) + while i < s.len and s[i] in validChars: inc(i) result = i-start token = substr(s, start, i-1) @@ -231,16 +248,17 @@ proc rawParseInt(s: string, b: var BiggestInt, start = 0): int = var sign: BiggestInt = -1 i = start - if s[i] == '+': inc(i) - elif s[i] == '-': - inc(i) - sign = 1 - if s[i] in {'0'..'9'}: + if i < s.len: + if s[i] == '+': inc(i) + elif s[i] == '-': + inc(i) + sign = 1 + if i < s.len and s[i] in {'0'..'9'}: b = 0 - while s[i] in {'0'..'9'}: + while i < s.len and s[i] in {'0'..'9'}: b = b * 10 - (ord(s[i]) - ord('0')) inc(i) - while s[i] == '_': inc(i) # underscores are allowed and ignored + while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored b = b * sign result = i - start {.pop.} # overflowChecks @@ -281,17 +299,17 @@ proc parseSaturatedNatural*(s: string, b: var int, start = 0): int = ## discard parseSaturatedNatural("848", res) ## doAssert res == 848 var i = start - if s[i] == '+': inc(i) - if s[i] in {'0'..'9'}: + if i < s.len and s[i] == '+': inc(i) + if i < s.len and s[i] in {'0'..'9'}: b = 0 - while s[i] in {'0'..'9'}: + while i < s.len and s[i] in {'0'..'9'}: let c = ord(s[i]) - ord('0') if b <= (high(int) - c) div 10: b = b * 10 + c else: b = high(int) inc(i) - while s[i] == '_': inc(i) # underscores are allowed and ignored + while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored result = i - start # overflowChecks doesn't work with BiggestUInt @@ -300,16 +318,16 @@ proc rawParseUInt(s: string, b: var BiggestUInt, start = 0): int = res = 0.BiggestUInt prev = 0.BiggestUInt i = start - if s[i] == '+': inc(i) # Allow - if s[i] in {'0'..'9'}: + if i < s.len and s[i] == '+': inc(i) # Allow + if i < s.len and s[i] in {'0'..'9'}: b = 0 - while s[i] in {'0'..'9'}: + while i < s.len and s[i] in {'0'..'9'}: prev = res res = res * 10 + (ord(s[i]) - ord('0')).BiggestUInt if prev > res: return 0 # overflowChecks emulation inc(i) - while s[i] == '_': inc(i) # underscores are allowed and ignored + while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored b = res result = i - start @@ -364,8 +382,6 @@ type ikVar, ## ``var`` part of the interpolated string ikExpr ## ``expr`` part of the interpolated string -{.deprecated: [TInterpolatedKind: InterpolatedKind].} - iterator interpolatedFragments*(s: string): tuple[kind: InterpolatedKind, value: string] = ## Tokenizes the string `s` into substrings for interpolation purposes. @@ -389,31 +405,31 @@ iterator interpolatedFragments*(s: string): tuple[kind: InterpolatedKind, var kind: InterpolatedKind while true: var j = i - if s[j] == '$': - if s[j+1] == '{': + if j < s.len and s[j] == '$': + if j+1 < s.len and s[j+1] == '{': inc j, 2 var nesting = 0 - while true: - case s[j] - of '{': inc nesting - of '}': - if nesting == 0: - inc j - break - dec nesting - of '\0': - raise newException(ValueError, - "Expected closing '}': " & substr(s, i, s.high)) - else: discard - inc j + block curlies: + while j < s.len: + case s[j] + of '{': inc nesting + of '}': + if nesting == 0: + inc j + break curlies + dec nesting + else: discard + inc j + raise newException(ValueError, + "Expected closing '}': " & substr(s, i, s.high)) inc i, 2 # skip ${ kind = ikExpr - elif s[j+1] in IdentStartChars: + elif j+1 < s.len and s[j+1] in IdentStartChars: inc j, 2 - while s[j] in IdentChars: inc(j) + while j < s.len and s[j] in IdentChars: inc(j) inc i # skip $ kind = ikVar - elif s[j+1] == '$': + elif j+1 < s.len and s[j+1] == '$': inc j, 2 inc i # skip $ kind = ikDollar diff --git a/lib/pure/parsexml.nim b/lib/pure/parsexml.nim index 978c9c516..d8d5a7a2d 100644 --- a/lib/pure/parsexml.nim +++ b/lib/pure/parsexml.nim @@ -26,27 +26,125 @@ ## creates. ## ## -## Example 1: Retrieve HTML title -## ============================== -## -## The file ``examples/htmltitle.nim`` demonstrates how to use the -## XML parser to accomplish a simple task: To determine the title of an HTML -## document. -## -## .. code-block:: nim -## :file: ../../examples/htmltitle.nim -## -## -## Example 2: Retrieve all HTML links -## ================================== -## -## The file ``examples/htmlrefs.nim`` demonstrates how to use the -## XML parser to accomplish another simple task: To determine all the links -## an HTML document contains. -## -## .. code-block:: nim -## :file: ../../examples/htmlrefs.nim -## + +##[ + +Example 1: Retrieve HTML title +============================== + +The file ``examples/htmltitle.nim`` demonstrates how to use the +XML parser to accomplish a simple task: To determine the title of an HTML +document. + +.. code-block:: nim + + # Example program to show the parsexml module + # This program reads an HTML file and writes its title to stdout. + # Errors and whitespace are ignored. + + import os, streams, parsexml, strutils + + if paramCount() < 1: + quit("Usage: htmltitle filename[.html]") + + var filename = addFileExt(paramStr(1), "html") + var s = newFileStream(filename, fmRead) + if s == nil: quit("cannot open the file " & filename) + var x: XmlParser + open(x, s, filename) + while true: + x.next() + case x.kind + of xmlElementStart: + if cmpIgnoreCase(x.elementName, "title") == 0: + var title = "" + x.next() # skip "<title>" + while x.kind == xmlCharData: + title.add(x.charData) + x.next() + if x.kind == xmlElementEnd and cmpIgnoreCase(x.elementName, "title") == 0: + echo("Title: " & title) + quit(0) # Success! + else: + echo(x.errorMsgExpected("/title")) + + of xmlEof: break # end of file reached + else: discard # ignore other events + + x.close() + quit("Could not determine title!") + +]## + +##[ + +Example 2: Retrieve all HTML links +================================== + +The file ``examples/htmlrefs.nim`` demonstrates how to use the +XML parser to accomplish another simple task: To determine all the links +an HTML document contains. + +.. code-block:: nim + + # Example program to show the new parsexml module + # This program reads an HTML file and writes all its used links to stdout. + # Errors and whitespace are ignored. + + import os, streams, parsexml, strutils + + proc `=?=` (a, b: string): bool = + # little trick: define our own comparator that ignores case + return cmpIgnoreCase(a, b) == 0 + + if paramCount() < 1: + quit("Usage: htmlrefs filename[.html]") + + var links = 0 # count the number of links + var filename = addFileExt(paramStr(1), "html") + var s = newFileStream(filename, fmRead) + if s == nil: quit("cannot open the file " & filename) + var x: XmlParser + open(x, s, filename) + next(x) # get first event + block mainLoop: + while true: + case x.kind + of xmlElementOpen: + # the <a href = "xyz"> tag we are interested in always has an attribute, + # thus we search for ``xmlElementOpen`` and not for ``xmlElementStart`` + if x.elementName =?= "a": + x.next() + if x.kind == xmlAttribute: + if x.attrKey =?= "href": + var link = x.attrValue + inc(links) + # skip until we have an ``xmlElementClose`` event + while true: + x.next() + case x.kind + of xmlEof: break mainLoop + of xmlElementClose: break + else: discard + x.next() # skip ``xmlElementClose`` + # now we have the description for the ``a`` element + var desc = "" + while x.kind == xmlCharData: + desc.add(x.charData) + x.next() + echo(desc & ": " & link) + else: + x.next() + of xmlEof: break # end of file reached + of xmlError: + echo(errorMsg(x)) + x.next() + else: x.next() # skip other events + + echo($links & " link(s) found!") + x.close() + +]## import hashes, strutils, lexbase, streams, unicode @@ -95,12 +193,10 @@ type kind: XmlEventKind err: XmlErrorKind state: ParserState + cIsEmpty: bool filename: string options: set[XmlParseOption] -{.deprecated: [TXmlParser: XmlParser, TXmlParseOptions: XmlParseOption, - TXmlError: XmlErrorKind, TXmlEventKind: XmlEventKind].} - const errorMessages: array[XmlErrorKind, string] = [ "no error", @@ -128,7 +224,8 @@ proc open*(my: var XmlParser, input: Stream, filename: string, my.kind = xmlError my.a = "" my.b = "" - my.c = nil + my.c = "" + my.cIsEmpty = true my.options = options proc close*(my: var XmlParser) {.inline.} = @@ -485,6 +582,7 @@ proc parseTag(my: var XmlParser) = my.kind = xmlElementOpen my.state = stateAttr my.c = my.a # save for later + my.cIsEmpty = false else: my.kind = xmlElementStart let slash = my.buf[my.bufpos] == '/' @@ -493,7 +591,8 @@ proc parseTag(my: var XmlParser) = if slash and my.buf[my.bufpos] == '>': inc(my.bufpos) my.state = stateEmptyElementTag - my.c = nil + my.c = "" + my.cIsEmpty = true elif my.buf[my.bufpos] == '>': inc(my.bufpos) else: @@ -681,7 +780,7 @@ proc next*(my: var XmlParser) = of stateEmptyElementTag: my.state = stateNormal my.kind = xmlElementEnd - if not my.c.isNil: + if not my.cIsEmpty: my.a = my.c of stateError: my.kind = xmlError diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim index 5ae2d9182..3ee82917d 100644 --- a/lib/pure/pegs.nim +++ b/lib/pure/pegs.nim @@ -20,11 +20,11 @@ include "system/inclrtl" const useUnicode = true ## change this to deactivate proper UTF-8 support -import - strutils +import strutils, macros when useUnicode: import unicode + export unicode.`==` const InlineThreshold = 5 ## number of leaves; -1 to disable inlining @@ -32,7 +32,7 @@ const ## can be captured. More subpatterns cannot be captured! type - PegKind = enum + PegKind* = enum pkEmpty, pkAny, ## any character (.) pkAnyRune, ## any Unicode character (_) @@ -67,15 +67,15 @@ type pkRule, ## a <- b pkList, ## a, b pkStartAnchor ## ^ --> Internal DSL: startAnchor() - NonTerminalFlag = enum + NonTerminalFlag* = enum ntDeclared, ntUsed NonTerminalObj = object ## represents a non terminal symbol name: string ## the name of the symbol line: int ## line the symbol has been declared/used in col: int ## column the symbol has been declared/used in flags: set[NonTerminalFlag] ## the nonterminal's flags - rule: Node ## the rule that the symbol refers to - Node {.shallow.} = object + rule: Peg ## the rule that the symbol refers to + Peg* {.shallow.} = object ## type that represents a PEG case kind: PegKind of pkEmpty..pkWhitespace: nil of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle: term: string @@ -83,12 +83,61 @@ type of pkCharChoice, pkGreedyRepSet: charChoice: ref set[char] of pkNonTerminal: nt: NonTerminal of pkBackRef..pkBackRefIgnoreStyle: index: range[0..MaxSubpatterns] - else: sons: seq[Node] + else: sons: seq[Peg] NonTerminal* = ref NonTerminalObj - Peg* = Node ## type that represents a PEG +proc kind*(p: Peg): PegKind = p.kind + ## Returns the *PegKind* of a given *Peg* object. -{.deprecated: [TPeg: Peg, TNode: Node].} +proc term*(p: Peg): string = p.term + ## Returns the *string* representation of a given *Peg* variant object + ## where present. + +proc ch*(p: Peg): char = p.ch + ## Returns the *char* representation of a given *Peg* variant object + ## where present. + +proc charChoice*(p: Peg): ref set[char] = p.charChoice + ## Returns the *charChoice* field of a given *Peg* variant object + ## where present. + +proc nt*(p: Peg): NonTerminal = p.nt + ## Returns the *NonTerminal* object of a given *Peg* variant object + ## where present. + +proc index*(p: Peg): range[0..MaxSubpatterns] = p.index + ## Returns the back-reference index of a captured sub-pattern in the + ## *Captures* object for a given *Peg* variant object where present. + +iterator items*(p: Peg): Peg {.inline.} = + ## Yields the child nodes of a *Peg* variant object where present. + for s in p.sons: + yield s + +iterator pairs*(p: Peg): (int, Peg) {.inline.} = + ## Yields the indices and child nodes of a *Peg* variant object where present. + for i in 0 ..< p.sons.len: + yield (i, p.sons[i]) + +proc name*(nt: NonTerminal): string = nt.name + ## Gets the name of the symbol represented by the parent *Peg* object variant + ## of a given *NonTerminal*. + +proc line*(nt: NonTerminal): int = nt.line + ## Gets the line number of the definition of the parent *Peg* object variant + ## of a given *NonTerminal*. + +proc col*(nt: NonTerminal): int = nt.col + ## Gets the column number of the definition of the parent *Peg* object variant + ## of a given *NonTerminal*. + +proc flags*(nt: NonTerminal): set[NonTerminalFlag] = nt.flags + ## Gets the *NonTerminalFlag*-typed flags field of the parent *Peg* variant + ## object of a given *NonTerminal*. + +proc rule*(nt: NonTerminal): Peg = nt.rule + ## Gets the *Peg* object representing the rule definition of the parent *Peg* + ## object variant of a given *NonTerminal*. proc term*(t: string): Peg {.nosideEffect, rtl, extern: "npegs$1Str".} = ## constructs a PEG from a terminal string @@ -525,215 +574,497 @@ when not useUnicode: proc isTitle(a: char): bool {.inline.} = return false proc isWhiteSpace(a: char): bool {.inline.} = return a in {' ', '\9'..'\13'} -proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {. - nosideEffect, rtl, extern: "npegs$1".} = - ## low-level matching proc that implements the PEG interpreter. Use this - ## for maximum efficiency (every other PEG operation ends up calling this - ## proc). - ## Returns -1 if it does not match, else the length of the match - case p.kind - of pkEmpty: result = 0 # match of length 0 - of pkAny: - if s[start] != '\0': result = 1 - else: result = -1 - of pkAnyRune: - if s[start] != '\0': - result = runeLenAt(s, start) - else: - result = -1 - of pkLetter: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isAlpha(a): dec(result, start) +template matchOrParse(mopProc: untyped): typed = + # Used to make the main matcher proc *rawMatch* as well as event parser + # procs. For the former, *enter* and *leave* event handler code generators + # are provided which just return *discard*. + + proc mopProc(s: string, p: Peg, start: int, c: var Captures): int = + proc matchBackRef(s: string, p: Peg, start: int, c: var Captures): int = + # Parse handler code must run in an *of* clause of its own for each + # *PegKind*, so we encapsulate the identical clause body for + # *pkBackRef..pkBackRefIgnoreStyle* here. + if p.index >= c.ml: return -1 + var (a, b) = c.matches[p.index] + var n: Peg + n.kind = succ(pkTerminal, ord(p.kind)-ord(pkBackRef)) + n.term = s.substr(a, b) + mopProc(s, n, start, c) + + case p.kind + of pkEmpty: + enter(pkEmpty, s, p, start) + result = 0 # match of length 0 + leave(pkEmpty, s, p, start, result) + of pkAny: + enter(pkAny, s, p, start) + if start < s.len: result = 1 else: result = -1 - else: - result = -1 - of pkLower: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isLower(a): dec(result, start) + leave(pkAny, s, p, start, result) + of pkAnyRune: + enter(pkAnyRune, s, p, start) + if start < s.len: + result = runeLenAt(s, start) + else: + result = -1 + leave(pkAnyRune, s, p, start, result) + of pkLetter: + enter(pkLetter, s, p, start) + if start < s.len: + var a: Rune + result = start + fastRuneAt(s, result, a) + if isAlpha(a): dec(result, start) + else: result = -1 + else: + result = -1 + leave(pkLetter, s, p, start, result) + of pkLower: + enter(pkLower, s, p, start) + if start < s.len: + var a: Rune + result = start + fastRuneAt(s, result, a) + if isLower(a): dec(result, start) + else: result = -1 + else: + result = -1 + leave(pkLower, s, p, start, result) + of pkUpper: + enter(pkUpper, s, p, start) + if start < s.len: + var a: Rune + result = start + fastRuneAt(s, result, a) + if isUpper(a): dec(result, start) + else: result = -1 + else: + result = -1 + leave(pkUpper, s, p, start, result) + of pkTitle: + enter(pkTitle, s, p, start) + if start < s.len: + var a: Rune + result = start + fastRuneAt(s, result, a) + if isTitle(a): dec(result, start) + else: result = -1 + else: + result = -1 + leave(pkTitle, s, p, start, result) + of pkWhitespace: + enter(pkWhitespace, s, p, start) + if start < s.len: + var a: Rune + result = start + fastRuneAt(s, result, a) + if isWhiteSpace(a): dec(result, start) + else: result = -1 + else: + result = -1 + leave(pkWhitespace, s, p, start, result) + of pkGreedyAny: + enter(pkGreedyAny, s, p, start) + result = len(s) - start + leave(pkGreedyAny, s, p, start, result) + of pkNewLine: + enter(pkNewLine, s, p, start) + if start < s.len and s[start] == '\L': result = 1 + elif start < s.len and s[start] == '\C': + if start+1 < s.len and s[start+1] == '\L': result = 2 + else: result = 1 else: result = -1 - else: - result = -1 - of pkUpper: - if s[start] != '\0': - var a: Rune + leave(pkNewLine, s, p, start, result) + of pkTerminal: + enter(pkTerminal, s, p, start) + result = len(p.term) + for i in 0..result-1: + if start+i >= s.len or p.term[i] != s[start+i]: + result = -1 + break + leave(pkTerminal, s, p, start, result) + of pkTerminalIgnoreCase: + enter(pkTerminalIgnoreCase, s, p, start) + var + i = 0 + a, b: Rune result = start - fastRuneAt(s, result, a) - if isUpper(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkTitle: - if s[start] != '\0': - var a: Rune + while i < len(p.term): + if result >= s.len: + result = -1 + break + fastRuneAt(p.term, i, a) + fastRuneAt(s, result, b) + if toLower(a) != toLower(b): + result = -1 + break + dec(result, start) + leave(pkTerminalIgnoreCase, s, p, start, result) + of pkTerminalIgnoreStyle: + enter(pkTerminalIgnoreStyle, s, p, start) + var + i = 0 + a, b: Rune result = start - fastRuneAt(s, result, a) - if isTitle(a): dec(result, start) + while i < len(p.term): + while i < len(p.term): + fastRuneAt(p.term, i, a) + if a != Rune('_'): break + while result < s.len: + fastRuneAt(s, result, b) + if b != Rune('_'): break + if result >= s.len: + if i >= p.term.len: break + else: + result = -1 + break + elif toLower(a) != toLower(b): + result = -1 + break + dec(result, start) + leave(pkTerminalIgnoreStyle, s, p, start, result) + of pkChar: + enter(pkChar, s, p, start) + if start < s.len and p.ch == s[start]: result = 1 else: result = -1 - else: - result = -1 - of pkWhitespace: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isWhiteSpace(a): dec(result, start) + leave(pkChar, s, p, start, result) + of pkCharChoice: + enter(pkCharChoice, s, p, start) + if start < s.len and contains(p.charChoice[], s[start]): result = 1 else: result = -1 - else: + leave(pkCharChoice, s, p, start, result) + of pkNonTerminal: + enter(pkNonTerminal, s, p, start) + var oldMl = c.ml + when false: echo "enter: ", p.nt.name + result = mopProc(s, p.nt.rule, start, c) + when false: echo "leave: ", p.nt.name + if result < 0: c.ml = oldMl + leave(pkNonTerminal, s, p, start, result) + of pkSequence: + enter(pkSequence, s, p, start) + var oldMl = c.ml + result = 0 + for i in 0..high(p.sons): + var x = mopProc(s, p.sons[i], start+result, c) + if x < 0: + c.ml = oldMl + result = -1 + break + else: inc(result, x) + leave(pkSequence, s, p, start, result) + of pkOrderedChoice: + enter(pkOrderedChoice, s, p, start) + var oldMl = c.ml + for i in 0..high(p.sons): + result = mopProc(s, p.sons[i], start, c) + if result >= 0: break + c.ml = oldMl + leave(pkOrderedChoice, s, p, start, result) + of pkSearch: + enter(pkSearch, s, p, start) + var oldMl = c.ml + result = 0 + while start+result <= s.len: + var x = mopProc(s, p.sons[0], start+result, c) + if x >= 0: + inc(result, x) + leave(pkSearch, s, p, start, result) + return + inc(result) result = -1 - of pkGreedyAny: - result = len(s) - start - of pkNewLine: - if s[start] == '\L': result = 1 - elif s[start] == '\C': - if s[start+1] == '\L': result = 2 - else: result = 1 - else: result = -1 - of pkTerminal: - result = len(p.term) - for i in 0..result-1: - if p.term[i] != s[start+i]: - result = -1 - break - of pkTerminalIgnoreCase: - var - i = 0 - a, b: Rune - result = start - while i < len(p.term): - fastRuneAt(p.term, i, a) - fastRuneAt(s, result, b) - if toLower(a) != toLower(b): - result = -1 - break - dec(result, start) - of pkTerminalIgnoreStyle: - var - i = 0 - a, b: Rune - result = start - while i < len(p.term): - while true: - fastRuneAt(p.term, i, a) - if a != Rune('_'): break + c.ml = oldMl + leave(pkSearch, s, p, start, result) + of pkCapturedSearch: + enter(pkCapturedSearch, s, p, start) + var idx = c.ml # reserve a slot for the subpattern + inc(c.ml) + result = 0 + while start+result <= s.len: + var x = mopProc(s, p.sons[0], start+result, c) + if x >= 0: + if idx < MaxSubpatterns: + c.matches[idx] = (start, start+result-1) + #else: silently ignore the capture + inc(result, x) + leave(pkCapturedSearch, s, p, start, result) + return + inc(result) + result = -1 + c.ml = idx + leave(pkCapturedSearch, s, p, start, result) + of pkGreedyRep: + enter(pkGreedyRep, s, p, start) + result = 0 while true: - fastRuneAt(s, result, b) - if b != Rune('_'): break - if toLower(a) != toLower(b): - result = -1 - break - dec(result, start) - of pkChar: - if p.ch == s[start]: result = 1 - else: result = -1 - of pkCharChoice: - if contains(p.charChoice[], s[start]): result = 1 - else: result = -1 - of pkNonTerminal: - var oldMl = c.ml - when false: echo "enter: ", p.nt.name - result = rawMatch(s, p.nt.rule, start, c) - when false: echo "leave: ", p.nt.name - if result < 0: c.ml = oldMl - of pkSequence: - var oldMl = c.ml - result = 0 - for i in 0..high(p.sons): - var x = rawMatch(s, p.sons[i], start+result, c) - if x < 0: + var x = mopProc(s, p.sons[0], start+result, c) + # if x == 0, we have an endless loop; so the correct behaviour would be + # not to break. But endless loops can be easily introduced: + # ``(comment / \w*)*`` is such an example. Breaking for x == 0 does the + # expected thing in this case. + if x <= 0: break + inc(result, x) + leave(pkGreedyRep, s, p, start, result) + of pkGreedyRepChar: + enter(pkGreedyRepChar, s, p, start) + result = 0 + var ch = p.ch + while start+result < s.len and ch == s[start+result]: inc(result) + leave(pkGreedyRepChar, s, p, start, result) + of pkGreedyRepSet: + enter(pkGreedyRepSet, s, p, start) + result = 0 + while start+result < s.len and contains(p.charChoice[], s[start+result]): inc(result) + leave(pkGreedyRepSet, s, p, start, result) + of pkOption: + enter(pkOption, s, p, start) + result = max(0, mopProc(s, p.sons[0], start, c)) + leave(pkOption, s, p, start, result) + of pkAndPredicate: + enter(pkAndPredicate, s, p, start) + var oldMl = c.ml + result = mopProc(s, p.sons[0], start, c) + if result >= 0: result = 0 # do not consume anything + else: c.ml = oldMl + leave(pkAndPredicate, s, p, start, result) + of pkNotPredicate: + enter(pkNotPredicate, s, p, start) + var oldMl = c.ml + result = mopProc(s, p.sons[0], start, c) + if result < 0: result = 0 + else: c.ml = oldMl result = -1 - break - else: inc(result, x) - of pkOrderedChoice: - var oldMl = c.ml - for i in 0..high(p.sons): - result = rawMatch(s, p.sons[i], start, c) - if result >= 0: break - c.ml = oldMl - of pkSearch: - var oldMl = c.ml - result = 0 - while start+result <= s.len: - var x = rawMatch(s, p.sons[0], start+result, c) - if x >= 0: - inc(result, x) - return - inc(result) - result = -1 - c.ml = oldMl - of pkCapturedSearch: - var idx = c.ml # reserve a slot for the subpattern - inc(c.ml) - result = 0 - while start+result <= s.len: - var x = rawMatch(s, p.sons[0], start+result, c) - if x >= 0: + leave(pkNotPredicate, s, p, start, result) + of pkCapture: + enter(pkCapture, s, p, start) + var idx = c.ml # reserve a slot for the subpattern + inc(c.ml) + result = mopProc(s, p.sons[0], start, c) + if result >= 0: if idx < MaxSubpatterns: c.matches[idx] = (start, start+result-1) #else: silently ignore the capture - inc(result, x) - return - inc(result) - result = -1 - c.ml = idx - of pkGreedyRep: - result = 0 - while true: - var x = rawMatch(s, p.sons[0], start+result, c) - # if x == 0, we have an endless loop; so the correct behaviour would be - # not to break. But endless loops can be easily introduced: - # ``(comment / \w*)*`` is such an example. Breaking for x == 0 does the - # expected thing in this case. - if x <= 0: break - inc(result, x) - of pkGreedyRepChar: - result = 0 - var ch = p.ch - while ch == s[start+result]: inc(result) - of pkGreedyRepSet: - result = 0 - while contains(p.charChoice[], s[start+result]): inc(result) - of pkOption: - result = max(0, rawMatch(s, p.sons[0], start, c)) - of pkAndPredicate: - var oldMl = c.ml - result = rawMatch(s, p.sons[0], start, c) - if result >= 0: result = 0 # do not consume anything - else: c.ml = oldMl - of pkNotPredicate: - var oldMl = c.ml - result = rawMatch(s, p.sons[0], start, c) - if result < 0: result = 0 - else: - c.ml = oldMl - result = -1 - of pkCapture: - var idx = c.ml # reserve a slot for the subpattern - inc(c.ml) - result = rawMatch(s, p.sons[0], start, c) - if result >= 0: - if idx < MaxSubpatterns: - c.matches[idx] = (start, start+result-1) - #else: silently ignore the capture - else: - c.ml = idx - of pkBackRef..pkBackRefIgnoreStyle: - if p.index >= c.ml: return -1 - var (a, b) = c.matches[p.index] - var n: Peg - n.kind = succ(pkTerminal, ord(p.kind)-ord(pkBackRef)) - n.term = s.substr(a, b) - result = rawMatch(s, n, start, c) - of pkStartAnchor: - if c.origStart == start: result = 0 - else: result = -1 - of pkRule, pkList: assert false + else: + c.ml = idx + leave(pkCapture, s, p, start, result) + of pkBackRef: + enter(pkBackRef, s, p, start) + result = matchBackRef(s, p, start, c) + leave(pkBackRef, s, p, start, result) + of pkBackRefIgnoreCase: + enter(pkBackRefIgnoreCase, s, p, start) + result = matchBackRef(s, p, start, c) + leave(pkBackRefIgnoreCase, s, p, start, result) + of pkBackRefIgnoreStyle: + enter(pkBackRefIgnoreStyle, s, p, start) + result = matchBackRef(s, p, start, c) + leave(pkBackRefIgnoreStyle, s, p, start, result) + of pkStartAnchor: + enter(pkStartAnchor, s, p, start) + if c.origStart == start: result = 0 + else: result = -1 + leave(pkStartAnchor, s, p, start, result) + of pkRule, pkList: assert false + +proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int + {.noSideEffect, rtl, extern: "npegs$1".} = + ## low-level matching proc that implements the PEG interpreter. Use this + ## for maximum efficiency (every other PEG operation ends up calling this + ## proc). + ## Returns -1 if it does not match, else the length of the match + + # Set the handler generators to produce do-nothing handlers. + template enter(pk, s, p, start) = + discard + template leave(pk, s, p, start, length) = + discard + matchOrParse(matchIt) + result = matchIt(s, p, start, c) + +macro mkHandlerTplts(handlers: untyped): untyped = + # Transforms the handler spec in *handlers* into handler templates. + # The AST structure of *handlers[0]*: + # + # .. code-block:: + # StmtList + # Call + # Ident "pkNonTerminal" + # StmtList + # Call + # Ident "enter" + # StmtList + # <handler code block> + # Call + # Ident "leave" + # StmtList + # <handler code block> + # Call + # Ident "pkChar" + # StmtList + # Call + # Ident "leave" + # StmtList + # <handler code block> + # ... + proc mkEnter(hdName, body: NimNode): NimNode = + quote do: + template `hdName`(s, p, start) = + let s {.inject.} = s + let p {.inject.} = p + let start {.inject.} = start + `body` + + template mkLeave(hdPostf, body) {.dirty.} = + # this has to be dirty to be able to capture *result* as *length* in + # *leaveXX* calls. + template `leave hdPostf`(s, p, start, length) = + body + + result = newStmtList() + for topCall in handlers[0]: + if nnkCall != topCall.kind: + error("Call syntax expected.", topCall) + let pegKind = topCall[0] + if nnkIdent != pegKind.kind: + error("PegKind expected.", pegKind) + if 2 == topCall.len: + for hdDef in topCall[1]: + if nnkCall != hdDef.kind: + error("Call syntax expected.", hdDef) + if nnkIdent != hdDef[0].kind: + error("Handler identifier expected.", hdDef[0]) + if 2 == hdDef.len: + let hdPostf = substr(pegKind.strVal, 2) + case hdDef[0].strVal + of "enter": + result.add mkEnter(newIdentNode("enter" & hdPostf), hdDef[1]) + of "leave": + result.add getAst(mkLeave(ident(hdPostf), hdDef[1])) + else: + error( + "Unsupported handler identifier, expected 'enter' or 'leave'.", + hdDef[0] + ) + +template eventParser*(pegAst, handlers: untyped): (proc(s: string): int) = + ## Generates an interpreting event parser *proc* according to the specified + ## PEG AST and handler code blocks. The *proc* can be called with a string + ## to be parsed and will execute the handler code blocks whenever their + ## associated grammar element is matched. It returns -1 if the string does not + ## match, else the length of the total match. The following example code + ## evaluates an arithmetic expression defined by a simple PEG: + ## + ## .. code-block:: nim + ## import strutils, pegs + ## + ## let + ## pegAst = """ + ## Expr <- Sum + ## Sum <- Product (('+' / '-')Product)* + ## Product <- Value (('*' / '/')Value)* + ## Value <- [0-9]+ / '(' Expr ')' + ## """.peg + ## txt = "(5+3)/2-7*22" + ## + ## var + ## pStack: seq[string] = @[] + ## valStack: seq[float] = @[] + ## opStack = "" + ## let + ## parseArithExpr = pegAst.eventParser: + ## pkNonTerminal: + ## enter: + ## pStack.add p.nt.name + ## leave: + ## pStack.setLen pStack.high + ## if length > 0: + ## let matchStr = s.substr(start, start+length-1) + ## case p.nt.name + ## of "Value": + ## try: + ## valStack.add matchStr.parseFloat + ## echo valStack + ## except ValueError: + ## discard + ## of "Sum", "Product": + ## try: + ## let val = matchStr.parseFloat + ## except ValueError: + ## if valStack.len > 1 and opStack.len > 0: + ## valStack[^2] = case opStack[^1] + ## of '+': valStack[^2] + valStack[^1] + ## of '-': valStack[^2] - valStack[^1] + ## of '*': valStack[^2] * valStack[^1] + ## else: valStack[^2] / valStack[^1] + ## valStack.setLen valStack.high + ## echo valStack + ## opStack.setLen opStack.high + ## echo opStack + ## pkChar: + ## leave: + ## if length == 1 and "Value" != pStack[^1]: + ## let matchChar = s[start] + ## opStack.add matchChar + ## echo opStack + ## + ## let pLen = parseArithExpr(txt) + ## + ## The *handlers* parameter consists of code blocks for *PegKinds*, + ## which define the grammar elements of interest. Each block can contain + ## handler code to be executed when the parser enters and leaves text + ## matching the grammar element. An *enter* handler can access the specific + ## PEG AST node being matched as *p*, the entire parsed string as *s* + ## and the position of the matched text segment in *s* as *start*. A *leave* + ## handler can access *p*, *s*, *start* and also the length of the matched + ## text segment as *length*. For an unsuccessful match, the *enter* and + ## *leave* handlers will be executed, with *length* set to -1. + ## + ## Symbols declared in an *enter* handler can be made visible in the + ## corresponding *leave* handler by annotating them with an *inject* pragma. + proc rawParse(s: string, p: Peg, start: int, c: var Captures): int + {.genSym.} = + + # binding from *macros* + bind strVal + + mkHandlerTplts: + handlers + + macro enter(pegKind, s, pegNode, start: untyped): untyped = + # This is called by the matcher code in *matchOrParse* at the + # start of the code for a grammar element of kind *pegKind*. + # Expands to a call to the handler template if one was generated + # by *mkHandlerTplts*. + template mkDoEnter(hdPostf, s, pegNode, start) = + when declared(`enter hdPostf`): + `enter hdPostf`(s, pegNode, start): + else: + discard + let hdPostf = ident(substr(strVal(pegKind), 2)) + getAst(mkDoEnter(hdPostf, s, pegNode, start)) + + macro leave(pegKind, s, pegNode, start, length: untyped): untyped = + # Like *enter*, but called at the end of the matcher code for + # a grammar element of kind *pegKind*. + template mkDoLeave(hdPostf, s, pegNode, start, length) = + when declared(`leave hdPostf`): + `leave hdPostf`(s, pegNode, start, length): + else: + discard + let hdPostf = ident(substr(strVal(pegKind), 2)) + getAst(mkDoLeave(hdPostf, s, pegNode, start, length)) + + matchOrParse(parseIt) + parseIt(s, p, start, c) + + proc parser(s: string): int {.genSym.} = + # the proc to be returned + var + ms: array[MaxSubpatterns, (int, int)] + cs = Captures(matches: ms, ml: 0, origStart: 0) + rawParse(s, pegAst, 0, cs) + parser template fillMatches(s, caps, c) = for k in 0..c.ml-1: @@ -742,7 +1073,7 @@ template fillMatches(s, caps, c) = if startIdx != -1: caps[k] = substr(s, startIdx, endIdx) else: - caps[k] = nil + caps[k] = "" proc matchLen*(s: string, pattern: Peg, matches: var openArray[string], start = 0): int {.nosideEffect, rtl, extern: "npegs$1Capture".} = @@ -1006,14 +1337,18 @@ proc replace*(s: string, sub: Peg, cb: proc( inc(m) add(result, substr(s, i)) -proc transformFile*(infile, outfile: string, - subs: varargs[tuple[pattern: Peg, repl: string]]) {. - rtl, extern: "npegs$1".} = - ## reads in the file `infile`, performs a parallel replacement (calls - ## `parallelReplace`) and writes back to `outfile`. Raises ``EIO`` if an - ## error occurs. This is supposed to be used for quick scripting. - var x = readFile(infile).string - writeFile(outfile, x.parallelReplace(subs)) +when not defined(js): + proc transformFile*(infile, outfile: string, + subs: varargs[tuple[pattern: Peg, repl: string]]) {. + rtl, extern: "npegs$1".} = + ## reads in the file `infile`, performs a parallel replacement (calls + ## `parallelReplace`) and writes back to `outfile`. Raises ``EIO`` if an + ## error occurs. This is supposed to be used for quick scripting. + ## + ## **Note**: this proc does not exist while using the JS backend. + var x = readFile(infile).string + writeFile(outfile, x.parallelReplace(subs)) + iterator split*(s: string, sep: Peg): string = ## Splits the string `s` into substrings. @@ -1117,7 +1452,7 @@ proc handleCR(L: var PegLexer, pos: int): int = assert(L.buf[pos] == '\c') inc(L.lineNumber) result = pos+1 - if L.buf[result] == '\L': inc(result) + if result < L.buf.len and L.buf[result] == '\L': inc(result) L.lineStart = result proc handleLF(L: var PegLexer, pos: int): int = @@ -1213,12 +1548,13 @@ proc getEscapedChar(c: var PegLexer, tok: var Token) = proc skip(c: var PegLexer) = var pos = c.bufpos var buf = c.buf - while true: + while pos < c.buf.len: case buf[pos] of ' ', '\t': inc(pos) of '#': - while not (buf[pos] in {'\c', '\L', '\0'}): inc(pos) + while (pos < c.buf.len) and + not (buf[pos] in {'\c', '\L', '\0'}): inc(pos) of '\c': pos = handleCR(c, pos) buf = c.buf @@ -1234,7 +1570,7 @@ proc getString(c: var PegLexer, tok: var Token) = var pos = c.bufpos + 1 var buf = c.buf var quote = buf[pos-1] - while true: + while pos < c.buf.len: case buf[pos] of '\\': c.bufpos = pos @@ -1257,7 +1593,7 @@ proc getDollar(c: var PegLexer, tok: var Token) = if buf[pos] in {'0'..'9'}: tok.kind = tkBackref tok.index = 0 - while buf[pos] in {'0'..'9'}: + while pos < c.buf.len and buf[pos] in {'0'..'9'}: tok.index = tok.index * 10 + ord(buf[pos]) - ord('0') inc(pos) else: @@ -1273,11 +1609,11 @@ proc getCharSet(c: var PegLexer, tok: var Token) = if buf[pos] == '^': inc(pos) caret = true - while true: + while pos < c.buf.len: var ch: char case buf[pos] of ']': - inc(pos) + if pos < c.buf.len: inc(pos) break of '\\': c.bufpos = pos @@ -1292,11 +1628,14 @@ proc getCharSet(c: var PegLexer, tok: var Token) = inc(pos) incl(tok.charset, ch) if buf[pos] == '-': - if buf[pos+1] == ']': + if pos+1 < c.buf.len and buf[pos+1] == ']': incl(tok.charset, '-') inc(pos) else: - inc(pos) + if pos+1 < c.buf.len: + inc(pos) + else: + break var ch2: char case buf[pos] of '\\': @@ -1308,8 +1647,11 @@ proc getCharSet(c: var PegLexer, tok: var Token) = tok.kind = tkInvalid break else: - ch2 = buf[pos] - inc(pos) + if pos+1 < c.buf.len: + ch2 = buf[pos] + inc(pos) + else: + break for i in ord(ch)+1 .. ord(ch2): incl(tok.charset, chr(i)) c.bufpos = pos @@ -1318,15 +1660,15 @@ proc getCharSet(c: var PegLexer, tok: var Token) = proc getSymbol(c: var PegLexer, tok: var Token) = var pos = c.bufpos var buf = c.buf - while true: + while pos < c.buf.len: add(tok.literal, buf[pos]) inc(pos) - if buf[pos] notin strutils.IdentChars: break + if pos < buf.len and buf[pos] notin strutils.IdentChars: break c.bufpos = pos tok.kind = tkIdentifier proc getBuiltin(c: var PegLexer, tok: var Token) = - if c.buf[c.bufpos+1] in strutils.Letters: + if c.bufpos+1 < c.buf.len and c.buf[c.bufpos+1] in strutils.Letters: inc(c.bufpos) getSymbol(c, tok) tok.kind = tkBuiltin @@ -1339,10 +1681,12 @@ proc getTok(c: var PegLexer, tok: var Token) = tok.modifier = modNone setLen(tok.literal, 0) skip(c) + case c.buf[c.bufpos] of '{': inc(c.bufpos) - if c.buf[c.bufpos] == '@' and c.buf[c.bufpos+1] == '}': + if c.buf[c.bufpos] == '@' and c.bufpos+2 < c.buf.len and + c.buf[c.bufpos+1] == '}': tok.kind = tkCurlyAt inc(c.bufpos, 2) add(tok.literal, "{@}") @@ -1375,13 +1719,11 @@ proc getTok(c: var PegLexer, tok: var Token) = getBuiltin(c, tok) of '\'', '"': getString(c, tok) of '$': getDollar(c, tok) - of '\0': - tok.kind = tkEof - tok.literal = "[EOF]" of 'a'..'z', 'A'..'Z', '\128'..'\255': getSymbol(c, tok) if c.buf[c.bufpos] in {'\'', '"'} or - c.buf[c.bufpos] == '$' and c.buf[c.bufpos+1] in {'0'..'9'}: + c.buf[c.bufpos] == '$' and c.bufpos+1 < c.buf.len and + c.buf[c.bufpos+1] in {'0'..'9'}: case tok.literal of "i": tok.modifier = modIgnoreCase of "y": tok.modifier = modIgnoreStyle @@ -1402,7 +1744,7 @@ proc getTok(c: var PegLexer, tok: var Token) = inc(c.bufpos) add(tok.literal, '+') of '<': - if c.buf[c.bufpos+1] == '-': + if c.bufpos+2 < c.buf.len and c.buf[c.bufpos+1] == '-': inc(c.bufpos, 2) tok.kind = tkArrow add(tok.literal, "<-") @@ -1437,14 +1779,17 @@ proc getTok(c: var PegLexer, tok: var Token) = inc(c.bufpos) add(tok.literal, '^') else: + if c.bufpos >= c.buf.len: + tok.kind = tkEof + tok.literal = "[EOF]" add(tok.literal, c.buf[c.bufpos]) inc(c.bufpos) proc arrowIsNextTok(c: PegLexer): bool = # the only look ahead we need var pos = c.bufpos - while c.buf[pos] in {'\t', ' '}: inc(pos) - result = c.buf[pos] == '<' and c.buf[pos+1] == '-' + while pos < c.buf.len and c.buf[pos] in {'\t', ' '}: inc(pos) + result = c.buf[pos] == '<' and (pos+1 < c.buf.len) and c.buf[pos+1] == '-' # ----------------------------- parser ---------------------------------------- @@ -1467,7 +1812,7 @@ proc pegError(p: PegParser, msg: string, line = -1, col = -1) = proc getTok(p: var PegParser) = getTok(p, p.tok) - if p.tok.kind == tkInvalid: pegError(p, "invalid token") + if p.tok.kind == tkInvalid: pegError(p, "'" & p.tok.literal & "' is invalid token") proc eat(p: var PegParser, kind: TokKind) = if p.tok.kind == kind: getTok(p) @@ -1817,7 +2162,7 @@ when isMainModule: assert match("prefix/start", peg"^start$", 7) if "foo" =~ peg"{'a'}?.*": - assert matches[0] == nil + assert matches[0].len == 0 else: assert false if "foo" =~ peg"{''}.*": diff --git a/lib/pure/random.nim b/lib/pure/random.nim index 97ad12b99..e565fccf8 100644 --- a/lib/pure/random.nim +++ b/lib/pure/random.nim @@ -110,7 +110,7 @@ proc random*[T](a: openArray[T]): T {.deprecated.} = ## Use ``rand`` instead. result = a[random(a.low..a.len)] -proc rand*(r: var Rand; max: int): int {.benign.} = +proc rand*(r: var Rand; max: Natural): int {.benign.} = ## Returns a random number in the range 0..max. The sequence of ## random number is always the same, unless `randomize` is called ## which initializes the random number generator with a "random" @@ -128,7 +128,7 @@ proc rand*(max: int): int {.benign.} = ## number, i.e. a tickcount. rand(state, max) -proc rand*(r: var Rand; max: float): float {.benign.} = +proc rand*(r: var Rand; max: range[0.0 .. high(float)]): float {.benign.} = ## Returns a random number in the range 0..max. The sequence of ## random number is always the same, unless `randomize` is called ## which initializes the random number generator with a "random" @@ -191,8 +191,8 @@ when not defined(nimscript): proc randomize*() {.benign.} = ## Initializes the random number generator with a "random" ## number, i.e. a tickcount. Note: Does not work for NimScript. - let time = int64(times.epochTime() * 1_000_000_000) - randomize(time) + let now = times.getTime() + randomize(convert(Seconds, Nanoseconds, now.toUnix) + now.nanosecond) {.pop.} @@ -218,4 +218,17 @@ when isMainModule: doAssert rand(0) == 0 doAssert rand("a") == 'a' + when compileOption("rangeChecks"): + try: + discard rand(-1) + doAssert false + except RangeError: + discard + + try: + discard rand(-1.0) + doAssert false + except RangeError: + discard + main() diff --git a/lib/pure/rationals.nim b/lib/pure/rationals.nim index 7907b4e6c..3946cf85b 100644 --- a/lib/pure/rationals.nim +++ b/lib/pure/rationals.nim @@ -241,6 +241,33 @@ proc abs*[T](x: Rational[T]): Rational[T] = result.num = abs x.num result.den = abs x.den +proc `div`*[T: SomeInteger](x, y: Rational[T]): T = + ## Computes the rational truncated division. + (x.num * y.den) div (y.num * x.den) + +proc `mod`*[T: SomeInteger](x, y: Rational[T]): Rational[T] = + ## Computes the rational modulo by truncated division (remainder). + ## This is same as ``x - (x div y) * y``. + result = ((x.num * y.den) mod (y.num * x.den)) // (x.den * y.den) + reduce(result) + +proc floorDiv*[T: SomeInteger](x, y: Rational[T]): T = + ## Computes the rational floor division. + ## + ## Floor division is conceptually defined as ``floor(x / y)``. + ## This is different from the ``div`` operator, which is defined + ## as ``trunc(x / y)``. That is, ``div`` rounds towards ``0`` and ``floorDiv`` + ## rounds down. + floorDiv(x.num * y.den, y.num * x.den) + +proc floorMod*[T: SomeInteger](x, y: Rational[T]): Rational[T] = + ## Computes the rational modulo by floor division (modulo). + ## + ## This is same as ``x - floorDiv(x, y) * y``. + ## This proc behaves the same as the ``%`` operator in python. + result = floorMod(x.num * y.den, y.num * x.den) // (x.den * y.den) + reduce(result) + proc hash*[T](x: Rational[T]): Hash = ## Computes hash for rational `x` # reduce first so that hash(x) == hash(y) for x == y @@ -339,3 +366,12 @@ when isMainModule: assert toRational(0.33) == 33 // 100 assert toRational(0.22) == 11 // 50 assert toRational(10.0) == 10 // 1 + + assert (1//1) div (3//10) == 3 + assert (-1//1) div (3//10) == -3 + assert (3//10) mod (1//1) == 3//10 + assert (-3//10) mod (1//1) == -3//10 + assert floorDiv(1//1, 3//10) == 3 + assert floorDiv(-1//1, 3//10) == -4 + assert floorMod(3//10, 1//1) == 3//10 + assert floorMod(-3//10, 1//1) == 7//10 diff --git a/lib/pure/ropes.nim b/lib/pure/ropes.nim index 6ddd61afa..559afd279 100644 --- a/lib/pure/ropes.nim +++ b/lib/pure/ropes.nim @@ -19,7 +19,7 @@ include "system/inclrtl" import streams -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated {.push debugger:off .} # the user does not want to trace a part # of the standard library! @@ -37,10 +37,6 @@ type length: int data: string # != nil if a leaf -{.deprecated: [PRope: Rope].} - -proc isConc(r: Rope): bool {.inline.} = return isNil(r.data) - # Note that the left and right pointers are not needed for leafs. # Leaves have relatively high memory overhead (~30 bytes on a 32 # bit machine) and we produce many of them. This is why we cache and @@ -131,7 +127,7 @@ proc insertInCache(s: string, tree: Rope): Rope = result.left = t t.right = nil -proc rope*(s: string = nil): Rope {.rtl, extern: "nro$1Str".} = +proc rope*(s: string = ""): Rope {.rtl, extern: "nro$1Str".} = ## Converts a string to a rope. if s.len == 0: result = nil @@ -173,16 +169,8 @@ proc `&`*(a, b: Rope): Rope {.rtl, extern: "nroConcRopeRope".} = else: result = newRope() result.length = a.length + b.length - when false: - # XXX rebalancing would be nice, but is too expensive. - result.left = a.left - var x = newRope() - x.left = a.right - x.right = b - result.right = x - else: - result.left = a - result.right = b + result.left = a + result.right = b proc `&`*(a: Rope, b: string): Rope {.rtl, extern: "nroConcRopeStr".} = ## the concatenation operator for ropes. @@ -211,11 +199,11 @@ proc `[]`*(r: Rope, i: int): char {.rtl, extern: "nroCharAt".} = var j = i if x == nil: return while true: - if not isConc(x): - if x.data.len <% j: return x.data[j] + if x != nil and x.data.len > 0: + if j < x.data.len: return x.data[j] return '\0' else: - if x.left.len >% j: + if x.left.length > j: x = x.left else: x = x.right @@ -227,11 +215,11 @@ iterator leaves*(r: Rope): string = var stack = @[r] while stack.len > 0: var it = stack.pop - while isConc(it): + while it.left != nil: + assert(it.right != nil) stack.add(it.right) it = it.left assert(it != nil) - assert(it.data != nil) yield it.data iterator items*(r: Rope): char = @@ -252,54 +240,6 @@ proc `$`*(r: Rope): string {.rtl, extern: "nroToString".}= result = newStringOfCap(r.len) for s in leaves(r): add(result, s) -when false: - # Format string caching seems reasonable: All leaves can be shared and format - # string parsing has to be done only once. A compiled format string is stored - # as a rope. A negative length is used for the index into the args array. - proc compiledArg(idx: int): Rope = - new(result) - result.length = -idx - - proc compileFrmt(frmt: string): Rope = - var i = 0 - var length = len(frmt) - result = nil - var num = 0 - while i < length: - if frmt[i] == '$': - inc(i) - case frmt[i] - of '$': - add(result, "$") - inc(i) - of '#': - inc(i) - add(result, compiledArg(num+1)) - inc(num) - of '0'..'9': - var j = 0 - while true: - j = j * 10 + ord(frmt[i]) - ord('0') - inc(i) - if frmt[i] notin {'0'..'9'}: break - add(s, compiledArg(j)) - of '{': - inc(i) - var j = 0 - while frmt[i] in {'0'..'9'}: - j = j * 10 + ord(frmt[i]) - ord('0') - inc(i) - if frmt[i] == '}': inc(i) - else: raise newException(EInvalidValue, "invalid format string") - add(s, compiledArg(j)) - else: raise newException(EInvalidValue, "invalid format string") - var start = i - while i < length: - if frmt[i] != '$': inc(i) - else: break - if i - 1 >= start: - add(result, substr(frmt, start, i-1)) - proc `%`*(frmt: string, args: openArray[Rope]): Rope {. rtl, extern: "nroFormat".} = ## `%` substitution operator for ropes. Does not support the ``$identifier`` diff --git a/lib/pure/scgi.nim b/lib/pure/scgi.nim index 1ff26954e..e36803823 100644 --- a/lib/pure/scgi.nim +++ b/lib/pure/scgi.nim @@ -94,9 +94,6 @@ type disp: Dispatcher AsyncScgiState* = ref AsyncScgiStateObj -{.deprecated: [EScgi: ScgiError, TScgiState: ScgiState, - PAsyncScgiState: AsyncScgiState].} - proc recvBuffer(s: var ScgiState, L: int) = if L > s.bufLen: s.bufLen = L diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim index dda5658a2..e4c2b2124 100644 --- a/lib/pure/selectors.nim +++ b/lib/pure/selectors.nim @@ -261,6 +261,9 @@ else: param: int data: T + const + InvalidIdent = -1 + proc raiseIOSelectorsError[T](message: T) = var msg = "" when T is string: @@ -271,7 +274,7 @@ else: msg.add("Internal Error\n") var err = newException(IOSelectorsException, msg) raise err - + proc setNonBlocking(fd: cint) {.inline.} = setBlocking(fd.SocketHandle, false) @@ -302,6 +305,12 @@ else: if posix.sigprocmask(SIG_UNBLOCK, newmask, oldmask) == -1: raiseIOSelectorsError(osLastError()) + template clearKey[T](key: ptr SelectorKey[T]) = + var empty: T + key.ident = InvalidIdent + key.events = {} + key.data = empty + when defined(linux): include ioselects/ioselectors_epoll elif bsdPlatform: @@ -312,22 +321,24 @@ else: include ioselects/ioselectors_poll # need to replace it with event ports elif defined(genode): include ioselects/ioselectors_select # TODO: use the native VFS layer + elif defined(nintendoswitch): + include ioselects/ioselectors_select else: include ioselects/ioselectors_poll proc register*[T](s: Selector[T], fd: int | SocketHandle, - events: set[Event], data: T) {.deprecated.} = + events: set[Event], data: T) {.deprecated: "use registerHandle instead".} = ## **Deprecated since v0.18.0:** Use ``registerHandle`` instead. s.registerHandle(fd, events, data) -proc setEvent*(ev: SelectEvent) {.deprecated.} = +proc setEvent*(ev: SelectEvent) {.deprecated: "use trigger instead".} = ## Trigger event ``ev``. ## ## **Deprecated since v0.18.0:** Use ``trigger`` instead. ev.trigger() proc update*[T](s: Selector[T], fd: int | SocketHandle, - events: set[Event]) {.deprecated.} = + events: set[Event]) {.deprecated: "use updateHandle instead".} = ## Update file/socket descriptor ``fd``, registered in selector ## ``s`` with new events set ``event``. ## diff --git a/lib/pure/smtp.nim b/lib/pure/smtp.nim index 08e6c8112..d9b863a52 100644 --- a/lib/pure/smtp.nim +++ b/lib/pure/smtp.nim @@ -51,8 +51,6 @@ type Smtp* = SmtpBase[Socket] AsyncSmtp* = SmtpBase[AsyncSocket] -{.deprecated: [EInvalidReply: ReplyError, TMessage: Message, TSMTP: Smtp].} - proc debugSend(smtp: Smtp | AsyncSmtp, cmd: string) {.multisync.} = if smtp.debug: echo("C:" & cmd) @@ -121,8 +119,7 @@ proc newSmtp*(useSsl = false, debug=false, when compiledWithSsl: sslContext.wrapSocket(result.sock) else: - raise newException(SystemError, - "SMTP module compiled without SSL support") + {.error: "SMTP module compiled without SSL support".} proc newAsyncSmtp*(useSsl = false, debug=false, sslContext = defaultSslContext): AsyncSmtp = @@ -135,8 +132,7 @@ proc newAsyncSmtp*(useSsl = false, debug=false, when compiledWithSsl: sslContext.wrapSocket(result.sock) else: - raise newException(SystemError, - "SMTP module compiled without SSL support") + {.error: "SMTP module compiled without SSL support".} proc quitExcpt(smtp: AsyncSmtp, msg: string): Future[void] = var retFuture = newFuture[void]() diff --git a/lib/pure/stats.nim b/lib/pure/stats.nim index 964938133..ce32108c2 100644 --- a/lib/pure/stats.nim +++ b/lib/pure/stats.nim @@ -65,8 +65,6 @@ type y_stats*: RunningStat ## stats for second set of data s_xy: float ## accumulated data for combined xy -{.deprecated: [TFloatClass: FloatClass, TRunningStat: RunningStat].} - # ----------- RunningStat -------------------------- proc clear*(s: var RunningStat) = ## reset `s` diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index 354e07da3..09626136f 100644 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -57,8 +57,6 @@ type tags: [WriteIOEffect], gcsafe.} flushImpl*: proc (s: Stream) {.nimcall, tags: [WriteIOEffect], gcsafe.} -{.deprecated: [PStream: Stream, TStream: StreamObj].} - proc flush*(s: Stream) = ## flushes the buffers that the stream `s` might use. if not isNil(s.flushImpl): s.flushImpl(s) @@ -76,48 +74,32 @@ proc atEnd*(s: Stream): bool = ## been read. result = s.atEndImpl(s) -proc atEnd*(s, unused: Stream): bool {.deprecated.} = - ## checks if more data can be read from `f`. Returns true if all data has - ## been read. - result = s.atEndImpl(s) - proc setPosition*(s: Stream, pos: int) = ## sets the position `pos` of the stream `s`. s.setPositionImpl(s, pos) -proc setPosition*(s, unused: Stream, pos: int) {.deprecated.} = - ## sets the position `pos` of the stream `s`. - s.setPositionImpl(s, pos) - proc getPosition*(s: Stream): int = ## retrieves the current position in the stream `s`. result = s.getPositionImpl(s) -proc getPosition*(s, unused: Stream): int {.deprecated.} = - ## retrieves the current position in the stream `s`. - result = s.getPositionImpl(s) - proc readData*(s: Stream, buffer: pointer, bufLen: int): int = ## low level proc that reads data into an untyped `buffer` of `bufLen` size. result = s.readDataImpl(s, buffer, bufLen) -proc readAll*(s: Stream): string = - ## Reads all available data. - const bufferSize = 1000 - result = newString(bufferSize) - var r = 0 - while true: - let readBytes = readData(s, addr(result[r]), bufferSize) - if readBytes < bufferSize: - setLen(result, r+readBytes) - break - inc r, bufferSize - setLen(result, r+bufferSize) - -proc readData*(s, unused: Stream, buffer: pointer, - bufLen: int): int {.deprecated.} = - ## low level proc that reads data into an untyped `buffer` of `bufLen` size. - result = s.readDataImpl(s, buffer, bufLen) +when not defined(js): + proc readAll*(s: Stream): string = + ## Reads all available data. + const bufferSize = 1024 + var buffer {.noinit.}: array[bufferSize, char] + while true: + let readBytes = readData(s, addr(buffer[0]), bufferSize) + if readBytes == 0: + break + let prevLen = result.len + result.setLen(prevLen + readBytes) + copyMem(addr(result[prevLen]), addr(buffer[0]), readBytes) + if readBytes < bufferSize: + break proc peekData*(s: Stream, buffer: pointer, bufLen: int): int = ## low level proc that reads data into an untyped `buffer` of `bufLen` size @@ -151,12 +133,12 @@ proc write*(s: Stream, x: string) = when nimvm: writeData(s, cstring(x), x.len) else: - if x.len > 0: writeData(s, unsafeAddr x[0], x.len) + if x.len > 0: writeData(s, cstring(x), x.len) -proc writeLn*(s: Stream, args: varargs[string, `$`]) {.deprecated.} = - ## **Deprecated since version 0.11.4:** Use **writeLine** instead. +proc write*(s: Stream, args: varargs[string, `$`]) = + ## writes one or more strings to the the stream. No length fields or + ## terminating zeros are written. for str in args: write(s, str) - write(s, "\n") proc writeLine*(s: Stream, args: varargs[string, `$`]) = ## writes one or more strings to the the stream `s` followed @@ -276,21 +258,21 @@ proc readStr*(s: Stream, length: int): TaintedString = ## reads a string of length `length` from the stream `s`. Raises `EIO` if ## an error occurred. result = newString(length).TaintedString - var L = readData(s, addr(string(result)[0]), length) + var L = readData(s, cstring(result), length) if L != length: setLen(result.string, L) proc peekStr*(s: Stream, length: int): TaintedString = ## peeks a string of length `length` from the stream `s`. Raises `EIO` if ## an error occurred. result = newString(length).TaintedString - var L = peekData(s, addr(string(result)[0]), length) + var L = peekData(s, cstring(result), length) if L != length: setLen(result.string, L) proc readLine*(s: Stream, line: var TaintedString): bool = ## reads a line of text from the stream `s` into `line`. `line` must not be ## ``nil``! May throw an IO exception. - ## A line of text may be delimited by ``CR``, ``LF`` or - ## ``CRLF``. The newline character(s) are not part of the returned string. + ## A line of text may be delimited by ```LF`` or ``CRLF``. + ## The newline character(s) are not part of the returned string. ## Returns ``false`` if the end of the file has been reached, ``true`` ## otherwise. If ``false`` is returned `line` contains no new data. line.string.setLen(0) @@ -321,6 +303,8 @@ proc readLine*(s: Stream): TaintedString = ## Reads a line from a stream `s`. Note: This is not very efficient. Raises ## `EIO` if an error occurred. result = TaintedString"" + if s.atEnd: + raise newEIO("cannot read from stream") while true: var c = readChar(s) if c == '\c': @@ -338,6 +322,13 @@ proc peekLine*(s: Stream): TaintedString = defer: setPosition(s, pos) result = readLine(s) +iterator lines*(s: Stream): TaintedString = + ## Iterates over every line in the stream. + ## The iteration is based on ``readLine``. + var line: TaintedString + while s.readLine(line): + yield line + when not defined(js): type @@ -346,8 +337,6 @@ when not defined(js): data*: string pos: int - {.deprecated: [PStringStream: StringStream, TStringStream: StringStreamObj].} - proc ssAtEnd(s: Stream): bool = var s = StringStream(s) return s.pos >= s.data.len @@ -388,7 +377,10 @@ when not defined(js): proc ssClose(s: Stream) = var s = StringStream(s) - s.data = nil + when defined(nimNoNilSeqs): + s.data = "" + else: + s.data = nil proc newStringStream*(s: string = ""): StringStream = ## creates a new stream from the string `s`. @@ -407,7 +399,6 @@ when not defined(js): FileStream* = ref FileStreamObj ## a stream that encapsulates a `File` FileStreamObj* = object of Stream f: File - {.deprecated: [PFileStream: FileStream, TFileStream: FileStreamObj].} proc fsClose(s: Stream) = if FileStream(s).f != nil: @@ -447,9 +438,19 @@ when not defined(js): ## creates a new stream from the file named `filename` with the mode `mode`. ## If the file cannot be opened, nil is returned. See the `system ## <system.html>`_ module for a list of available FileMode enums. + ## **This function returns nil in case of failure. To prevent unexpected + ## behavior and ensure proper error handling, use openFileStream instead.** var f: File if open(f, filename, mode, bufSize): result = newFileStream(f) + proc openFileStream*(filename: string, mode: FileMode = fmRead, bufSize: int = -1): FileStream = + ## creates a new stream from the file named `filename` with the mode `mode`. + ## If the file cannot be opened, an IO exception is raised. + var f: File + if open(f, filename, mode, bufSize): + return newFileStream(f) + else: + raise newEIO("cannot open file") when true: discard @@ -460,9 +461,6 @@ else: handle*: FileHandle pos: int - {.deprecated: [PFileHandleStream: FileHandleStream, - TFileHandleStream: FileHandleStreamObj].} - proc newEOS(msg: string): ref OSError = new(result) result.msg = msg diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim index ba21f894f..f13eb5e8e 100644 --- a/lib/pure/strformat.nim +++ b/lib/pure/strformat.nim @@ -11,29 +11,78 @@ String `interpolation`:idx: / `format`:idx: inspired by Python's ``f``-strings. -Examples: +``fmt`` vs. ``&`` +================= + +You can use either ``fmt`` or the unary ``&`` operator for formatting. The +difference between them is subtle but important. + +The ``fmt"{expr}"`` syntax is more aesthetically pleasing, but it hides a small +gotcha. The string is a +`generalized raw string literal <manual.html#lexical-analysis-generalized-raw-string-literals>`_. +This has some surprising effects: + +.. code-block:: nim + + import strformat + let msg = "hello" + doAssert fmt"{msg}\n" == "hello\\n" + +Because the literal is a raw string literal, the ``\n`` is not interpreted as +an escape sequence. + +There are multiple ways to get around this, including the use of the ``&`` +operator: + +.. code-block:: nim + + import strformat + let msg = "hello" + + doAssert &"{msg}\n" == "hello\n" + + doAssert fmt"{msg}{'\n'}" == "hello\n" + doAssert fmt("{msg}\n") == "hello\n" + doAssert "{msg}\n".fmt == "hello\n" + +The choice of style is up to you. + +Formatting strings +================== .. code-block:: nim + import strformat + doAssert &"""{"abc":>4}""" == " abc" doAssert &"""{"abc":<4}""" == "abc " - doAssert &"{-12345:08}" == "-0012345" - doAssert &"{-1:3}" == " -1" - doAssert &"{-1:03}" == "-01" - doAssert &"{16:#X}" == "0x10" +Formatting floats +================= - doAssert &"{123.456}" == "123.456" - doAssert &"{123.456:>9.3f}" == " 123.456" - doAssert &"{123.456:9.3f}" == " 123.456" - doAssert &"{123.456:9.4f}" == " 123.4560" - doAssert &"{123.456:>9.0f}" == " 123." - doAssert &"{123.456:<9.4f}" == "123.4560 " +.. code-block:: nim - doAssert &"{123.456:e}" == "1.234560e+02" - doAssert &"{123.456:>13e}" == " 1.234560e+02" - doAssert &"{123.456:13e}" == " 1.234560e+02" + import strformat + doAssert fmt"{-12345:08}" == "-0012345" + doAssert fmt"{-1:3}" == " -1" + doAssert fmt"{-1:03}" == "-01" + doAssert fmt"{16:#X}" == "0x10" + + doAssert fmt"{123.456}" == "123.456" + doAssert fmt"{123.456:>9.3f}" == " 123.456" + doAssert fmt"{123.456:9.3f}" == " 123.456" + doAssert fmt"{123.456:9.4f}" == " 123.4560" + doAssert fmt"{123.456:>9.0f}" == " 123." + doAssert fmt"{123.456:<9.4f}" == "123.4560 " + + doAssert fmt"{123.456:e}" == "1.234560e+02" + doAssert fmt"{123.456:>13e}" == " 1.234560e+02" + doAssert fmt"{123.456:13e}" == " 1.234560e+02" + + +Implementation details +====================== An expression like ``&"{key} is {value:arg} {{z}}"`` is transformed into: @@ -86,8 +135,8 @@ For strings and numeric types the optional argument is a so-called "standard format specifier". -Standard format specifier -========================= +Standard format specifier for strings, integers and floats +========================================================== The general form of a standard format specifier is:: @@ -209,7 +258,9 @@ template callFormat(res, arg) {.dirty.} = # workaround in order to circumvent 'strutils.format' which matches # too but doesn't adhere to our protocol. res.add arg - elif compiles(format(arg, res)): + elif compiles(format(arg, res)) and + # Check if format returns void + not (compiles do: discard format(arg, res)): format(arg, res) elif compiles(format(arg)): res.add format(arg) @@ -228,133 +279,8 @@ template callFormatOption(res, arg, option) {.dirty.} = macro `&`*(pattern: string): untyped = ## For a specification of the ``&`` macro, see the module level documentation. - runnableExamples: - template check(actual, expected: string) = - doAssert actual == expected - - from strutils import toUpperAscii, repeat - - # Basic tests - let s = "string" - check &"{0} {s}", "0 string" - check &"{s[0..2].toUpperAscii}", "STR" - check &"{-10:04}", "-010" - check &"{-10:<04}", "-010" - check &"{-10:>04}", "-010" - check &"0x{10:02X}", "0x0A" - - check &"{10:#04X}", "0x0A" - - check &"""{"test":#>5}""", "#test" - check &"""{"test":>5}""", " test" - - check &"""{"test":#^7}""", "#test##" - - check &"""{"test": <5}""", "test " - check &"""{"test":<5}""", "test " - check &"{1f:.3f}", "1.000" - check &"Hello, {s}!", "Hello, string!" - - # Tests for identifers without parenthesis - check &"{s} works{s}", "string worksstring" - check &"{s:>7}", " string" - doAssert(not compiles(&"{s_works}")) # parsed as identifier `s_works` - - # Misc general tests - check &"{{}}", "{}" - check &"{0}%", "0%" - check &"{0}%asdf", "0%asdf" - check &("\n{\"\\n\"}\n"), "\n\n\n" - check &"""{"abc"}s""", "abcs" - - # String tests - check &"""{"abc"}""", "abc" - check &"""{"abc":>4}""", " abc" - check &"""{"abc":<4}""", "abc " - check &"""{"":>4}""", " " - check &"""{"":<4}""", " " - - # Int tests - check &"{12345}", "12345" - check &"{ - 12345}", "-12345" - check &"{12345:6}", " 12345" - check &"{12345:>6}", " 12345" - check &"{12345:4}", "12345" - check &"{12345:08}", "00012345" - check &"{-12345:08}", "-0012345" - check &"{0:0}", "0" - check &"{0:02}", "00" - check &"{-1:3}", " -1" - check &"{-1:03}", "-01" - check &"{10}", "10" - check &"{16:#X}", "0x10" - check &"{16:^#7X}", " 0x10 " - check &"{16:^+#7X}", " +0x10 " - - # Hex tests - check &"{0:x}", "0" - check &"{-0:x}", "0" - check &"{255:x}", "ff" - check &"{255:X}", "FF" - check &"{-255:x}", "-ff" - check &"{-255:X}", "-FF" - check &"{255:x} uNaffeCteD CaSe", "ff uNaffeCteD CaSe" - check &"{255:X} uNaffeCteD CaSe", "FF uNaffeCteD CaSe" - check &"{255:4x}", " ff" - check &"{255:04x}", "00ff" - check &"{-255:4x}", " -ff" - check &"{-255:04x}", "-0ff" - - # Float tests - check &"{123.456}", "123.456" - check &"{-123.456}", "-123.456" - check &"{123.456:.3f}", "123.456" - check &"{123.456:+.3f}", "+123.456" - check &"{-123.456:+.3f}", "-123.456" - check &"{-123.456:.3f}", "-123.456" - check &"{123.456:1g}", "123.456" - check &"{123.456:.1f}", "123.5" - check &"{123.456:.0f}", "123." - #check &"{123.456:.0f}", "123." - check &"{123.456:>9.3f}", " 123.456" - check &"{123.456:9.3f}", " 123.456" - check &"{123.456:>9.4f}", " 123.4560" - check &"{123.456:>9.0f}", " 123." - check &"{123.456:<9.4f}", "123.4560 " - - # Float (scientific) tests - check &"{123.456:e}", "1.234560e+02" - check &"{123.456:>13e}", " 1.234560e+02" - check &"{123.456:<13e}", "1.234560e+02 " - check &"{123.456:.1e}", "1.2e+02" - check &"{123.456:.2e}", "1.23e+02" - check &"{123.456:.3e}", "1.235e+02" - - # Note: times.format adheres to the format protocol. Test that this - # works: - import times - - var nullTime: DateTime - check &"{nullTime:yyyy-mm-dd}", "0000-00-00" - - # Unicode string tests - check &"""{"αβγ"}""", "αβγ" - check &"""{"αβγ":>5}""", " αβγ" - check &"""{"αβγ":<5}""", "αβγ " - check &"""a{"a"}α{"α"}€{"€"}𐍈{"𐍈"}""", "aaαα€€𐍈𐍈" - check &"""a{"a":2}α{"α":2}€{"€":2}𐍈{"𐍈":2}""", "aa αα €€ 𐍈𐍈 " - # Invalid unicode sequences should be handled as plain strings. - # Invalid examples taken from: https://stackoverflow.com/a/3886015/1804173 - let invalidUtf8 = [ - "\xc3\x28", "\xa0\xa1", - "\xe2\x28\xa1", "\xe2\x82\x28", - "\xf0\x28\x8c\xbc", "\xf0\x90\x28\xbc", "\xf0\x28\x8c\x28" - ] - for s in invalidUtf8: - check &"{s:>5}", repeat(" ", 5-s.len) & s - if pattern.kind notin {nnkStrLit..nnkTripleStrLit}: - error "& only works with string literals", pattern + error "string formatting (fmt(), &) only works with string literals", pattern let f = pattern.strVal var i = 0 let res = genSym(nskVar, "fmtRes") @@ -409,14 +335,6 @@ macro `&`*(pattern: string): untyped = template fmt*(pattern: string): untyped = ## An alias for ``&``. - ## **Examples:** - ## - ## .. code-block:: nim - ## import json - ## import strformat except `&` - ## - ## let example = "oh, look no conflicts anymore" - ## echo fmt"{example}" bind `&` &pattern @@ -459,7 +377,7 @@ type ## ``parseStandardFormatSpecifier`` returned. proc formatInt(n: SomeNumber; radix: int; spec: StandardFormatSpecifier): string = - ## Converts ``n`` to string. If ``n`` is `SomeReal`, it casts to `int64`. + ## Converts ``n`` to string. If ``n`` is `SomeFloat`, it casts to `int64`. ## Conversion is done using ``radix``. If result's length is lesser than ## ``minimumWidth``, it aligns result to the right or left (depending on ``a``) ## with ``fill`` char. @@ -587,8 +505,8 @@ proc format*(value: SomeInteger; specifier: string; res: var string) = " of 'x', 'X', 'b', 'd', 'o' but got: " & spec.typ) res.add formatInt(value, radix, spec) -proc format*(value: SomeReal; specifier: string; res: var string) = - ## Standard format implementation for ``SomeReal``. It makes little +proc format*(value: SomeFloat; specifier: string; res: var string) = + ## Standard format implementation for ``SomeFloat``. It makes little ## sense to call this directly, but it is required to exist ## by the ``&`` macro. let spec = parseStandardFormatSpecifier(specifier) @@ -608,8 +526,31 @@ proc format*(value: SomeReal; specifier: string; res: var string) = " of 'e', 'E', 'f', 'F', 'g', 'G' but got: " & spec.typ) var f = formatBiggestFloat(value, fmode, spec.precision) - if value >= 0.0 and spec.sign != '-': - f = spec.sign & f + var sign = false + if value >= 0.0: + if spec.sign != '-': + sign = true + if value == 0.0: + if 1.0 / value == Inf: + # only insert the sign if value != negZero + f.insert($spec.sign, 0) + else: + f.insert($spec.sign, 0) + else: + sign = true + + if spec.padWithZero: + var sign_str = "" + if sign: + sign_str = $f[0] + f = f[1..^1] + + let toFill = spec.minimumWidth - f.len - ord(sign) + if toFill > 0: + f = repeat('0', toFill) & f + if sign: + f = sign_str & f + # the default for numbers is right-alignment: let align = if spec.align == '\0': '>' else: spec.align let result = alignString(f, spec.minimumWidth, @@ -624,15 +565,149 @@ proc format*(value: string; specifier: string; res: var string) = ## sense to call this directly, but it is required to exist ## by the ``&`` macro. let spec = parseStandardFormatSpecifier(specifier) + var value = value case spec.typ of 's', '\0': discard else: raise newException(ValueError, "invalid type in format string for string, expected 's', but got " & spec.typ) + if spec.precision != -1: + if spec.precision < runelen(value): + setLen(value, runeOffset(value, spec.precision)) res.add alignString(value, spec.minimumWidth, spec.align, spec.fill) when isMainModule: + template check(actual, expected: string) = + doAssert actual == expected + + from strutils import toUpperAscii, repeat + + # Basic tests + let s = "string" + check &"{0} {s}", "0 string" + check &"{s[0..2].toUpperAscii}", "STR" + check &"{-10:04}", "-010" + check &"{-10:<04}", "-010" + check &"{-10:>04}", "-010" + check &"0x{10:02X}", "0x0A" + + check &"{10:#04X}", "0x0A" + + check &"""{"test":#>5}""", "#test" + check &"""{"test":>5}""", " test" + + check &"""{"test":#^7}""", "#test##" + + check &"""{"test": <5}""", "test " + check &"""{"test":<5}""", "test " + check &"{1f:.3f}", "1.000" + check &"Hello, {s}!", "Hello, string!" + + # Tests for identifers without parenthesis + check &"{s} works{s}", "string worksstring" + check &"{s:>7}", " string" + doAssert(not compiles(&"{s_works}")) # parsed as identifier `s_works` + + # Misc general tests + check &"{{}}", "{}" + check &"{0}%", "0%" + check &"{0}%asdf", "0%asdf" + check &("\n{\"\\n\"}\n"), "\n\n\n" + check &"""{"abc"}s""", "abcs" + + # String tests + check &"""{"abc"}""", "abc" + check &"""{"abc":>4}""", " abc" + check &"""{"abc":<4}""", "abc " + check &"""{"":>4}""", " " + check &"""{"":<4}""", " " + + # Int tests + check &"{12345}", "12345" + check &"{ - 12345}", "-12345" + check &"{12345:6}", " 12345" + check &"{12345:>6}", " 12345" + check &"{12345:4}", "12345" + check &"{12345:08}", "00012345" + check &"{-12345:08}", "-0012345" + check &"{0:0}", "0" + check &"{0:02}", "00" + check &"{-1:3}", " -1" + check &"{-1:03}", "-01" + check &"{10}", "10" + check &"{16:#X}", "0x10" + check &"{16:^#7X}", " 0x10 " + check &"{16:^+#7X}", " +0x10 " + + # Hex tests + check &"{0:x}", "0" + check &"{-0:x}", "0" + check &"{255:x}", "ff" + check &"{255:X}", "FF" + check &"{-255:x}", "-ff" + check &"{-255:X}", "-FF" + check &"{255:x} uNaffeCteD CaSe", "ff uNaffeCteD CaSe" + check &"{255:X} uNaffeCteD CaSe", "FF uNaffeCteD CaSe" + check &"{255:4x}", " ff" + check &"{255:04x}", "00ff" + check &"{-255:4x}", " -ff" + check &"{-255:04x}", "-0ff" + + # Float tests + check &"{123.456}", "123.456" + check &"{-123.456}", "-123.456" + check &"{123.456:.3f}", "123.456" + check &"{123.456:+.3f}", "+123.456" + check &"{-123.456:+.3f}", "-123.456" + check &"{-123.456:.3f}", "-123.456" + check &"{123.456:1g}", "123.456" + check &"{123.456:.1f}", "123.5" + check &"{123.456:.0f}", "123." + #check &"{123.456:.0f}", "123." + check &"{123.456:>9.3f}", " 123.456" + check &"{123.456:9.3f}", " 123.456" + check &"{123.456:>9.4f}", " 123.4560" + check &"{123.456:>9.0f}", " 123." + check &"{123.456:<9.4f}", "123.4560 " + + # Float (scientific) tests + check &"{123.456:e}", "1.234560e+02" + check &"{123.456:>13e}", " 1.234560e+02" + check &"{123.456:<13e}", "1.234560e+02 " + check &"{123.456:.1e}", "1.2e+02" + check &"{123.456:.2e}", "1.23e+02" + check &"{123.456:.3e}", "1.235e+02" + + # Note: times.format adheres to the format protocol. Test that this + # works: + import times + + var dt = initDateTime(01, mJan, 2000, 00, 00, 00) + check &"{dt:yyyy-MM-dd}", "2000-01-01" + + var tm = fromUnix(0) + discard &"{tm}" + + # Unicode string tests + check &"""{"αβγ"}""", "αβγ" + check &"""{"αβγ":>5}""", " αβγ" + check &"""{"αβγ":<5}""", "αβγ " + check &"""a{"a"}α{"α"}€{"€"}𐍈{"𐍈"}""", "aaαα€€𐍈𐍈" + check &"""a{"a":2}α{"α":2}€{"€":2}𐍈{"𐍈":2}""", "aa αα €€ 𐍈𐍈 " + # Invalid unicode sequences should be handled as plain strings. + # Invalid examples taken from: https://stackoverflow.com/a/3886015/1804173 + let invalidUtf8 = [ + "\xc3\x28", "\xa0\xa1", + "\xe2\x28\xa1", "\xe2\x82\x28", + "\xf0\x28\x8c\xbc", "\xf0\x90\x28\xbc", "\xf0\x28\x8c\x28" + ] + for s in invalidUtf8: + check &"{s:>5}", repeat(" ", 5-s.len) & s + + import json doAssert fmt"{'a'} {'b'}" == "a b" + + echo("All tests ok") diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 89ef2fcd2..d1ff920c9 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -12,7 +12,7 @@ import strutils -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated proc expandTabs*(s: string, tabSize: int = 8): string {.noSideEffect, procvar.} = diff --git a/lib/pure/strscans.nim b/lib/pure/strscans.nim index 2bd87837f..b17eee6ff 100644 --- a/lib/pure/strscans.nim +++ b/lib/pure/strscans.nim @@ -10,7 +10,7 @@ ##[ This module contains a `scanf`:idx: macro that can be used for extracting substrings from an input string. This is often easier than regular expressions. -Some examples as an apetizer: +Some examples as an appetizer: .. code-block:: nim # check if input string matches a triple of integers: @@ -87,7 +87,7 @@ which we then use in our scanf pattern to help us in the matching process: proc someSep(input: string; start: int; seps: set[char] = {':','-','.'}): int = # Note: The parameters and return value must match to what ``scanf`` requires result = 0 - while input[start+result] in seps: inc result + while start+result < input.len and input[start+result] in seps: inc result if scanf(input, "$w$[someSep]$w", key, value): ... @@ -231,7 +231,7 @@ is performed. var i = start var u = 0 while true: - if s[i] == '\0' or s[i] == unless: + if i >= s.len or s[i] == unless: return 0 elif s[i] == until[0]: u = 1 @@ -308,13 +308,18 @@ proc buildUserCall(x: string; args: varargs[NimNode]): NimNode = for i in 1..<y.len: result.add y[i] macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): bool = - ## See top level documentation of his module of how ``scanf`` works. + ## See top level documentation of this module about how ``scanf`` works. template matchBind(parser) {.dirty.} = var resLen = genSym(nskLet, "resLen") conds.add newLetStmt(resLen, newCall(bindSym(parser), inp, results[i], idx)) conds.add resLen.notZero conds.add resLen + template at(s: string; i: int): char = (if i < s.len: s[i] else: '\0') + template matchError() = + error("type mismatch between pattern '$" & pattern[p] & "' (position: " & $p & ") and " & $getType(results[i]) & + " var '" & repr(results[i]) & "'") + var i = 0 var p = 0 var idx = genSym(nskVar, "idx") @@ -336,37 +341,37 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b if i < results.len and getType(results[i]).typeKind == ntyString: matchBind "parseIdent" else: - error("no string var given for $w") + matchError inc i of 'b': if i < results.len and getType(results[i]).typeKind == ntyInt: matchBind "parseBin" else: - error("no int var given for $b") + matchError inc i of 'o': if i < results.len and getType(results[i]).typeKind == ntyInt: matchBind "parseOct" else: - error("no int var given for $o") + matchError inc i of 'i': if i < results.len and getType(results[i]).typeKind == ntyInt: matchBind "parseInt" else: - error("no int var given for $i") + matchError inc i of 'h': if i < results.len and getType(results[i]).typeKind == ntyInt: matchBind "parseHex" else: - error("no int var given for $h") + matchError inc i of 'f': if i < results.len and getType(results[i]).typeKind == ntyFloat: matchBind "parseFloat" else: - error("no float var given for $f") + matchError inc i of 's': conds.add newCall(bindSym"inc", idx, newCall(bindSym"skipWhitespace", inp, idx)) @@ -390,14 +395,14 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b conds.add newCall(bindSym"!=", resLen, newLit min) conds.add resLen else: - error("no string var given for $" & pattern[p]) + matchError inc i of '{': inc p var nesting = 0 let start = p while true: - case pattern[p] + case pattern.at(p) of '{': inc nesting of '}': if nesting == 0: break @@ -412,14 +417,14 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b conds.add newCall(bindSym"!=", resLen, newLit 0) conds.add resLen else: - error("no var given for $" & expr) + error("no var given for $" & expr & " (position: " & $p & ")") inc i of '[': inc p var nesting = 0 let start = p while true: - case pattern[p] + case pattern.at(p) of '[': inc nesting of ']': if nesting == 0: break @@ -451,10 +456,12 @@ macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): b template atom*(input: string; idx: int; c: char): bool = ## Used in scanp for the matching of atoms (usually chars). - input[idx] == c + idx < input.len and input[idx] == c template atom*(input: string; idx: int; s: set[char]): bool = - input[idx] in s + idx < input.len and input[idx] in s + +template hasNxt*(input: string; idx: int): bool = idx < input.len #template prepare*(input: string): int = 0 template success*(x: int): bool = x != 0 @@ -462,7 +469,7 @@ template success*(x: int): bool = x != 0 template nxt*(input: string; idx, step: int = 1) = inc(idx, step) macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = - ## ``scanp`` is currently undocumented. + ## See top level documentation of this module about how ``scanp`` works. type StmtTriple = tuple[init, cond, action: NimNode] template interf(x): untyped = bindSym(x, brForceOpen) @@ -508,8 +515,8 @@ macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = !!newCall(interf"nxt", input, idx, resLen)) of nnkCallKinds: # *{'A'..'Z'} !! s.add(!_) - template buildWhile(init, cond, action): untyped = - while true: + template buildWhile(input, idx, init, cond, action): untyped = + while hasNxt(input, idx): init if not cond: break action @@ -528,11 +535,11 @@ macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = !!newCall(interf"nxt", input, idx, it[2])) elif it.kind == nnkPrefix and it[0].eqIdent"*": let (init, cond, action) = atm(it[1], input, idx, attached) - result = (getAst(buildWhile(init, cond, action)), + result = (getAst(buildWhile(input, idx, init, cond, action)), newEmptyNode(), newEmptyNode()) elif it.kind == nnkPrefix and it[0].eqIdent"+": # x+ is the same as xx* - result = atm(newTree(nnkPar, it[1], newTree(nnkPrefix, ident"*", it[1])), + result = atm(newTree(nnkTupleConstr, it[1], newTree(nnkPrefix, ident"*", it[1])), input, idx, attached) elif it.kind == nnkPrefix and it[0].eqIdent"?": # optional. @@ -583,18 +590,18 @@ macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = result = (newEmptyNode(), newCall(interf"atom", input, idx, it), !!newCall(interf"nxt", input, idx)) of nnkCurlyExpr: if it.len == 3 and it[1].kind == nnkIntLit and it[2].kind == nnkIntLit: - var h = newTree(nnkPar, it[0]) + var h = newTree(nnkTupleConstr, it[0]) for count in 2i64 .. it[1].intVal: h.add(it[0]) for count in it[1].intVal .. it[2].intVal-1: h.add(newTree(nnkPrefix, ident"?", it[0])) result = atm(h, input, idx, attached) elif it.len == 2 and it[1].kind == nnkIntLit: - var h = newTree(nnkPar, it[0]) + var h = newTree(nnkTupleConstr, it[0]) for count in 2i64 .. it[1].intVal: h.add(it[0]) result = atm(h, input, idx, attached) else: error("invalid pattern") - of nnkPar: - if it.len == 1: + of nnkPar, nnkTupleConstr: + if it.len == 1 and it.kind == nnkPar: result = atm(it[0], input, idx, attached) else: # concatenation: @@ -621,7 +628,7 @@ macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = when isMainModule: proc twoDigits(input: string; x: var int; start: int): int = - if input[start] == '0' and input[start+1] == '0': + if start+1 < input.len and input[start] == '0' and input[start+1] == '0': result = 2 x = 13 else: @@ -629,10 +636,10 @@ when isMainModule: proc someSep(input: string; start: int; seps: set[char] = {';',',','-','.'}): int = result = 0 - while input[start+result] in seps: inc result + while start+result < input.len and input[start+result] in seps: inc result proc demangle(s: string; res: var string; start: int): int = - while s[result+start] in {'_', '@'}: inc result + while result+start < s.len and s[result+start] in {'_', '@'}: inc result res = "" while result+start < s.len and s[result+start] > ' ' and s[result+start] != '_': res.add s[result+start] @@ -652,7 +659,7 @@ when isMainModule: var info = "" if scanp(resp, idx, *`whites`, '#', *`digits`, +`whites`, ?("0x", *`hexdigits`, " in "), demangle($input, prc, $index), *`whites`, '(', * ~ ')', ')', - *`whites`, "at ", +(~{'\C', '\L', '\0'} -> info.add($_)) ): + *`whites`, "at ", +(~{'\C', '\L'} -> info.add($_)) ): result.add prc & " " & info else: break @@ -713,7 +720,7 @@ when isMainModule: "NimMainInner c:/users/anwender/projects/nim/lib/system.nim:2605", "NimMain c:/users/anwender/projects/nim/lib/system.nim:2613", "main c:/users/anwender/projects/nim/lib/system.nim:2620"] - doAssert parseGDB(gdbOut) == result + #doAssert parseGDB(gdbOut) == result # bug #6487 var count = 0 diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim index 75c5e171d..d8a23286a 100644 --- a/lib/pure/strtabs.nim +++ b/lib/pure/strtabs.nim @@ -17,9 +17,7 @@ import when defined(js): {.pragma: rtlFunc.} - {.pragma: deprecatedGetFunc.} else: - {.pragma: deprecatedGetFunc, deprecatedGet.} {.pragma: rtlFunc, rtl.} import os include "system/inclrtl" @@ -29,7 +27,7 @@ type modeCaseSensitive, ## the table is case sensitive modeCaseInsensitive, ## the table is case insensitive modeStyleInsensitive ## the table is style insensitive - KeyValuePair = tuple[key, val: string] + KeyValuePair = tuple[key, val: string, hasValue: bool] KeyValuePairSeq = seq[KeyValuePair] StringTableObj* = object of RootObj counter: int @@ -38,9 +36,6 @@ type StringTableRef* = ref StringTableObj ## use this type to declare string tables -{.deprecated: [TStringTableMode: StringTableMode, - TStringTable: StringTableObj, PStringTable: StringTableRef].} - proc len*(t: StringTableRef): int {.rtlFunc, extern: "nst$1".} = ## returns the number of keys in `t`. result = t.counter @@ -48,19 +43,19 @@ proc len*(t: StringTableRef): int {.rtlFunc, extern: "nst$1".} = iterator pairs*(t: StringTableRef): tuple[key, value: string] = ## iterates over every (key, value) pair in the table `t`. for h in 0..high(t.data): - if not isNil(t.data[h].key): + if t.data[h].hasValue: yield (t.data[h].key, t.data[h].val) iterator keys*(t: StringTableRef): string = ## iterates over every key in the table `t`. for h in 0..high(t.data): - if not isNil(t.data[h].key): + if t.data[h].hasValue: yield t.data[h].key iterator values*(t: StringTableRef): string = ## iterates over every value in the table `t`. for h in 0..high(t.data): - if not isNil(t.data[h].key): + if t.data[h].hasValue: yield t.data[h].val type @@ -73,10 +68,6 @@ type useKey ## do not replace ``$key`` if it is not found ## in the table (or in the environment) -{.deprecated: [TFormatFlag: FormatFlag].} - -# implementation - const growthFactor = 2 startSize = 64 @@ -102,7 +93,7 @@ proc nextTry(h, maxHash: Hash): Hash {.inline.} = proc rawGet(t: StringTableRef, key: string): int = var h: Hash = myhash(t, key) and high(t.data) # start with real hash value - while not isNil(t.data[h].key): + while t.data[h].hasValue: if myCmp(t, t.data[h].key, key): return h h = nextTry(h, high(t.data)) @@ -118,17 +109,12 @@ template get(t: StringTableRef, key: string) = raise newException(KeyError, "key not found") proc `[]`*(t: StringTableRef, key: string): var string {. - rtlFunc, extern: "nstTake", deprecatedGetFunc.} = + rtlFunc, extern: "nstTake".} = ## retrieves the location at ``t[key]``. If `key` is not in `t`, the ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether ## the key exists. get(t, key) -proc mget*(t: StringTableRef, key: string): var string {.deprecated.} = - ## retrieves the location at ``t[key]``. If `key` is not in `t`, the - ## ``KeyError`` exception is raised. Use ```[]``` instead. - get(t, key) - proc getOrDefault*(t: StringTableRef; key: string, default: string = ""): string = var index = rawGet(t, key) if index >= 0: result = t.data[index].val @@ -144,16 +130,17 @@ proc contains*(t: StringTableRef, key: string): bool = proc rawInsert(t: StringTableRef, data: var KeyValuePairSeq, key, val: string) = var h: Hash = myhash(t, key) and high(data) - while not isNil(data[h].key): + while data[h].hasValue: h = nextTry(h, high(data)) data[h].key = key data[h].val = val + data[h].hasValue = true proc enlarge(t: StringTableRef) = var n: KeyValuePairSeq newSeq(n, len(t.data) * growthFactor) for i in countup(0, high(t.data)): - if not isNil(t.data[i].key): rawInsert(t, n, t.data[i].key, t.data[i].val) + if t.data[i].hasValue: rawInsert(t, n, t.data[i].key, t.data[i].val) swap(t.data, n) proc `[]=`*(t: StringTableRef, key, val: string) {.rtlFunc, extern: "nstPut".} = @@ -192,14 +179,14 @@ proc newStringTable*(mode: StringTableMode): StringTableRef {. result.counter = 0 newSeq(result.data, startSize) -proc clear*(s: StringTableRef, mode: StringTableMode) = +proc clear*(s: StringTableRef, mode: StringTableMode) {. + rtlFunc, extern: "nst$1".} = ## resets a string table to be empty again. s.mode = mode s.counter = 0 s.data.setLen(startSize) for i in 0..<s.data.len: - if not isNil(s.data[i].key): - s.data[i].key = nil + s.data[i].hasValue = false proc newStringTable*(keyValuePairs: varargs[string], mode: StringTableMode): StringTableRef {. diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 2ad006001..396f14972 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -17,7 +17,11 @@ import parseutils from math import pow, round, floor, log10 from algorithm import reverse -{.deadCodeElim: on.} +when defined(nimVmExportFixed): + from unicode import toLower, toUpper + export toLower, toUpper + +{.deadCodeElim: on.} # dce option deprecated {.push debugger:off .} # the user does not want to trace a part # of the standard library! @@ -106,6 +110,12 @@ proc isUpperAscii*(c: char): bool {.noSideEffect, procvar, ## This checks ASCII characters only. return c in {'A'..'Z'} +template isImpl(call) = + if s.len == 0: return false + result = true + for c in s: + if not call(c): return false + proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsAlphaAsciiStr".} = ## Checks whether or not `s` is alphabetical. @@ -114,12 +124,7 @@ proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, ## Returns true if all characters in `s` are ## alphabetic and there is at least one character ## in `s`. - if s.len() == 0: - return false - - result = true - for c in s: - if not c.isAlphaAscii(): return false + isImpl isAlphaAscii proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsAlphaNumericStr".} = @@ -129,13 +134,7 @@ proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, ## Returns true if all characters in `s` are ## alpanumeric and there is at least one character ## in `s`. - if s.len() == 0: - return false - - result = true - for c in s: - if not c.isAlphaNumeric(): - return false + isImpl isAlphaNumeric proc isDigit*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsDigitStr".} = @@ -145,13 +144,7 @@ proc isDigit*(s: string): bool {.noSideEffect, procvar, ## Returns true if all characters in `s` are ## numeric and there is at least one character ## in `s`. - if s.len() == 0: - return false - - result = true - for c in s: - if not c.isDigit(): - return false + isImpl isDigit proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsSpaceAsciiStr".} = @@ -159,43 +152,54 @@ proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, ## ## Returns true if all characters in `s` are whitespace ## characters and there is at least one character in `s`. - if s.len() == 0: - return false + isImpl isSpaceAscii - result = true +template isCaseImpl(s, charProc, skipNonAlpha) = + var hasAtleastOneAlphaChar = false + if s.len == 0: return false for c in s: - if not c.isSpaceAscii(): - return false + if skipNonAlpha: + var charIsAlpha = c.isAlphaAscii() + if not hasAtleastOneAlphaChar: + hasAtleastOneAlphaChar = charIsAlpha + if charIsAlpha and (not charProc(c)): + return false + else: + if not charProc(c): + return false + return if skipNonAlpha: hasAtleastOneAlphaChar else: true -proc isLowerAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsLowerAsciiStr".} = - ## Checks whether or not `s` contains all lower case characters. +proc isLowerAscii*(s: string, skipNonAlpha: bool): bool = + ## Checks whether ``s`` is lower case. ## ## This checks ASCII characters only. - ## Returns true if all characters in `s` are lower case - ## and there is at least one character in `s`. - if s.len() == 0: - return false - - for c in s: - if not c.isLowerAscii(): - return false - true + ## + ## If ``skipNonAlpha`` is true, returns true if all alphabetical + ## characters in ``s`` are lower case. Returns false if none of the + ## characters in ``s`` are alphabetical. + ## + ## If ``skipNonAlpha`` is false, returns true only if all characters + ## in ``s`` are alphabetical and lower case. + ## + ## For either value of ``skipNonAlpha``, returns false if ``s`` is + ## an empty string. + isCaseImpl(s, isLowerAscii, skipNonAlpha) -proc isUpperAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsUpperAsciiStr".} = - ## Checks whether or not `s` contains all upper case characters. +proc isUpperAscii*(s: string, skipNonAlpha: bool): bool = + ## Checks whether ``s`` is upper case. ## ## This checks ASCII characters only. - ## Returns true if all characters in `s` are upper case - ## and there is at least one character in `s`. - if s.len() == 0: - return false - - for c in s: - if not c.isUpperAscii(): - return false - true + ## + ## If ``skipNonAlpha`` is true, returns true if all alphabetical + ## characters in ``s`` are upper case. Returns false if none of the + ## characters in ``s`` are alphabetical. + ## + ## If ``skipNonAlpha`` is false, returns true only if all characters + ## in ``s`` are alphabetical and upper case. + ## + ## For either value of ``skipNonAlpha``, returns false if ``s`` is + ## an empty string. + isCaseImpl(s, isUpperAscii, skipNonAlpha) proc toLowerAscii*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToLowerAsciiChar".} = @@ -209,6 +213,11 @@ proc toLowerAscii*(c: char): char {.noSideEffect, procvar, else: result = c +template toImpl(call) = + result = newString(len(s)) + for i in 0..len(s) - 1: + result[i] = call(s[i]) + proc toLowerAscii*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuToLowerAsciiStr".} = ## Converts `s` into lower case. @@ -216,9 +225,7 @@ proc toLowerAscii*(s: string): string {.noSideEffect, procvar, ## This works only for the letters ``A-Z``. See `unicode.toLower ## <unicode.html#toLower>`_ for a version that works for any Unicode ## character. - result = newString(len(s)) - for i in 0..len(s) - 1: - result[i] = toLowerAscii(s[i]) + toImpl toLowerAscii proc toUpperAscii*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToUpperAsciiChar".} = @@ -239,154 +246,22 @@ proc toUpperAscii*(s: string): string {.noSideEffect, procvar, ## This works only for the letters ``A-Z``. See `unicode.toUpper ## <unicode.html#toUpper>`_ for a version that works for any Unicode ## character. - result = newString(len(s)) - for i in 0..len(s) - 1: - result[i] = toUpperAscii(s[i]) + toImpl toUpperAscii proc capitalizeAscii*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuCapitalizeAscii".} = ## Converts the first character of `s` into upper case. ## ## This works only for the letters ``A-Z``. - result = toUpperAscii(s[0]) & substr(s, 1) - -proc isSpace*(c: char): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsSpaceChar".}= - ## Checks whether or not `c` is a whitespace character. - ## - ## **Deprecated since version 0.15.0**: use ``isSpaceAscii`` instead. - isSpaceAscii(c) - -proc isLower*(c: char): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsLowerChar".}= - ## Checks whether or not `c` is a lower case character. - ## - ## This checks ASCII characters only. - ## - ## **Deprecated since version 0.15.0**: use ``isLowerAscii`` instead. - isLowerAscii(c) - -proc isUpper*(c: char): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsUpperChar".}= - ## Checks whether or not `c` is an upper case character. - ## - ## This checks ASCII characters only. - ## - ## **Deprecated since version 0.15.0**: use ``isUpperAscii`` instead. - isUpperAscii(c) - -proc isAlpha*(c: char): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsAlphaChar".}= - ## Checks whether or not `c` is alphabetical. - ## - ## This checks a-z, A-Z ASCII characters only. - ## - ## **Deprecated since version 0.15.0**: use ``isAlphaAscii`` instead. - isAlphaAscii(c) - -proc isAlpha*(s: string): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsAlphaStr".}= - ## Checks whether or not `s` is alphabetical. - ## - ## This checks a-z, A-Z ASCII characters only. - ## Returns true if all characters in `s` are - ## alphabetic and there is at least one character - ## in `s`. - ## - ## **Deprecated since version 0.15.0**: use ``isAlphaAscii`` instead. - isAlphaAscii(s) - -proc isSpace*(s: string): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsSpaceStr".}= - ## Checks whether or not `s` is completely whitespace. - ## - ## Returns true if all characters in `s` are whitespace - ## characters and there is at least one character in `s`. - ## - ## **Deprecated since version 0.15.0**: use ``isSpaceAscii`` instead. - isSpaceAscii(s) - -proc isLower*(s: string): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsLowerStr".}= - ## Checks whether or not `s` contains all lower case characters. - ## - ## This checks ASCII characters only. - ## Returns true if all characters in `s` are lower case - ## and there is at least one character in `s`. - ## - ## **Deprecated since version 0.15.0**: use ``isLowerAscii`` instead. - isLowerAscii(s) - -proc isUpper*(s: string): bool {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuIsUpperStr".}= - ## Checks whether or not `s` contains all upper case characters. - ## - ## This checks ASCII characters only. - ## Returns true if all characters in `s` are upper case - ## and there is at least one character in `s`. - ## - ## **Deprecated since version 0.15.0**: use ``isUpperAscii`` instead. - isUpperAscii(s) - -proc toLower*(c: char): char {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuToLowerChar".} = - ## Converts `c` into lower case. - ## - ## This works only for the letters ``A-Z``. See `unicode.toLower - ## <unicode.html#toLower>`_ for a version that works for any Unicode - ## character. - ## - ## **Deprecated since version 0.15.0**: use ``toLowerAscii`` instead. - toLowerAscii(c) - -proc toLower*(s: string): string {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuToLowerStr".} = - ## Converts `s` into lower case. - ## - ## This works only for the letters ``A-Z``. See `unicode.toLower - ## <unicode.html#toLower>`_ for a version that works for any Unicode - ## character. - ## - ## **Deprecated since version 0.15.0**: use ``toLowerAscii`` instead. - toLowerAscii(s) - -proc toUpper*(c: char): char {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuToUpperChar".} = - ## Converts `c` into upper case. - ## - ## This works only for the letters ``A-Z``. See `unicode.toUpper - ## <unicode.html#toUpper>`_ for a version that works for any Unicode - ## character. - ## - ## **Deprecated since version 0.15.0**: use ``toUpperAscii`` instead. - toUpperAscii(c) - -proc toUpper*(s: string): string {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuToUpperStr".} = - ## Converts `s` into upper case. - ## - ## This works only for the letters ``A-Z``. See `unicode.toUpper - ## <unicode.html#toUpper>`_ for a version that works for any Unicode - ## character. - ## - ## **Deprecated since version 0.15.0**: use ``toUpperAscii`` instead. - toUpperAscii(s) - -proc capitalize*(s: string): string {.noSideEffect, procvar, - rtl, deprecated, extern: "nsuCapitalize".} = - ## Converts the first character of `s` into upper case. - ## - ## This works only for the letters ``A-Z``. - ## - ## **Deprecated since version 0.15.0**: use ``capitalizeAscii`` instead. - capitalizeAscii(s) + if s.len == 0: result = "" + else: result = toUpperAscii(s[0]) & substr(s, 1) proc normalize*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuNormalize".} = ## Normalizes the string `s`. ## - ## That means to convert it to lower case and remove any '_'. This is needed - ## for Nim identifiers for example. + ## That means to convert it to lower case and remove any '_'. This + ## should NOT be used to normalize Nim identifier names. result = newString(s.len) var j = 0 for i in 0..len(s) - 1: @@ -418,8 +293,10 @@ proc cmpIgnoreCase*(a, b: string): int {.noSideEffect, proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect, rtl, extern: "nsuCmpIgnoreStyle", procvar.} = - ## Compares two strings normalized (i.e. case and - ## underscores do not matter). Returns: + ## Semantically the same as ``cmp(normalize(a), normalize(b))``. It + ## is just optimized to not allocate temporary strings. This should + ## NOT be used to compare Nim identifier names. use `macros.eqIdent` + ## for that. Returns: ## ## | 0 iff a == b ## | < 0 iff a < b @@ -427,28 +304,37 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect, var i = 0 var j = 0 while true: - while a[i] == '_': inc(i) - while b[j] == '_': inc(j) # BUGFIX: typo - var aa = toLowerAscii(a[i]) - var bb = toLowerAscii(b[j]) + while i < a.len and a[i] == '_': inc i + while j < b.len and b[j] == '_': inc j + var aa = if i < a.len: toLowerAscii(a[i]) else: '\0' + var bb = if j < b.len: toLowerAscii(b[j]) else: '\0' result = ord(aa) - ord(bb) - if result != 0 or aa == '\0': break - inc(i) - inc(j) - + if result != 0: return result + # the characters are identical: + if i >= a.len: + # both cursors at the end: + if j >= b.len: return 0 + # not yet at the end of 'b': + return -1 + elif j >= b.len: + return 1 + inc i + inc j proc strip*(s: string, leading = true, trailing = true, chars: set[char] = Whitespace): string {.noSideEffect, rtl, extern: "nsuStrip".} = - ## Strips `chars` from `s` and returns the resulting string. + ## Strips leading or trailing `chars` from `s` and returns + ## the resulting string. ## ## If `leading` is true, leading `chars` are stripped. ## If `trailing` is true, trailing `chars` are stripped. + ## If both are false, the string is returned unchanged. var first = 0 last = len(s)-1 if leading: - while s[first] in chars: inc(first) + while first <= last and s[first] in chars: inc(first) if trailing: while last >= 0 and s[last] in chars: dec(last) result = substr(s, first, last) @@ -464,15 +350,14 @@ proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} = result[i] = chr(val mod 8 + ord('0')) val = val div 8 -proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrEmpty".} = +proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, + extern: "nsuIsNilOrEmpty", + deprecated: "use 'x.len == 0' instead".} = ## Checks if `s` is nil or empty. result = len(s) == 0 proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrWhitespace".} = ## Checks if `s` is nil or consists entirely of whitespace characters. - if len(s) == 0: - return true - result = true for c in s: if not c.isSpaceAscii(): @@ -483,7 +368,6 @@ proc substrEq(s: string, pos: int, substr: string): bool = var length = substr.len while i < length and s[pos+i] == substr[i]: inc i - return i == length # --------- Private templates for different split separators ----------- @@ -517,7 +401,7 @@ template oldSplit(s, seps, maxsplit) = var splits = maxsplit assert(not ('\0' in seps)) while last < len(s): - while s[last] in seps: inc(last) + while last < len(s) and s[last] in seps: inc(last) var first = last while last < len(s) and s[last] notin seps: inc(last) if first <= last-1: @@ -568,10 +452,7 @@ iterator split*(s: string, seps: set[char] = Whitespace, ## "08" ## "08.398990" ## - when defined(nimOldSplit): - oldSplit(s, seps, maxsplit) - else: - splitCommon(s, seps, maxsplit, 1) + splitCommon(s, seps, maxsplit, 1) iterator splitWhitespace*(s: string, maxsplit: int = -1): string = ## Splits the string ``s`` at whitespace stripping leading and trailing @@ -657,7 +538,6 @@ iterator split*(s: string, sep: string, maxsplit: int = -1): string = ## "is" ## "corrupted" ## - splitCommon(s, sep, maxsplit, sep.len) template rsplitCommon(s, sep, maxsplit, sepLen) = @@ -667,29 +547,21 @@ template rsplitCommon(s, sep, maxsplit, sepLen) = first = last splits = maxsplit startPos = 0 - # go to -1 in order to get separators at the beginning while first >= -1: while first >= 0 and not stringHasSep(s, first, sep): dec(first) - if splits == 0: # No more splits means set first to the beginning first = -1 - if first == -1: startPos = 0 else: startPos = first + sepLen - yield substr(s, startPos, last) - - if splits == 0: - break - + if splits == 0: break dec(splits) dec(first) - last = first iterator rsplit*(s: string, seps: set[char] = Whitespace, @@ -709,7 +581,6 @@ iterator rsplit*(s: string, seps: set[char] = Whitespace, ## "foo" ## ## Substrings are separated from the right by the set of chars `seps` - rsplitCommon(s, seps, maxsplit, 1) iterator rsplit*(s: string, sep: char, @@ -750,12 +621,13 @@ iterator rsplit*(s: string, sep: string, maxsplit: int = -1, ## Substrings are separated from the right by the string `sep` rsplitCommon(s, sep, maxsplit, sep.len) -iterator splitLines*(s: string): string = +iterator splitLines*(s: string, keepEol = false): string = ## Splits the string `s` into its containing lines. ## ## Every `character literal <manual.html#character-literals>`_ newline ## combination (CR, LF, CR-LF) is supported. The result strings contain no - ## trailing ``\n``. + ## trailing end of line characters unless parameter ``keepEol`` is set to + ## ``true``. ## ## Example: ## @@ -775,22 +647,30 @@ iterator splitLines*(s: string): string = ## "" var first = 0 var last = 0 + var eolpos = 0 while true: - while s[last] notin {'\0', '\c', '\l'}: inc(last) - yield substr(s, first, last-1) - # skip newlines: - if s[last] == '\l': inc(last) - elif s[last] == '\c': - inc(last) + while last < s.len and s[last] notin {'\c', '\l'}: inc(last) + + eolpos = last + if last < s.len: if s[last] == '\l': inc(last) - else: break # was '\0' + elif s[last] == '\c': + inc(last) + if last < s.len and s[last] == '\l': inc(last) + + yield substr(s, first, if keepEol: last-1 else: eolpos-1) + + # no eol characters consumed means that the string is over + if eolpos == last: + break + first = last -proc splitLines*(s: string): seq[string] {.noSideEffect, +proc splitLines*(s: string, keepEol = false): seq[string] {.noSideEffect, rtl, extern: "nsuSplitLines".} = ## The same as the `splitLines <#splitLines.i,string>`_ iterator, but is a ## proc that returns a sequence of substrings. - accumulateResult(splitLines(s)) + accumulateResult(splitLines(s, keepEol=keepEol)) proc countLines*(s: string): int {.noSideEffect, rtl, extern: "nsuCountLines".} = @@ -808,7 +688,7 @@ proc countLines*(s: string): int {.noSideEffect, while i < s.len: case s[i] of '\c': - if s[i+1] == '\l': inc i + if i+1 < s.len and s[i+1] == '\l': inc i inc result of '\l': inc result else: discard @@ -940,7 +820,7 @@ proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, # handle negative overflow if n == 0 and x < 0: n = -1 -proc toHex*[T](x: T): string = +proc toHex*[T: SomeInteger](x: T): string = ## Shortcut for ``toHex(x, T.sizeOf * 2)`` toHex(BiggestInt(x), T.sizeOf * 2) @@ -974,7 +854,7 @@ proc parseInt*(s: string): int {.noSideEffect, procvar, ## Parses a decimal integer value contained in `s`. ## ## If `s` is not a valid integer, `ValueError` is raised. - var L = parseutils.parseInt(s, result, 0) + let L = parseutils.parseInt(s, result, 0) if L != s.len or L == 0: raise newException(ValueError, "invalid integer: " & s) @@ -983,7 +863,7 @@ proc parseBiggestInt*(s: string): BiggestInt {.noSideEffect, procvar, ## Parses a decimal integer value contained in `s`. ## ## If `s` is not a valid integer, `ValueError` is raised. - var L = parseutils.parseBiggestInt(s, result, 0) + let L = parseutils.parseBiggestInt(s, result, 0) if L != s.len or L == 0: raise newException(ValueError, "invalid integer: " & s) @@ -992,7 +872,7 @@ proc parseUInt*(s: string): uint {.noSideEffect, procvar, ## Parses a decimal unsigned integer value contained in `s`. ## ## If `s` is not a valid integer, `ValueError` is raised. - var L = parseutils.parseUInt(s, result, 0) + let L = parseutils.parseUInt(s, result, 0) if L != s.len or L == 0: raise newException(ValueError, "invalid unsigned integer: " & s) @@ -1001,7 +881,7 @@ proc parseBiggestUInt*(s: string): BiggestUInt {.noSideEffect, procvar, ## Parses a decimal unsigned integer value contained in `s`. ## ## If `s` is not a valid integer, `ValueError` is raised. - var L = parseutils.parseBiggestUInt(s, result, 0) + let L = parseutils.parseBiggestUInt(s, result, 0) if L != s.len or L == 0: raise newException(ValueError, "invalid unsigned integer: " & s) @@ -1010,34 +890,42 @@ proc parseFloat*(s: string): float {.noSideEffect, procvar, ## Parses a decimal floating point value contained in `s`. If `s` is not ## a valid floating point number, `ValueError` is raised. ``NAN``, ## ``INF``, ``-INF`` are also supported (case insensitive comparison). - var L = parseutils.parseFloat(s, result, 0) + let L = parseutils.parseFloat(s, result, 0) if L != s.len or L == 0: raise newException(ValueError, "invalid float: " & s) +proc parseBinInt*(s: string): int {.noSideEffect, procvar, + rtl, extern: "nsuParseBinInt".} = + ## Parses a binary integer value contained in `s`. + ## + ## If `s` is not a valid binary integer, `ValueError` is raised. `s` can have + ## one of the following optional prefixes: ``0b``, ``0B``. Underscores within + ## `s` are ignored. + let L = parseutils.parseBin(s, result, 0) + if L != s.len or L == 0: + raise newException(ValueError, "invalid binary integer: " & s) + +proc parseOctInt*(s: string): int {.noSideEffect, + rtl, extern: "nsuParseOctInt".} = + ## Parses an octal integer value contained in `s`. + ## + ## If `s` is not a valid oct integer, `ValueError` is raised. `s` can have one + ## of the following optional prefixes: ``0o``, ``0O``. Underscores within + ## `s` are ignored. + let L = parseutils.parseOct(s, result, 0) + if L != s.len or L == 0: + raise newException(ValueError, "invalid oct integer: " & s) + proc parseHexInt*(s: string): int {.noSideEffect, procvar, rtl, extern: "nsuParseHexInt".} = ## Parses a hexadecimal integer value contained in `s`. ## - ## If `s` is not a valid integer, `ValueError` is raised. `s` can have one + ## If `s` is not a valid hex integer, `ValueError` is raised. `s` can have one ## of the following optional prefixes: ``0x``, ``0X``, ``#``. Underscores ## within `s` are ignored. - var i = 0 - if s[i] == '0' and (s[i+1] == 'x' or s[i+1] == 'X'): inc(i, 2) - elif s[i] == '#': inc(i) - while true: - case s[i] - of '_': inc(i) - of '0'..'9': - result = result shl 4 or (ord(s[i]) - ord('0')) - inc(i) - of 'a'..'f': - result = result shl 4 or (ord(s[i]) - ord('a') + 10) - inc(i) - of 'A'..'F': - result = result shl 4 or (ord(s[i]) - ord('A') + 10) - inc(i) - of '\0': break - else: raise newException(ValueError, "invalid integer: " & s) + let L = parseutils.parseHex(s, result, 0) + if L != s.len or L == 0: + raise newException(ValueError, "invalid hex integer: " & s) proc generateHexCharToValueMap(): string = ## Generate a string to map a hex digit to uint value @@ -1145,14 +1033,6 @@ template spaces*(n: Natural): string = repeat(' ', n) ## echo text1 & spaces(max(0, width - text1.len)) & "|" ## echo text2 & spaces(max(0, width - text2.len)) & "|" -proc repeatChar*(count: Natural, c: char = ' '): string {.deprecated.} = - ## deprecated: use repeat() or spaces() - repeat(c, count) - -proc repeatStr*(count: Natural, s: string): string {.deprecated.} = - ## deprecated: use repeat(string, count) or string.repeat(count) - repeat(s, count) - proc align*(s: string, count: Natural, padding = ' '): string {. noSideEffect, rtl, extern: "nsuAlignString".} = ## Aligns a string `s` with `padding`, so that it is of length `count`. @@ -1223,7 +1103,7 @@ iterator tokenize*(s: string, seps: set[char] = Whitespace): tuple[ var i = 0 while true: var j = i - var isSep = s[j] in seps + var isSep = j < s.len and s[j] in seps while j < s.len and (s[j] in seps) == isSep: inc(j) if j > i: yield (substr(s, i, j-1), isSep) @@ -1248,7 +1128,7 @@ proc wordWrap*(s: string, maxLineWidth = 80, if len(word) > spaceLeft: if splitLongWords and len(word) > maxLineWidth: result.add(substr(word, 0, spaceLeft-1)) - var w = spaceLeft+1 + var w = spaceLeft var wordLeft = len(word) - spaceLeft while wordLeft > 0: result.add(newLine) @@ -1294,7 +1174,7 @@ proc unindent*(s: string, count: Natural, padding: string = " "): string var indentCount = 0 for j in 0..<count.int: indentCount.inc - if line[j .. j + padding.len-1] != padding: + if j + padding.len-1 >= line.len or line[j .. j + padding.len-1] != padding: indentCount = j break result.add(line[indentCount*padding.len .. ^1]) @@ -1322,13 +1202,13 @@ proc startsWith*(s, prefix: string): bool {.noSideEffect, ## If ``prefix == ""`` true is returned. var i = 0 while true: - if prefix[i] == '\0': return true - if s[i] != prefix[i]: return false + if i >= prefix.len: return true + if i >= s.len or s[i] != prefix[i]: return false inc(i) proc startsWith*(s: string, prefix: char): bool {.noSideEffect, inline.} = ## Returns true iff ``s`` starts with ``prefix``. - result = s[0] == prefix + result = s.len > 0 and s[0] == prefix proc endsWith*(s, suffix: string): bool {.noSideEffect, rtl, extern: "nsuEndsWith".} = @@ -1340,11 +1220,11 @@ proc endsWith*(s, suffix: string): bool {.noSideEffect, while i+j <% s.len: if s[i+j] != suffix[i]: return false inc(i) - if suffix[i] == '\0': return true + if i >= suffix.len: return true proc endsWith*(s: string, suffix: char): bool {.noSideEffect, inline.} = ## Returns true iff ``s`` ends with ``suffix``. - result = s[s.high] == suffix + result = s.len > 0 and s[s.high] == suffix proc continuesWith*(s, substr: string, start: Natural): bool {.noSideEffect, rtl, extern: "nsuContinuesWith".} = @@ -1353,8 +1233,8 @@ proc continuesWith*(s, substr: string, start: Natural): bool {.noSideEffect, ## If ``substr == ""`` true is returned. var i = 0 while true: - if substr[i] == '\0': return true - if s[i+start] != substr[i]: return false + if i >= substr.len: return true + if i+start >= s.len or s[i+start] != substr[i]: return false inc(i) proc addSep*(dest: var string, sep = ", ", startLen: Natural = 0) @@ -1430,21 +1310,20 @@ proc initSkipTable*(a: var SkipTable, sub: string) {.noSideEffect, rtl, extern: "nsuInitSkipTable".} = ## Preprocess table `a` for `sub`. let m = len(sub) - let m1 = m + 1 var i = 0 while i <= 0xff-7: - a[chr(i + 0)] = m1 - a[chr(i + 1)] = m1 - a[chr(i + 2)] = m1 - a[chr(i + 3)] = m1 - a[chr(i + 4)] = m1 - a[chr(i + 5)] = m1 - a[chr(i + 6)] = m1 - a[chr(i + 7)] = m1 + a[chr(i + 0)] = m + a[chr(i + 1)] = m + a[chr(i + 2)] = m + a[chr(i + 3)] = m + a[chr(i + 4)] = m + a[chr(i + 5)] = m + a[chr(i + 6)] = m + a[chr(i + 7)] = m i += 8 - for i in 0..m-1: - a[sub[i]] = m-i + for i in 0 ..< m - 1: + a[sub[i]] = m - 1 - i proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last: Natural = 0): int {.noSideEffect, rtl, extern: "nsuFindStrA".} = @@ -1452,18 +1331,29 @@ proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last: Natural = 0): ## If `last` is unspecified, it defaults to `s.high`. ## ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + let last = if last==0: s.high else: last - m = len(sub) - n = last + 1 - # search: - var j = start - while j <= n - m: - block match: - for k in 0..m-1: - if sub[k] != s[k+j]: break match - return j - inc(j, a[s[j+m]]) + sLen = last - start + 1 + subLast = sub.len - 1 + + if subLast == -1: + # this was an empty needle string, + # we count this as match in the first possible position: + return start + + # This is an implementation of the Boyer-Moore Horspool algorithms + # https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%E2%80%93Horspool_algorithm + var skip = start + + while last - skip >= subLast: + var i = subLast + while s[skip + i] == sub[i]: + if i == 0: + return skip + dec i + inc skip, a[s[skip + subLast]] + return -1 when not (defined(js) or defined(nimdoc) or defined(nimscript)): @@ -1485,9 +1375,11 @@ proc find*(s: string, sub: char, start: Natural = 0, last: Natural = 0): int {.n if sub == s[i]: return i else: when hasCStringBuiltin: - let found = c_memchr(s[start].unsafeAddr, sub, last-start+1) - if not found.isNil: - return cast[ByteAddress](found) -% cast[ByteAddress](s.cstring) + let L = last-start+1 + if L > 0: + let found = c_memchr(s[start].unsafeAddr, sub, L) + if not found.isNil: + return cast[ByteAddress](found) -% cast[ByteAddress](s.cstring) else: for i in start..last: if sub == s[i]: return i @@ -1499,12 +1391,8 @@ proc find*(s, sub: string, start: Natural = 0, last: Natural = 0): int {.noSideE ## If `last` is unspecified, it defaults to `s.high`. ## ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. - if sub.len > s.len: - return -1 - - if sub.len == 1: - return find(s, sub[0], start, last) - + if sub.len > s.len: return -1 + if sub.len == 1: return find(s, sub[0], start, last) var a {.noinit.}: SkipTable initSkipTable(a, sub) result = find(a, s, sub, start, last) @@ -1561,18 +1449,14 @@ proc center*(s: string, width: int, fillChar: char = ' '): string {. ## ## The original string is returned if `width` is less than or equal ## to `s.len`. - if width <= s.len: - return s - + if width <= s.len: return s result = newString(width) - # Left padding will be one fillChar # smaller if there are an odd number # of characters let charsLeft = (width - s.len) leftPadding = charsLeft div 2 - for i in 0 ..< width: if i >= leftPadding and i < leftPadding + s.len: # we are where the string should be located @@ -1590,27 +1474,22 @@ proc count*(s: string, sub: string, overlapping: bool = false): int {. var i = 0 while true: i = s.find(sub, i) - if i < 0: - break - if overlapping: - inc i - else: - i += sub.len + if i < 0: break + if overlapping: inc i + else: i += sub.len inc result proc count*(s: string, sub: char): int {.noSideEffect, rtl, extern: "nsuCountChar".} = ## Count the occurrences of the character `sub` in the string `s`. for c in s: - if c == sub: - inc result + if c == sub: inc result proc count*(s: string, subs: set[char]): int {.noSideEffect, rtl, extern: "nsuCountCharSet".} = ## Count the occurrences of the group of character `subs` in the string `s`. for c in s: - if c in subs: - inc result + if c in subs: inc result proc quoteIfContainsWhite*(s: string): string {.deprecated.} = ## Returns ``'"' & s & '"'`` if `s` contains a space and does not @@ -1618,10 +1497,8 @@ proc quoteIfContainsWhite*(s: string): string {.deprecated.} = ## ## **DEPRECATED** as it was confused for shell quoting function. For this ## application use `osproc.quoteShell <osproc.html#quoteShell>`_. - if find(s, {' ', '\t'}) >= 0 and s[0] != '"': - result = '"' & s & '"' - else: - result = s + if find(s, {' ', '\t'}) >= 0 and s[0] != '"': result = '"' & s & '"' + else: result = s proc contains*(s: string, c: char): bool {.noSideEffect.} = ## Same as ``find(s, c) >= 0``. @@ -1638,19 +1515,41 @@ proc contains*(s: string, chars: set[char]): bool {.noSideEffect.} = proc replace*(s, sub: string, by = ""): string {.noSideEffect, rtl, extern: "nsuReplaceStr".} = ## Replaces `sub` in `s` by the string `by`. - var a {.noinit.}: SkipTable result = "" - initSkipTable(a, sub) - let last = s.high - var i = 0 - while true: - var j = find(a, s, sub, i, last) - if j < 0: break - add result, substr(s, i, j - 1) + let subLen = sub.len + if subLen == 0: + for c in s: + add result, by + add result, c add result, by - i = j + len(sub) - # copy the rest: - add result, substr(s, i) + return + elif subLen == 1: + # when the pattern is a single char, we use a faster + # char-based search that doesn't need a skip table: + let c = sub[0] + let last = s.high + var i = 0 + while true: + let j = find(s, c, i, last) + if j < 0: break + add result, substr(s, i, j - 1) + add result, by + i = j + subLen + # copy the rest: + add result, substr(s, i) + else: + var a {.noinit.}: SkipTable + initSkipTable(a, sub) + let last = s.high + var i = 0 + while true: + let j = find(a, s, sub, i, last) + if j < 0: break + add result, substr(s, i, j - 1) + add result, by + i = j + subLen + # copy the rest: + add result, substr(s, i) proc replace*(s: string, sub, by: char): string {.noSideEffect, rtl, extern: "nsuReplaceChar".} = @@ -1671,12 +1570,14 @@ proc replaceWord*(s, sub: string, by = ""): string {.noSideEffect, ## Each occurrence of `sub` has to be surrounded by word boundaries ## (comparable to ``\\w`` in regular expressions), otherwise it is not ## replaced. + if sub.len == 0: return s const wordChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'} var a {.noinit.}: SkipTable result = "" initSkipTable(a, sub) var i = 0 let last = s.high + let sublen = max(sub.len, 1) while true: var j = find(a, s, sub, i, last) if j < 0: break @@ -1685,7 +1586,7 @@ proc replaceWord*(s, sub: string, by = ""): string {.noSideEffect, (j+sub.len >= s.len or s[j+sub.len] notin wordChars): add result, substr(s, i, j - 1) add result, by - i = j + len(sub) + i = j + sublen else: add result, substr(s, i, j) i = j + 1 @@ -1696,9 +1597,8 @@ proc multiReplace*(s: string, replacements: varargs[(string, string)]): string { ## Same as replace, but specialized for doing multiple replacements in a single ## pass through the input string. ## - ## Calling replace multiple times after each other is inefficient and result in too many allocations - ## follwed by immediate deallocations as portions of the string gets replaced. - ## multiReplace performs all replacements in a single pass. + ## multiReplace performs all replacements in a single pass, this means it can be used + ## to swap the occurences of "a" and "b", for instance. ## ## If the resulting string is not longer than the original input string, only a single ## memory allocation is required. @@ -1737,24 +1637,6 @@ proc delete*(s: var string, first, last: int) {.noSideEffect, inc(j) setLen(s, newLen) -proc parseOctInt*(s: string): int {.noSideEffect, - rtl, extern: "nsuParseOctInt".} = - ## Parses an octal integer value contained in `s`. - ## - ## If `s` is not a valid integer, `ValueError` is raised. `s` can have one - ## of the following optional prefixes: ``0o``, ``0O``. Underscores within - ## `s` are ignored. - var i = 0 - if s[i] == '0' and (s[i+1] == 'o' or s[i+1] == 'O'): inc(i, 2) - while true: - case s[i] - of '_': inc(i) - of '0'..'7': - result = result shl 3 or (ord(s[i]) - ord('0')) - inc(i) - of '\0': break - else: raise newException(ValueError, "invalid integer: " & s) - proc toOct*(x: BiggestInt, len: Positive): string {.noSideEffect, rtl, extern: "nsuToOct".} = ## Converts `x` into its octal representation. @@ -1819,7 +1701,14 @@ proc escape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, result = newStringOfCap(s.len + s.len shr 2) result.add(prefix) for c in items(s): - result.addEscapedChar(c) + case c + of '\0'..'\31', '\127'..'\255': + add(result, "\\x") + add(result, toHex(ord(c), 2)) + of '\\': add(result, "\\\\") + of '\'': add(result, "\\'") + of '\"': add(result, "\\\"") + else: add(result, c) add(result, suffix) proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, @@ -1835,11 +1724,13 @@ proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, var i = prefix.len if not s.startsWith(prefix): raise newException(ValueError, - "String does not start with a prefix of: " & prefix) + "String does not start with: " & prefix) while true: - if i == s.len-suffix.len: break - case s[i] - of '\\': + if i >= s.len-suffix.len: break + if s[i] == '\\': + if i+1 >= s.len: + result.add('\\') + break case s[i+1]: of 'x': inc i, 2 @@ -1853,15 +1744,15 @@ proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, result.add('\'') of '\"': result.add('\"') - else: result.add("\\" & s[i+1]) - inc(i) - of '\0': break + else: + result.add("\\" & s[i+1]) + inc(i, 2) else: result.add(s[i]) - inc(i) + inc(i) if not s.endsWith(suffix): raise newException(ValueError, - "String does not end with a suffix of: " & suffix) + "String does not end in: " & suffix) proc validIdentifier*(s: string): bool {.noSideEffect, rtl, extern: "nsuValidIdentifier".} = @@ -1871,7 +1762,7 @@ proc validIdentifier*(s: string): bool {.noSideEffect, ## and is followed by any number of characters of the set `IdentChars`. runnableExamples: doAssert "abc_def08".validIdentifier - if s[0] in IdentStartChars: + if s.len > 0 and s[0] in IdentStartChars: for i in 1..s.len-1: if s[i] notin IdentChars: return false return true @@ -1890,7 +1781,7 @@ proc editDistance*(a, b: string): int {.noSideEffect, # strip common prefix: var s = 0 - while a[s] == b[s] and a[s] != '\0': + while s < len1 and a[s] == b[s]: inc(s) dec(len1) dec(len2) @@ -1963,8 +1854,6 @@ proc editDistance*(a, b: string): int {.noSideEffect, if x > c3: x = c3 row[p] = x result = row[e] - #dealloc(row) - # floating point formating: when not defined(js): @@ -1994,6 +1883,10 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, ## ## If ``precision == -1``, it tries to format it nicely. when defined(js): + var precision = precision + if precision == -1: + # use the same default precision as c_sprintf + precision = 6 var res: cstring case format of ffDefault: @@ -2003,6 +1896,9 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, of ffScientific: {.emit: "`res` = `f`.toExponential(`precision`);".} result = $res + if 1.0 / f == -Inf: + # JavaScript removes the "-" from negative Zero, add it back here + result = "-" & $res for i in 0 ..< result.len: # Depending on the locale either dot or comma is produced, # but nothing else is possible: @@ -2073,7 +1969,7 @@ proc trimZeros*(x: var string) {.noSideEffect.} = var spl: seq[string] if x.contains('.') or x.contains(','): if x.contains('e'): - spl= x.split('e') + spl = x.split('e') x = spl[0] while x[x.high] == '0': x.setLen(x.len-1) @@ -2142,12 +2038,13 @@ proc formatEng*(f: BiggestFloat, precision: range[0..32] = 10, trim: bool = true, siPrefix: bool = false, - unit: string = nil, - decimalSep = '.'): string {.noSideEffect.} = + unit: string = "", + decimalSep = '.', + useUnitSpace = false): string {.noSideEffect.} = ## Converts a floating point value `f` to a string using engineering notation. ## ## Numbers in of the range -1000.0<f<1000.0 will be formatted without an - ## exponent. Numbers outside of this range will be formatted as a + ## exponent. Numbers outside of this range will be formatted as a ## significand in the range -1000.0<f<1000.0 and an exponent that will always ## be an integer multiple of 3, corresponding with the SI prefix scale k, M, ## G, T etc for numbers with an absolute value greater than 1 and m, μ, n, p @@ -2155,7 +2052,7 @@ proc formatEng*(f: BiggestFloat, ## ## The default configuration (`trim=true` and `precision=10`) shows the ## **shortest** form that precisely (up to a maximum of 10 decimal places) - ## displays the value. For example, 4.100000 will be displayed as 4.1 (which + ## displays the value. For example, 4.100000 will be displayed as 4.1 (which ## is mathematically identical) whereas 4.1000003 will be displayed as ## 4.1000003. ## @@ -2175,15 +2072,15 @@ proc formatEng*(f: BiggestFloat, ## formatEng(-52731234, 2) == "-52.73e6" ## ## If `siPrefix` is set to true, the number will be displayed with the SI - ## prefix corresponding to the exponent. For example 4100 will be displayed - ## as "4.1 k" instead of "4.1e3". Note that `u` is used for micro- in place - ## of the greek letter mu (μ) as per ISO 2955. Numbers with an absolute + ## prefix corresponding to the exponent. For example 4100 will be displayed + ## as "4.1 k" instead of "4.1e3". Note that `u` is used for micro- in place + ## of the greek letter mu (μ) as per ISO 2955. Numbers with an absolute ## value outside of the range 1e-18<f<1000e18 (1a<f<1000E) will be displayed ## with an exponent rather than an SI prefix, regardless of whether ## `siPrefix` is true. ## - ## If `unit` is not nil, the provided unit will be appended to the string - ## (with a space as required by the SI standard). This behaviour is slightly + ## If `useUnitSpace` is true, the provided unit will be appended to the string + ## (with a space as required by the SI standard). This behaviour is slightly ## different to appending the unit to the result as the location of the space ## is altered depending on whether there is an exponent. ## @@ -2197,7 +2094,7 @@ proc formatEng*(f: BiggestFloat, ## formatEng(4100, siPrefix=true, unit="") == "4.1 k" ## formatEng(4100) == "4.1e3" ## formatEng(4100, unit="V") == "4.1e3 V" - ## formatEng(4100, unit="") == "4.1e3 " # Space with unit="" + ## formatEng(4100, unit="", useUnitSpace=true) == "4.1e3 " # Space with useUnitSpace=true ## ## `decimalSep` is used as the decimal separator. var @@ -2265,10 +2162,9 @@ proc formatEng*(f: BiggestFloat, if p != ' ': suffix = " " & p exponent = 0 # Exponent replaced by SI prefix - if suffix == "" and unit != nil: + if suffix == "" and useUnitSpace: suffix = " " - if unit != nil: - suffix &= unit + suffix &= unit if exponent != 0: result &= "e" & $exponent result &= suffix @@ -2291,11 +2187,10 @@ proc addf*(s: var string, formatstr: string, a: varargs[string, `$`]) {. var i = 0 var num = 0 while i < len(formatstr): - if formatstr[i] == '$': - case formatstr[i+1] # again we use the fact that strings - # are zero-terminated here + if formatstr[i] == '$' and i+1 < len(formatstr): + case formatstr[i+1] of '#': - if num >% a.high: invalidFormatString() + if num > a.high: invalidFormatString() add s, a[num] inc i, 2 inc num @@ -2307,11 +2202,11 @@ proc addf*(s: var string, formatstr: string, a: varargs[string, `$`]) {. inc(i) # skip $ var negative = formatstr[i] == '-' if negative: inc i - while formatstr[i] in Digits: + while i < formatstr.len and formatstr[i] in Digits: j = j * 10 + ord(formatstr[i]) - ord('0') inc(i) let idx = if not negative: j-1 else: a.len-j - if idx >% a.high: invalidFormatString() + if idx < 0 or idx > a.high: invalidFormatString() add s, a[idx] of '{': var j = i+2 @@ -2319,7 +2214,7 @@ proc addf*(s: var string, formatstr: string, a: varargs[string, `$`]) {. var negative = formatstr[j] == '-' if negative: inc j var isNumber = 0 - while formatstr[j] notin {'\0', '}'}: + while j < formatstr.len and formatstr[j] notin {'\0', '}'}: if formatstr[j] in Digits: k = k * 10 + ord(formatstr[j]) - ord('0') if isNumber == 0: isNumber = 1 @@ -2328,7 +2223,7 @@ proc addf*(s: var string, formatstr: string, a: varargs[string, `$`]) {. inc(j) if isNumber == 1: let idx = if not negative: k-1 else: a.len-k - if idx >% a.high: invalidFormatString() + if idx < 0 or idx > a.high: invalidFormatString() add s, a[idx] else: var x = findNormalized(substr(formatstr, i+2, j-1), a) @@ -2337,7 +2232,7 @@ proc addf*(s: var string, formatstr: string, a: varargs[string, `$`]) {. i = j+1 of 'a'..'z', 'A'..'Z', '\128'..'\255', '_': var j = i+1 - while formatstr[j] in PatternChars: inc(j) + while j < formatstr.len and formatstr[j] in PatternChars: inc(j) var x = findNormalized(substr(formatstr, i+1, j-1), a) if x >= 0 and x < high(a): add s, a[x+1] else: invalidFormatString() @@ -2496,234 +2391,260 @@ proc removePrefix*(s: var string, prefix: string) {. s.delete(0, prefix.len - 1) when isMainModule: - doAssert align("abc", 4) == " abc" - doAssert align("a", 0) == "a" - doAssert align("1232", 6) == " 1232" - doAssert align("1232", 6, '#') == "##1232" - - doAssert alignLeft("abc", 4) == "abc " - doAssert alignLeft("a", 0) == "a" - doAssert alignLeft("1232", 6) == "1232 " - doAssert alignLeft("1232", 6, '#') == "1232##" - - let - inp = """ this is a long text -- muchlongerthan10chars and here - it goes""" - outp = " this is a\nlong text\n--\nmuchlongerthan10chars\nand here\nit goes" - doAssert wordWrap(inp, 10, false) == outp - - doAssert formatBiggestFloat(1234.567, ffDecimal, -1) == "1234.567000" - doAssert formatBiggestFloat(1234.567, ffDecimal, 0) == "1235." - doAssert formatBiggestFloat(1234.567, ffDecimal, 1) == "1234.6" - doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001" - doAssert formatBiggestFloat(0.00000000001, ffScientific, 1, ',') in - ["1,0e-11", "1,0e-011"] - # bug #6589 - doAssert formatFloat(123.456, ffScientific, precision = -1) == "1.234560e+02" - - doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c" - doAssert "${1}12 ${-1}$2" % ["a", "b"] == "a12 bb" - - block: # formatSize tests - doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" - doAssert formatSize((2.234*1024*1024).int) == "2.234MiB" - doAssert formatSize(4096) == "4KiB" - doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB" - doAssert formatSize(4096, includeSpace=true) == "4 KiB" - doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB" - - doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] == - "The cat eats fish." - - doAssert "-ld a-ldz -ld".replaceWord("-ld") == " a-ldz " - doAssert "-lda-ldz -ld abc".replaceWord("-ld") == "-lda-ldz abc" - - type MyEnum = enum enA, enB, enC, enuD, enE - doAssert parseEnum[MyEnum]("enu_D") == enuD - - doAssert parseEnum("invalid enum value", enC) == enC - - doAssert center("foo", 13) == " foo " - doAssert center("foo", 0) == "foo" - doAssert center("foo", 3, fillChar = 'a') == "foo" - doAssert center("foo", 10, fillChar = '\t') == "\t\t\tfoo\t\t\t\t" - - doAssert count("foofoofoo", "foofoo") == 1 - doAssert count("foofoofoo", "foofoo", overlapping = true) == 2 - doAssert count("foofoofoo", 'f') == 3 - doAssert count("foofoofoobar", {'f','b'}) == 4 - - doAssert strip(" foofoofoo ") == "foofoofoo" - doAssert strip("sfoofoofoos", chars = {'s'}) == "foofoofoo" - doAssert strip("barfoofoofoobar", chars = {'b', 'a', 'r'}) == "foofoofoo" - doAssert strip("stripme but don't strip this stripme", - chars = {'s', 't', 'r', 'i', 'p', 'm', 'e'}) == - " but don't strip this " - doAssert strip("sfoofoofoos", leading = false, chars = {'s'}) == "sfoofoofoo" - doAssert strip("sfoofoofoos", trailing = false, chars = {'s'}) == "foofoofoos" - - doAssert " foo\n bar".indent(4, "Q") == "QQQQ foo\nQQQQ bar" - - doAssert "abba".multiReplace(("a", "b"), ("b", "a")) == "baab" - doAssert "Hello World.".multiReplace(("ello", "ELLO"), ("World.", "PEOPLE!")) == "HELLO PEOPLE!" - doAssert "aaaa".multiReplace(("a", "aa"), ("aa", "bb")) == "aaaaaaaa" - - doAssert isAlphaAscii('r') - doAssert isAlphaAscii('A') - doAssert(not isAlphaAscii('$')) - - doAssert isAlphaAscii("Rasp") - doAssert isAlphaAscii("Args") - doAssert(not isAlphaAscii("$Tomato")) - - doAssert isAlphaNumeric('3') - doAssert isAlphaNumeric('R') - doAssert(not isAlphaNumeric('!')) - - doAssert isAlphaNumeric("34ABc") - doAssert isAlphaNumeric("Rad") - doAssert isAlphaNumeric("1234") - doAssert(not isAlphaNumeric("@nose")) - - doAssert isDigit('3') - doAssert(not isDigit('a')) - doAssert(not isDigit('%')) - - doAssert isDigit("12533") - doAssert(not isDigit("12.33")) - doAssert(not isDigit("A45b")) - - doAssert isSpaceAscii('\t') - doAssert isSpaceAscii('\l') - doAssert(not isSpaceAscii('A')) - - doAssert isSpaceAscii("\t\l \v\r\f") - doAssert isSpaceAscii(" ") - doAssert(not isSpaceAscii("ABc \td")) - - doAssert(isNilOrEmpty("")) - doAssert(isNilOrEmpty(nil)) - doAssert(not isNilOrEmpty("test")) - doAssert(not isNilOrEmpty(" ")) - - doAssert(isNilOrWhitespace("")) - doAssert(isNilOrWhitespace(nil)) - doAssert(isNilOrWhitespace(" ")) - doAssert(isNilOrWhitespace("\t\l \v\r\f")) - doAssert(not isNilOrWhitespace("ABc \td")) - - doAssert isLowerAscii('a') - doAssert isLowerAscii('z') - doAssert(not isLowerAscii('A')) - doAssert(not isLowerAscii('5')) - doAssert(not isLowerAscii('&')) - - doAssert isLowerAscii("abcd") - doAssert(not isLowerAscii("abCD")) - doAssert(not isLowerAscii("33aa")) - - doAssert isUpperAscii('A') - doAssert(not isUpperAscii('b')) - doAssert(not isUpperAscii('5')) - doAssert(not isUpperAscii('%')) - - doAssert isUpperAscii("ABC") - doAssert(not isUpperAscii("AAcc")) - doAssert(not isUpperAscii("A#$")) - - doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"] - doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"] - doAssert rsplit(" foo bar ", seps=Whitespace, maxsplit=1) == @[" foo bar", ""] - doAssert rsplit(":foo:bar", sep=':') == @["", "foo", "bar"] - doAssert rsplit(":foo:bar", sep=':', maxsplit=2) == @["", "foo", "bar"] - doAssert rsplit(":foo:bar", sep=':', maxsplit=3) == @["", "foo", "bar"] - doAssert rsplit("foothebar", sep="the") == @["foo", "bar"] - - doAssert(unescape(r"\x013", "", "") == "\x013") - - doAssert join(["foo", "bar", "baz"]) == "foobarbaz" - doAssert join(@["foo", "bar", "baz"], ", ") == "foo, bar, baz" - doAssert join([1, 2, 3]) == "123" - doAssert join(@[1, 2, 3], ", ") == "1, 2, 3" - - doAssert """~~!!foo + proc nonStaticTests = + doAssert formatBiggestFloat(1234.567, ffDecimal, -1) == "1234.567000" + when not defined(js): + doAssert formatBiggestFloat(1234.567, ffDecimal, 0) == "1235." # <=== bug 8242 + doAssert formatBiggestFloat(1234.567, ffDecimal, 1) == "1234.6" + doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001" + doAssert formatBiggestFloat(0.00000000001, ffScientific, 1, ',') in + ["1,0e-11", "1,0e-011"] + # bug #6589 + when not defined(js): + doAssert formatFloat(123.456, ffScientific, precision = -1) == "1.234560e+02" + + doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c" + doAssert "${1}12 ${-1}$2" % ["a", "b"] == "a12 bb" + + block: # formatSize tests + when not defined(js): + doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" # <=== bug #8231 + doAssert formatSize((2.234*1024*1024).int) == "2.234MiB" + doAssert formatSize(4096) == "4KiB" + doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB" + doAssert formatSize(4096, includeSpace=true) == "4 KiB" + doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB" + + block: # formatEng tests + doAssert formatEng(0, 2, trim=false) == "0.00" + doAssert formatEng(0, 2) == "0" + doAssert formatEng(53, 2, trim=false) == "53.00" + doAssert formatEng(0.053, 2, trim=false) == "53.00e-3" + doAssert formatEng(0.053, 4, trim=false) == "53.0000e-3" + doAssert formatEng(0.053, 4, trim=true) == "53e-3" + doAssert formatEng(0.053, 0) == "53e-3" + doAssert formatEng(52731234) == "52.731234e6" + doAssert formatEng(-52731234) == "-52.731234e6" + doAssert formatEng(52731234, 1) == "52.7e6" + doAssert formatEng(-52731234, 1) == "-52.7e6" + doAssert formatEng(52731234, 1, decimalSep=',') == "52,7e6" + doAssert formatEng(-52731234, 1, decimalSep=',') == "-52,7e6" + + doAssert formatEng(4100, siPrefix=true, unit="V") == "4.1 kV" + doAssert formatEng(4.1, siPrefix=true, unit="V", useUnitSpace=true) == "4.1 V" + doAssert formatEng(4.1, siPrefix=true) == "4.1" # Note lack of space + doAssert formatEng(4100, siPrefix=true) == "4.1 k" + doAssert formatEng(4.1, siPrefix=true, unit="", useUnitSpace=true) == "4.1 " # Includes space + doAssert formatEng(4100, siPrefix=true, unit="") == "4.1 k" + doAssert formatEng(4100) == "4.1e3" + doAssert formatEng(4100, unit="V", useUnitSpace=true) == "4.1e3 V" + doAssert formatEng(4100, unit="", useUnitSpace=true) == "4.1e3 " + # Don't use SI prefix as number is too big + doAssert formatEng(3.1e22, siPrefix=true, unit="a", useUnitSpace=true) == "31e21 a" + # Don't use SI prefix as number is too small + doAssert formatEng(3.1e-25, siPrefix=true, unit="A", useUnitSpace=true) == "310e-27 A" + + proc staticTests = + doAssert align("abc", 4) == " abc" + doAssert align("a", 0) == "a" + doAssert align("1232", 6) == " 1232" + doAssert align("1232", 6, '#') == "##1232" + + doAssert alignLeft("abc", 4) == "abc " + doAssert alignLeft("a", 0) == "a" + doAssert alignLeft("1232", 6) == "1232 " + doAssert alignLeft("1232", 6, '#') == "1232##" + + let + inp = """ this is a long text -- muchlongerthan10chars and here + it goes""" + outp = " this is a\nlong text\n--\nmuchlongerthan10chars\nand here\nit goes" + doAssert wordWrap(inp, 10, false) == outp + + let + longInp = """ThisIsOneVeryLongStringWhichWeWillSplitIntoEightSeparatePartsNow""" + longOutp = "ThisIsOn\neVeryLon\ngStringW\nhichWeWi\nllSplitI\nntoEight\nSeparate\nPartsNow" + doAssert wordWrap(longInp, 8, true) == longOutp + + doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] == + "The cat eats fish." + + doAssert "-ld a-ldz -ld".replaceWord("-ld") == " a-ldz " + doAssert "-lda-ldz -ld abc".replaceWord("-ld") == "-lda-ldz abc" + + doAssert "-lda-ldz -ld abc".replaceWord("") == "-lda-ldz -ld abc" + doAssert "oo".replace("", "abc") == "abcoabcoabc" + + type MyEnum = enum enA, enB, enC, enuD, enE + doAssert parseEnum[MyEnum]("enu_D") == enuD + + doAssert parseEnum("invalid enum value", enC) == enC + + doAssert center("foo", 13) == " foo " + doAssert center("foo", 0) == "foo" + doAssert center("foo", 3, fillChar = 'a') == "foo" + doAssert center("foo", 10, fillChar = '\t') == "\t\t\tfoo\t\t\t\t" + + doAssert count("foofoofoo", "foofoo") == 1 + doAssert count("foofoofoo", "foofoo", overlapping = true) == 2 + doAssert count("foofoofoo", 'f') == 3 + doAssert count("foofoofoobar", {'f','b'}) == 4 + + doAssert strip(" foofoofoo ") == "foofoofoo" + doAssert strip("sfoofoofoos", chars = {'s'}) == "foofoofoo" + doAssert strip("barfoofoofoobar", chars = {'b', 'a', 'r'}) == "foofoofoo" + doAssert strip("stripme but don't strip this stripme", + chars = {'s', 't', 'r', 'i', 'p', 'm', 'e'}) == + " but don't strip this " + doAssert strip("sfoofoofoos", leading = false, chars = {'s'}) == "sfoofoofoo" + doAssert strip("sfoofoofoos", trailing = false, chars = {'s'}) == "foofoofoos" + + doAssert " foo\n bar".indent(4, "Q") == "QQQQ foo\nQQQQ bar" + + doAssert "abba".multiReplace(("a", "b"), ("b", "a")) == "baab" + doAssert "Hello World.".multiReplace(("ello", "ELLO"), ("World.", "PEOPLE!")) == "HELLO PEOPLE!" + doAssert "aaaa".multiReplace(("a", "aa"), ("aa", "bb")) == "aaaaaaaa" + + doAssert isAlphaAscii('r') + doAssert isAlphaAscii('A') + doAssert(not isAlphaAscii('$')) + + doAssert isAlphaAscii("Rasp") + doAssert isAlphaAscii("Args") + doAssert(not isAlphaAscii("$Tomato")) + + doAssert isAlphaNumeric('3') + doAssert isAlphaNumeric('R') + doAssert(not isAlphaNumeric('!')) + + doAssert isAlphaNumeric("34ABc") + doAssert isAlphaNumeric("Rad") + doAssert isAlphaNumeric("1234") + doAssert(not isAlphaNumeric("@nose")) + + doAssert isDigit('3') + doAssert(not isDigit('a')) + doAssert(not isDigit('%')) + + doAssert isDigit("12533") + doAssert(not isDigit("12.33")) + doAssert(not isDigit("A45b")) + + doAssert isSpaceAscii('\t') + doAssert isSpaceAscii('\l') + doAssert(not isSpaceAscii('A')) + + doAssert isSpaceAscii("\t\l \v\r\f") + doAssert isSpaceAscii(" ") + doAssert(not isSpaceAscii("ABc \td")) + + doAssert(isNilOrWhitespace("")) + doAssert(isNilOrWhitespace(" ")) + doAssert(isNilOrWhitespace("\t\l \v\r\f")) + doAssert(not isNilOrWhitespace("ABc \td")) + + doAssert isLowerAscii('a') + doAssert isLowerAscii('z') + doAssert(not isLowerAscii('A')) + doAssert(not isLowerAscii('5')) + doAssert(not isLowerAscii('&')) + doAssert(not isLowerAscii(' ')) + + doAssert isLowerAscii("abcd", false) + doAssert(not isLowerAscii("33aa", false)) + doAssert(not isLowerAscii("a b", false)) + + doAssert(not isLowerAscii("abCD", true)) + doAssert isLowerAscii("33aa", true) + doAssert isLowerAscii("a b", true) + doAssert isLowerAscii("1, 2, 3 go!", true) + doAssert(not isLowerAscii(" ", true)) + doAssert(not isLowerAscii("(*&#@(^#$ ", true)) # None of the string chars are alphabets + + doAssert isUpperAscii('A') + doAssert(not isUpperAscii('b')) + doAssert(not isUpperAscii('5')) + doAssert(not isUpperAscii('%')) + + doAssert isUpperAscii("ABC", false) + doAssert(not isUpperAscii("A#$", false)) + doAssert(not isUpperAscii("A B", false)) + + doAssert(not isUpperAscii("AAcc", true)) + doAssert isUpperAscii("A#$", true) + doAssert isUpperAscii("A B", true) + doAssert isUpperAscii("1, 2, 3 GO!", true) + doAssert(not isUpperAscii(" ", true)) + doAssert(not isUpperAscii("(*&#@(^#$ ", true)) # None of the string chars are alphabets + + doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"] + doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"] + doAssert rsplit(" foo bar ", seps=Whitespace, maxsplit=1) == @[" foo bar", ""] + doAssert rsplit(":foo:bar", sep=':') == @["", "foo", "bar"] + doAssert rsplit(":foo:bar", sep=':', maxsplit=2) == @["", "foo", "bar"] + doAssert rsplit(":foo:bar", sep=':', maxsplit=3) == @["", "foo", "bar"] + doAssert rsplit("foothebar", sep="the") == @["foo", "bar"] + + doAssert(unescape(r"\x013", "", "") == "\x013") + + doAssert join(["foo", "bar", "baz"]) == "foobarbaz" + doAssert join(@["foo", "bar", "baz"], ", ") == "foo, bar, baz" + doAssert join([1, 2, 3]) == "123" + doAssert join(@[1, 2, 3], ", ") == "1, 2, 3" + + doAssert """~~!!foo ~~!!bar ~~!!baz""".unindent(2, "~~!!") == "foo\nbar\nbaz" - doAssert """~~!!foo + doAssert """~~!!foo ~~!!bar ~~!!baz""".unindent(2, "~~!!aa") == "~~!!foo\n~~!!bar\n~~!!baz" - doAssert """~~foo + doAssert """~~foo ~~ bar ~~ baz""".unindent(4, "~") == "foo\n bar\n baz" - doAssert """foo + doAssert """foo bar baz """.unindent(4) == "foo\nbar\nbaz\n" - doAssert """foo + doAssert """foo bar baz """.unindent(2) == "foo\n bar\n baz\n" - doAssert """foo + doAssert """foo bar baz """.unindent(100) == "foo\nbar\nbaz\n" - doAssert """foo + doAssert """foo foo bar """.unindent() == "foo\nfoo\nbar\n" - let s = " this is an example " - let s2 = ":this;is;an:example;;" - - doAssert s.split() == @["", "this", "is", "an", "example", "", ""] - doAssert s2.split(seps={':', ';'}) == @["", "this", "is", "an", "example", "", ""] - doAssert s.split(maxsplit=4) == @["", "this", "is", "an", "example "] - doAssert s.split(' ', maxsplit=1) == @["", "this is an example "] - doAssert s.split(" ", maxsplit=4) == @["", "this", "is", "an", "example "] - - doAssert s.splitWhitespace() == @["this", "is", "an", "example"] - doAssert s.splitWhitespace(maxsplit=1) == @["this", "is an example "] - doAssert s.splitWhitespace(maxsplit=2) == @["this", "is", "an example "] - doAssert s.splitWhitespace(maxsplit=3) == @["this", "is", "an", "example "] - doAssert s.splitWhitespace(maxsplit=4) == @["this", "is", "an", "example"] - - block: # formatEng tests - doAssert formatEng(0, 2, trim=false) == "0.00" - doAssert formatEng(0, 2) == "0" - doAssert formatEng(53, 2, trim=false) == "53.00" - doAssert formatEng(0.053, 2, trim=false) == "53.00e-3" - doAssert formatEng(0.053, 4, trim=false) == "53.0000e-3" - doAssert formatEng(0.053, 4, trim=true) == "53e-3" - doAssert formatEng(0.053, 0) == "53e-3" - doAssert formatEng(52731234) == "52.731234e6" - doAssert formatEng(-52731234) == "-52.731234e6" - doAssert formatEng(52731234, 1) == "52.7e6" - doAssert formatEng(-52731234, 1) == "-52.7e6" - doAssert formatEng(52731234, 1, decimalSep=',') == "52,7e6" - doAssert formatEng(-52731234, 1, decimalSep=',') == "-52,7e6" - - doAssert formatEng(4100, siPrefix=true, unit="V") == "4.1 kV" - doAssert formatEng(4.1, siPrefix=true, unit="V") == "4.1 V" - doAssert formatEng(4.1, siPrefix=true) == "4.1" # Note lack of space - doAssert formatEng(4100, siPrefix=true) == "4.1 k" - doAssert formatEng(4.1, siPrefix=true, unit="") == "4.1 " # Includes space - doAssert formatEng(4100, siPrefix=true, unit="") == "4.1 k" - doAssert formatEng(4100) == "4.1e3" - doAssert formatEng(4100, unit="V") == "4.1e3 V" - doAssert formatEng(4100, unit="") == "4.1e3 " # Space with unit="" - # Don't use SI prefix as number is too big - doAssert formatEng(3.1e22, siPrefix=true, unit="a") == "31e21 a" - # Don't use SI prefix as number is too small - doAssert formatEng(3.1e-25, siPrefix=true, unit="A") == "310e-27 A" - - block: # startsWith / endsWith char tests - var s = "abcdef" - doAssert s.startsWith('a') - doAssert s.startsWith('b') == false - doAssert s.endsWith('f') - doAssert s.endsWith('a') == false - doAssert s.endsWith('\0') == false - - #echo("strutils tests passed") + let s = " this is an example " + let s2 = ":this;is;an:example;;" + + doAssert s.split() == @["", "this", "is", "an", "example", "", ""] + doAssert s2.split(seps={':', ';'}) == @["", "this", "is", "an", "example", "", ""] + doAssert s.split(maxsplit=4) == @["", "this", "is", "an", "example "] + doAssert s.split(' ', maxsplit=1) == @["", "this is an example "] + doAssert s.split(" ", maxsplit=4) == @["", "this", "is", "an", "example "] + + doAssert s.splitWhitespace() == @["this", "is", "an", "example"] + doAssert s.splitWhitespace(maxsplit=1) == @["this", "is an example "] + doAssert s.splitWhitespace(maxsplit=2) == @["this", "is", "an example "] + doAssert s.splitWhitespace(maxsplit=3) == @["this", "is", "an", "example "] + doAssert s.splitWhitespace(maxsplit=4) == @["this", "is", "an", "example"] + + block: # startsWith / endsWith char tests + var s = "abcdef" + doAssert s.startsWith('a') + doAssert s.startsWith('b') == false + doAssert s.endsWith('f') + doAssert s.endsWith('a') == false + doAssert s.endsWith('\0') == false + + #echo("strutils tests passed") + + nonStaticTests() + staticTests() + static: staticTests() diff --git a/lib/pure/subexes.nim b/lib/pure/subexes.nim index 9d807abd4..8149c72cc 100644 --- a/lib/pure/subexes.nim +++ b/lib/pure/subexes.nim @@ -31,8 +31,6 @@ type SubexError* = object of ValueError ## exception that is raised for ## an invalid subex -{.deprecated: [EInvalidSubex: SubexError].} - proc raiseInvalidFormat(msg: string) {.noinline.} = raise newException(SubexError, "invalid format string: " & msg) @@ -44,7 +42,6 @@ type else: f: cstring num, i, lineLen: int -{.deprecated: [TFormatParser: FormatParser].} template call(x: untyped): untyped = p.i = i diff --git a/lib/pure/sugar.nim b/lib/pure/sugar.nim new file mode 100644 index 000000000..8ded552d9 --- /dev/null +++ b/lib/pure/sugar.nim @@ -0,0 +1,238 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Dominik Picheta +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements nice syntactic sugar based on Nim's +## macro system. + +import macros + +proc createProcType(p, b: NimNode): NimNode {.compileTime.} = + #echo treeRepr(p) + #echo treeRepr(b) + result = newNimNode(nnkProcTy) + var formalParams = newNimNode(nnkFormalParams) + + formalParams.add b + + case p.kind + of nnkPar, nnkTupleConstr: + for i in 0 ..< p.len: + let ident = p[i] + var identDefs = newNimNode(nnkIdentDefs) + case ident.kind + of nnkExprColonExpr: + identDefs.add ident[0] + identDefs.add ident[1] + else: + identDefs.add newIdentNode("i" & $i) + identDefs.add(ident) + identDefs.add newEmptyNode() + formalParams.add identDefs + else: + var identDefs = newNimNode(nnkIdentDefs) + identDefs.add newIdentNode("i0") + identDefs.add(p) + identDefs.add newEmptyNode() + formalParams.add identDefs + + result.add formalParams + result.add newEmptyNode() + #echo(treeRepr(result)) + #echo(result.toStrLit()) + +macro `=>`*(p, b: untyped): untyped = + ## Syntax sugar for anonymous procedures. + ## + ## .. code-block:: nim + ## + ## proc passTwoAndTwo(f: (int, int) -> int): int = + ## f(2, 2) + ## + ## passTwoAndTwo((x, y) => x + y) # 4 + + #echo treeRepr(p) + #echo(treeRepr(b)) + var params: seq[NimNode] = @[newIdentNode("auto")] + + case p.kind + of nnkPar, nnkTupleConstr: + for c in children(p): + var identDefs = newNimNode(nnkIdentDefs) + case c.kind + of nnkExprColonExpr: + identDefs.add(c[0]) + identDefs.add(c[1]) + identDefs.add(newEmptyNode()) + of nnkIdent: + identDefs.add(c) + identDefs.add(newIdentNode("auto")) + identDefs.add(newEmptyNode()) + of nnkInfix: + if c[0].kind == nnkIdent and c[0].ident == !"->": + var procTy = createProcType(c[1], c[2]) + params[0] = procTy[0][0] + for i in 1 ..< procTy[0].len: + params.add(procTy[0][i]) + else: + error("Expected proc type (->) got (" & $c[0].ident & ").") + break + else: + echo treeRepr c + error("Incorrect procedure parameter list.") + params.add(identDefs) + of nnkIdent: + var identDefs = newNimNode(nnkIdentDefs) + identDefs.add(p) + identDefs.add(newIdentNode("auto")) + identDefs.add(newEmptyNode()) + params.add(identDefs) + of nnkInfix: + if p[0].kind == nnkIdent and p[0].ident == !"->": + var procTy = createProcType(p[1], p[2]) + params[0] = procTy[0][0] + for i in 1 ..< procTy[0].len: + params.add(procTy[0][i]) + else: + error("Expected proc type (->) got (" & $p[0].ident & ").") + else: + error("Incorrect procedure parameter list.") + result = newProc(params = params, body = b, procType = nnkLambda) + #echo(result.treeRepr) + #echo(result.toStrLit()) + #return result # TODO: Bug? + +macro `->`*(p, b: untyped): untyped = + ## Syntax sugar for procedure types. + ## + ## .. code-block:: nim + ## + ## proc pass2(f: (float, float) -> float): float = + ## f(2, 2) + ## + ## # is the same as: + ## + ## proc pass2(f: proc (x, y: float): float): float = + ## f(2, 2) + + result = createProcType(p, b) + +type ListComprehension = object +var lc*: ListComprehension + +macro `[]`*(lc: ListComprehension, comp, typ: untyped): untyped = + ## List comprehension, returns a sequence. `comp` is the actual list + ## comprehension, for example ``x | (x <- 1..10, x mod 2 == 0)``. `typ` is + ## the type that will be stored inside the result seq. + ## + ## .. code-block:: nim + ## + ## echo lc[x | (x <- 1..10, x mod 2 == 0), int] + ## + ## const n = 20 + ## echo lc[(x,y,z) | (x <- 1..n, y <- x..n, z <- y..n, x*x + y*y == z*z), + ## tuple[a,b,c: int]] + + expectLen(comp, 3) + expectKind(comp, nnkInfix) + expectKind(comp[0], nnkIdent) + assert($comp[0].ident == "|") + + result = newCall( + newDotExpr( + newIdentNode("result"), + newIdentNode("add")), + comp[1]) + + for i in countdown(comp[2].len-1, 0): + let x = comp[2][i] + expectMinLen(x, 1) + if x[0].kind == nnkIdent and $x[0].ident == "<-": + expectLen(x, 3) + result = newNimNode(nnkForStmt).add(x[1], x[2], result) + else: + result = newIfStmt((x, result)) + + result = newNimNode(nnkCall).add( + newNimNode(nnkPar).add( + newNimNode(nnkLambda).add( + newEmptyNode(), + newEmptyNode(), + newEmptyNode(), + newNimNode(nnkFormalParams).add( + newNimNode(nnkBracketExpr).add( + newIdentNode("seq"), + typ)), + newEmptyNode(), + newEmptyNode(), + newStmtList( + newAssignment( + newIdentNode("result"), + newNimNode(nnkPrefix).add( + newIdentNode("@"), + newNimNode(nnkBracket))), + result)))) + + +macro dump*(x: typed): untyped = + ## Dumps the content of an expression, useful for debugging. + ## It accepts any expression and prints a textual representation + ## of the tree representing the expression - as it would appear in + ## source code - together with the value of the expression. + ## + ## As an example, + ## + ## .. code-block:: nim + ## let + ## x = 10 + ## y = 20 + ## dump(x + y) + ## + ## will print ``x + y = 30``. + let s = x.toStrLit + let r = quote do: + debugEcho `s`, " = ", `x` + return r + +# TODO: consider exporting this in macros.nim +proc freshIdentNodes(ast: NimNode): NimNode = + # Replace NimIdent and NimSym by a fresh ident node + # see also https://github.com/nim-lang/Nim/pull/8531#issuecomment-410436458 + proc inspect(node: NimNode): NimNode = + case node.kind: + of nnkIdent, nnkSym: + result = ident($node) + of nnkEmpty, nnkLiterals: + result = node + else: + result = node.kind.newTree() + for child in node: + result.add inspect(child) + result = inspect(ast) + +macro distinctBase*(T: typedesc): untyped = + ## reverses ``type T = distinct A``; works recursively. + runnableExamples: + type T = distinct int + doAssert distinctBase(T) is int + doAssert: not compiles(distinctBase(int)) + type T2 = distinct T + doAssert distinctBase(T2) is int + + let typeNode = getTypeImpl(T) + expectKind(typeNode, nnkBracketExpr) + if typeNode[0].typeKind != ntyTypeDesc: + error "expected typeDesc, got " & $typeNode[0] + var typeSym = typeNode[1] + typeSym = getTypeImpl(typeSym) + if typeSym.typeKind != ntyDistinct: + error "type is not distinct" + typeSym = typeSym[0] + while typeSym.typeKind == ntyDistinct: + typeSym = getTypeImpl(typeSym)[0] + typeSym.freshIdentNodes diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index 4f2f73ba7..2e138b27e 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -19,32 +19,36 @@ import macros import strformat from strutils import toLowerAscii -import colors +import colors, tables -const - hasThreadSupport = compileOption("threads") +when defined(windows): + import winlean -when not hasThreadSupport: - import tables - var - colorsFGCache = initTable[Color, string]() - colorsBGCache = initTable[Color, string]() - when not defined(windows): - var - styleCache = initTable[int, string]() +type + PTerminal = ref object + trueColorIsSupported: bool + trueColorIsEnabled: bool + fgSetColor: bool + when defined(windows): + hStdout: Handle + hStderr: Handle + oldStdoutAttr: int16 + oldStderrAttr: int16 -var - trueColorIsSupported: bool - trueColorIsEnabled: bool - fgSetColor: bool +var gTerm {.threadvar.}: PTerminal + +proc newTerminal(): PTerminal + +proc getTerminal(): PTerminal {.inline.} = + if isNil(gTerm): + gTerm = newTerminal() + result = gTerm const fgPrefix = "\x1b[38;2;" bgPrefix = "\x1b[48;2;" - -when not defined(windows): - const - stylePrefix = "\e[" + ansiResetCode* = "\e[0m" + stylePrefix = "\e[" when defined(windows): import winlean, os @@ -160,23 +164,6 @@ when defined(windows): proc setConsoleMode(hConsoleHandle: Handle, dwMode: DWORD): WINBOOL{. stdcall, dynlib: "kernel32", importc: "SetConsoleMode".} - var - hStdout: Handle # = createFile("CONOUT$", GENERIC_WRITE, 0, nil, - # OPEN_ALWAYS, 0, 0) - hStderr: Handle - - block: - var hStdoutTemp = getStdHandle(STD_OUTPUT_HANDLE) - if duplicateHandle(getCurrentProcess(), hStdoutTemp, getCurrentProcess(), - addr(hStdout), 0, 1, DUPLICATE_SAME_ACCESS) == 0: - when defined(consoleapp): - raiseOSError(osLastError()) - var hStderrTemp = getStdHandle(STD_ERROR_HANDLE) - if duplicateHandle(getCurrentProcess(), hStderrTemp, getCurrentProcess(), - addr(hStderr), 0, 1, DUPLICATE_SAME_ACCESS) == 0: - when defined(consoleapp): - raiseOSError(osLastError()) - proc getCursorPos(h: Handle): tuple [x,y: int] = var c: CONSOLESCREENBUFFERINFO if getConsoleScreenBufferInfo(h, addr(c)) == 0: @@ -197,12 +184,23 @@ when defined(windows): return c.wAttributes return 0x70'i16 # ERROR: return white background, black text - var - oldStdoutAttr = getAttributes(hStdout) - oldStderrAttr = getAttributes(hStderr) + proc initTerminal(term: PTerminal) = + var hStdoutTemp = getStdHandle(STD_OUTPUT_HANDLE) + if duplicateHandle(getCurrentProcess(), hStdoutTemp, getCurrentProcess(), + addr(term.hStdout), 0, 1, DUPLICATE_SAME_ACCESS) == 0: + when defined(consoleapp): + raiseOSError(osLastError()) + var hStderrTemp = getStdHandle(STD_ERROR_HANDLE) + if duplicateHandle(getCurrentProcess(), hStderrTemp, getCurrentProcess(), + addr(term.hStderr), 0, 1, DUPLICATE_SAME_ACCESS) == 0: + when defined(consoleapp): + raiseOSError(osLastError()) + term.oldStdoutAttr = getAttributes(term.hStdout) + term.oldStderrAttr = getAttributes(term.hStderr) template conHandle(f: File): Handle = - if f == stderr: hStderr else: hStdout + let term = getTerminal() + if f == stderr: term.hStderr else: term.hStdout else: import termios, posix, os, parseutils @@ -310,7 +308,7 @@ proc setCursorPos*(f: File, x, y: int) = let h = conHandle(f) setCursorPos(h, x, y) else: - f.write(fmt"{stylePrefix}{y};{x}f") + f.write(fmt"{stylePrefix}{y+1};{x+1}f") proc setCursorXPos*(f: File, x: int) = ## Sets the terminal's cursor to the x position. @@ -325,7 +323,7 @@ proc setCursorXPos*(f: File, x: int) = if setConsoleCursorPosition(h, origin) == 0: raiseOSError(osLastError()) else: - f.write(fmt"{stylePrefix}{x}G") + f.write(fmt"{stylePrefix}{x+1}G") when defined(windows): proc setCursorYPos*(f: File, y: int) = @@ -463,39 +461,43 @@ proc eraseScreen*(f: File) = proc resetAttributes*(f: File) = ## Resets all attributes. when defined(windows): + let term = getTerminal() if f == stderr: - discard setConsoleTextAttribute(hStderr, oldStderrAttr) + discard setConsoleTextAttribute(term.hStderr, term.oldStderrAttr) else: - discard setConsoleTextAttribute(hStdout, oldStdoutAttr) + discard setConsoleTextAttribute(term.hStdout, term.oldStdoutAttr) else: - f.write("\e[0m") + f.write(ansiResetCode) type - Style* = enum ## different styles for text output + Style* = enum ## different styles for text output styleBright = 1, ## bright text styleDim, ## dim text - styleUnknown, ## unknown - styleUnderscore = 4, ## underscored text + styleItalic, ## italic (or reverse on terminals not supporting) + styleUnderscore, ## underscored text styleBlink, ## blinking/bold text - styleReverse = 7, ## unknown - styleHidden ## hidden text + styleBlinkRapid, ## rapid blinking/bold text (not widely supported) + styleReverse, ## reverse + styleHidden, ## hidden text + styleStrikethrough ## strikethrough {.deprecated: [TStyle: Style].} +{.deprecated: [styleUnknown: styleItalic].} when not defined(windows): var gFG {.threadvar.}: int gBG {.threadvar.}: int - proc getStyleStr(style: int): string = - when hasThreadSupport: - result = fmt"{stylePrefix}{style}m" - else: - if styleCache.hasKey(style): - result = styleCache[style] - else: - result = fmt"{stylePrefix}{style}m" - styleCache[style] = result +proc ansiStyleCode*(style: int): string = + result = fmt"{stylePrefix}{style}m" + +template ansiStyleCode*(style: Style): string = + ansiStyleCode(style.int) + +# The styleCache can be skipped when `style` is known at compile-time +template ansiStyleCode*(style: static[Style]): string = + (static(stylePrefix & $style.int & "m")) proc setStyle*(f: File, style: set[Style]) = ## Sets the terminal style. @@ -510,23 +512,24 @@ proc setStyle*(f: File, style: set[Style]) = discard setConsoleTextAttribute(h, old or a) else: for s in items(style): - f.write(getStyleStr(ord(s))) + f.write(ansiStyleCode(s)) proc writeStyled*(txt: string, style: set[Style] = {styleBright}) = ## Writes the text `txt` in a given `style` to stdout. when defined(windows): - var old = getAttributes(hStdout) + let term = getTerminal() + var old = getAttributes(term.hStdout) stdout.setStyle(style) stdout.write(txt) - discard setConsoleTextAttribute(hStdout, old) + discard setConsoleTextAttribute(term.hStdout, old) else: stdout.setStyle(style) stdout.write(txt) stdout.resetAttributes() if gFG != 0: - stdout.write(getStyleStr(gFG)) + stdout.write(ansiStyleCode(gFG)) if gBG != 0: - stdout.write(getStyleStr(gBG)) + stdout.write(ansiStyleCode(gBG)) type ForegroundColor* = enum ## terminal's foreground colors @@ -537,7 +540,9 @@ type fgBlue, ## blue fgMagenta, ## magenta fgCyan, ## cyan - fgWhite ## white + fgWhite, ## white + fg8Bit, ## 256-color (not supported, see ``enableTrueColors`` instead.) + fgDefault ## default terminal foreground color BackgroundColor* = enum ## terminal's background colors bgBlack = 40, ## black @@ -547,92 +552,112 @@ type bgBlue, ## blue bgMagenta, ## magenta bgCyan, ## cyan - bgWhite ## white + bgWhite, ## white + bg8Bit, ## 256-color (not supported, see ``enableTrueColors`` instead.) + bgDefault ## default terminal background color {.deprecated: [TForegroundColor: ForegroundColor, TBackgroundColor: BackgroundColor].} +when defined(windows): + var defaultForegroundColor, defaultBackgroundColor: int16 = 0xFFFF'i16 # Default to an invalid value 0xFFFF + proc setForegroundColor*(f: File, fg: ForegroundColor, bright=false) = ## Sets the terminal's foreground color. when defined(windows): let h = conHandle(f) var old = getAttributes(h) and not FOREGROUND_RGB - if bright: - old = old or FOREGROUND_INTENSITY + if defaultForegroundColor == 0xFFFF'i16: + defaultForegroundColor = old + old = if bright: old or FOREGROUND_INTENSITY + else: old and not(FOREGROUND_INTENSITY) const lookup: array[ForegroundColor, int] = [ - 0, + 0, # ForegroundColor enum with ordinal 30 (FOREGROUND_RED), (FOREGROUND_GREEN), (FOREGROUND_RED or FOREGROUND_GREEN), (FOREGROUND_BLUE), (FOREGROUND_RED or FOREGROUND_BLUE), (FOREGROUND_BLUE or FOREGROUND_GREEN), - (FOREGROUND_BLUE or FOREGROUND_GREEN or FOREGROUND_RED)] - discard setConsoleTextAttribute(h, toU16(old or lookup[fg])) + (FOREGROUND_BLUE or FOREGROUND_GREEN or FOREGROUND_RED), + 0, # fg8Bit not supported, see ``enableTrueColors`` instead. + 0] # unused + if fg == fgDefault: + discard setConsoleTextAttribute(h, toU16(old or defaultForegroundColor)) + else: + discard setConsoleTextAttribute(h, toU16(old or lookup[fg])) else: gFG = ord(fg) if bright: inc(gFG, 60) - f.write(getStyleStr(gFG)) + f.write(ansiStyleCode(gFG)) proc setBackgroundColor*(f: File, bg: BackgroundColor, bright=false) = ## Sets the terminal's background color. when defined(windows): let h = conHandle(f) var old = getAttributes(h) and not BACKGROUND_RGB - if bright: - old = old or BACKGROUND_INTENSITY + if defaultBackgroundColor == 0xFFFF'i16: + defaultBackgroundColor = old + old = if bright: old or BACKGROUND_INTENSITY + else: old and not(BACKGROUND_INTENSITY) const lookup: array[BackgroundColor, int] = [ - 0, + 0, # BackgroundColor enum with ordinal 40 (BACKGROUND_RED), (BACKGROUND_GREEN), (BACKGROUND_RED or BACKGROUND_GREEN), (BACKGROUND_BLUE), (BACKGROUND_RED or BACKGROUND_BLUE), (BACKGROUND_BLUE or BACKGROUND_GREEN), - (BACKGROUND_BLUE or BACKGROUND_GREEN or BACKGROUND_RED)] - discard setConsoleTextAttribute(h, toU16(old or lookup[bg])) + (BACKGROUND_BLUE or BACKGROUND_GREEN or BACKGROUND_RED), + 0, # bg8Bit not supported, see ``enableTrueColors`` instead. + 0] # unused + if bg == bgDefault: + discard setConsoleTextAttribute(h, toU16(old or defaultBackgroundColor)) + else: + discard setConsoleTextAttribute(h, toU16(old or lookup[bg])) else: gBG = ord(bg) if bright: inc(gBG, 60) - f.write(getStyleStr(gBG)) + f.write(ansiStyleCode(gBG)) +proc ansiForegroundColorCode*(fg: ForegroundColor, bright=false): string = + var style = ord(fg) + if bright: inc(style, 60) + return ansiStyleCode(style) -proc getFGColorStr(color: Color): string = - when hasThreadSupport: - let rgb = extractRGB(color) - result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m" - else: - if colorsFGCache.hasKey(color): - result = colorsFGCache[color] - else: - let rgb = extractRGB(color) - result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m" - colorsFGCache[color] = result - -proc getBGColorStr(color: Color): string = - when hasThreadSupport: - let rgb = extractRGB(color) - result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m" - else: - if colorsBGCache.hasKey(color): - result = colorsBGCache[color] - else: - let rgb = extractRGB(color) - result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m" - colorsFGCache[color] = result +template ansiForegroundColorCode*(fg: static[ForegroundColor], + bright: static[bool] = false): string = + ansiStyleCode(fg.int + bright.int * 60) + +proc ansiForegroundColorCode*(color: Color): string = + let rgb = extractRGB(color) + result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m" + +template ansiForegroundColorCode*(color: static[Color]): string = + const rgb = extractRGB(color) + (static(fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m")) + +proc ansiBackgroundColorCode*(color: Color): string = + let rgb = extractRGB(color) + result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m" + +template ansiBackgroundColorCode*(color: static[Color]): string = + const rgb = extractRGB(color) + (static(fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m")) proc setForegroundColor*(f: File, color: Color) = ## Sets the terminal's foreground true color. - if trueColorIsEnabled: - f.write(getFGColorStr(color)) + if getTerminal().trueColorIsEnabled: + f.write(ansiForegroundColorCode(color)) proc setBackgroundColor*(f: File, color: Color) = ## Sets the terminal's background true color. - if trueColorIsEnabled: - f.write(getBGColorStr(color)) + if getTerminal().trueColorIsEnabled: + f.write(ansiBackgroundColorCode(color)) proc setTrueColor(f: File, color: Color) = - if fgSetColor: + let term = getTerminal() + if term.fgSetColor: setForegroundColor(f, color) else: setBackgroundColor(f, color) @@ -671,8 +696,8 @@ template styledEchoProcessArg(f: File, cmd: TerminalCmd) = when cmd == bgColor: fgSetColor = false -macro styledWriteLine*(f: File, m: varargs[typed]): untyped = - ## Similar to ``writeLine``, but treating terminal style arguments specially. +macro styledWrite*(f: File, m: varargs[typed]): untyped = + ## Similar to ``write``, but treating terminal style arguments specially. ## When some argument is ``Style``, ``set[Style]``, ``ForegroundColor``, ## ``BackgroundColor`` or ``TerminalCmd`` then it is not sent directly to ## ``f``, but instead corresponding terminal style proc is called. @@ -681,20 +706,19 @@ macro styledWriteLine*(f: File, m: varargs[typed]): untyped = ## ## .. code-block:: nim ## - ## proc error(msg: string) = - ## styledWriteLine(stderr, fgRed, "Error: ", resetStyle, msg) + ## stdout.styledWrite(fgRed, "red text ") + ## stdout.styledWrite(fgGreen, "green text") ## - let m = callsite() var reset = false result = newNimNode(nnkStmtList) - for i in countup(2, m.len - 1): + for i in countup(0, m.len - 1): let item = m[i] case item.kind of nnkStrLit..nnkTripleStrLit: if i == m.len - 1: - # optimize if string literal is last, just call writeLine - result.add(newCall(bindSym"writeLine", f, item)) + # optimize if string literal is last, just call write + result.add(newCall(bindSym"write", f, item)) if reset: result.add(newCall(bindSym"resetAttributes", f)) return else: @@ -703,16 +727,24 @@ macro styledWriteLine*(f: File, m: varargs[typed]): untyped = else: result.add(newCall(bindSym"styledEchoProcessArg", f, item)) reset = true - - result.add(newCall(bindSym"write", f, newStrLitNode("\n"))) if reset: result.add(newCall(bindSym"resetAttributes", f)) -macro styledEcho*(args: varargs[untyped]): untyped = +template styledWriteLine*(f: File, args: varargs[untyped]) = + ## Calls ``styledWrite`` and appends a newline at the end. + ## + ## Example: + ## + ## .. code-block:: nim + ## + ## proc error(msg: string) = + ## styledWriteLine(stderr, fgRed, "Error: ", resetStyle, msg) + ## + styledWrite(f, args) + write(f, "\n") + +template styledEcho*(args: varargs[untyped]) = ## Echoes styles arguments to stdout using ``styledWriteLine``. - result = newCall(bindSym"styledWriteLine") - result.add(bindSym"stdout") - for arg in children(args): - result.add(arg) + stdout.styledWriteLine(args) proc getch*(): char = ## Read a single character from the terminal, blocking until it is entered. @@ -759,6 +791,10 @@ when defined(windows): x = runeLenAt(password.string, i) inc i, x password.string.setLen(max(password.len - x, 0)) + of chr(0x0): + # modifier key - ignore - for details see + # https://github.com/nim-lang/Nim/issues/7764 + continue else: password.string.add(toUTF8(c.Rune)) stdout.write "\n" @@ -815,63 +851,102 @@ proc resetAttributes*() {.noconv.} = ## ``system.addQuitProc(resetAttributes)``. resetAttributes(stdout) -when not defined(testing) and isMainModule: - #system.addQuitProc(resetAttributes) - write(stdout, "never mind") - stdout.eraseLine() - stdout.styledWriteLine("styled text ", {styleBright, styleBlink, styleUnderscore}) - stdout.setBackGroundColor(bgCyan, true) - stdout.setForeGroundColor(fgBlue) - stdout.writeLine("ordinary text") - stdout.resetAttributes() - proc isTrueColorSupported*(): bool = ## Returns true if a terminal supports true color. - return trueColorIsSupported + return getTerminal().trueColorIsSupported when defined(windows): import os proc enableTrueColors*() = ## Enable true color. + var term = getTerminal() when defined(windows): var ver: OSVERSIONINFO ver.dwOSVersionInfoSize = sizeof(ver).DWORD let res = getVersionExW(addr ver) if res == 0: - trueColorIsSupported = false + term.trueColorIsSupported = false else: - trueColorIsSupported = ver.dwMajorVersion > 10 or + term.trueColorIsSupported = ver.dwMajorVersion > 10 or (ver.dwMajorVersion == 10 and (ver.dwMinorVersion > 0 or (ver.dwMinorVersion == 0 and ver.dwBuildNumber >= 10586))) - if not trueColorIsSupported: - trueColorIsSupported = getEnv("ANSICON_DEF").len > 0 + if not term.trueColorIsSupported: + term.trueColorIsSupported = getEnv("ANSICON_DEF").len > 0 - if trueColorIsSupported: + if term.trueColorIsSupported: if getEnv("ANSICON_DEF").len == 0: var mode: DWORD = 0 if getConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), addr(mode)) != 0: mode = mode or ENABLE_VIRTUAL_TERMINAL_PROCESSING if setConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), mode) != 0: - trueColorIsEnabled = true + term.trueColorIsEnabled = true else: - trueColorIsEnabled = false + term.trueColorIsEnabled = false else: - trueColorIsEnabled = true + term.trueColorIsEnabled = true else: - trueColorIsSupported = string(getEnv("COLORTERM")).toLowerAscii() in ["truecolor", "24bit"] - trueColorIsEnabled = trueColorIsSupported + term.trueColorIsSupported = string(getEnv("COLORTERM")).toLowerAscii() in ["truecolor", "24bit"] + term.trueColorIsEnabled = term.trueColorIsSupported proc disableTrueColors*() = ## Disable true color. + var term = getTerminal() when defined(windows): - if trueColorIsSupported: + if term.trueColorIsSupported: if getEnv("ANSICON_DEF").len == 0: var mode: DWORD = 0 if getConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), addr(mode)) != 0: mode = mode and not ENABLE_VIRTUAL_TERMINAL_PROCESSING discard setConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), mode) - trueColorIsEnabled = false + term.trueColorIsEnabled = false else: - trueColorIsEnabled = false + term.trueColorIsEnabled = false + +proc newTerminal(): PTerminal = + new result + when defined(windows): + initTerminal(result) + +when not defined(testing) and isMainModule: + assert ansiStyleCode(styleBright) == "\e[1m" + assert ansiStyleCode(styleStrikethrough) == "\e[9m" + #system.addQuitProc(resetAttributes) + write(stdout, "never mind") + stdout.eraseLine() + stdout.styledWriteLine({styleBright, styleBlink, styleUnderscore}, "styled text ") + stdout.styledWriteLine("italic text ", {styleItalic}) + stdout.setBackGroundColor(bgCyan, true) + stdout.setForeGroundColor(fgBlue) + stdout.write("blue text in cyan background") + stdout.resetAttributes() + echo "" + stdout.writeLine("ordinary text") + echo "more ordinary text" + styledEcho styleBright, fgGreen, "[PASS]", resetStyle, fgGreen, " Yay!" + echo "ordinary text again" + styledEcho styleBright, fgRed, "[FAIL]", resetStyle, fgRed, " Nay :(" + echo "ordinary text again" + setForeGroundColor(fgGreen) + echo "green text" + echo "more green text" + setForeGroundColor(fgBlue) + echo "blue text" + resetAttributes() + echo "ordinary text" + + stdout.styledWriteLine(fgRed, "red text ") + stdout.styledWriteLine(fgWhite, bgRed, "white text in red background") + stdout.styledWriteLine(" ordinary text ") + stdout.styledWriteLine(fgGreen, "green text") + + stdout.styledWrite(fgRed, "red text ") + stdout.styledWrite(fgWhite, bgRed, "white text in red background") + stdout.styledWrite(" ordinary text ") + stdout.styledWrite(fgGreen, "green text") + echo "" + echo "ordinary text" + stdout.styledWriteLine(fgRed, "red text ", styleBright, "bold red", fgDefault, " bold text") + stdout.styledWriteLine(bgYellow, "text in yellow bg", styleBright, " bold text in yellow bg", bgDefault, " bold text") + echo "ordinary text" diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 6c1e1fe87..a7ccbf6ee 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1,49 +1,166 @@ # # # Nim's Runtime Library -# (c) Copyright 2015 Andreas Rumpf +# (c) Copyright 2017 Nim contributors # # See the file "copying.txt", included in this # distribution, for details about the copyright. # +##[ + This module contains routines and types for dealing with time using a proleptic Gregorian calendar. + It's also available for the `JavaScript target <backends.html#the-javascript-target>`_. + + Although the types use nanosecond time resolution, the underlying resolution used by ``getTime()`` + depends on the platform and backend (JS is limited to millisecond precision). + + Examples: + + .. code-block:: nim + + import times, os + let time = cpuTime() + + sleep(100) # replace this with something to be timed + echo "Time taken: ",cpuTime() - time + + echo "My formatted time: ", format(now(), "d MMMM yyyy HH:mm") + echo "Using predefined formats: ", getClockStr(), " ", getDateStr() + + echo "cpuTime() float value: ", cpuTime() + echo "An hour from now : ", now() + 1.hours + echo "An hour from (UTC) now: ", getTime().utc + initDuration(hours = 1) + + Parsing and Formatting Dates + ---------------------------- + + The ``DateTime`` type can be parsed and formatted using the different + ``parse`` and ``format`` procedures. + + .. code-block:: nim + + let dt = parse("2000-01-01", "yyyy-MM-dd") + echo dt.format("yyyy-MM-dd") + + The different format patterns that are supported are documented below. + + ============= ================================================================================= ================================================ + Pattern Description Example + ============= ================================================================================= ================================================ + ``d`` Numeric value representing the day of the month, | ``1/04/2012 -> 1`` + it will be either one or two digits long. | ``21/04/2012 -> 21`` + ``dd`` Same as above, but is always two digits. | ``1/04/2012 -> 01`` + | ``21/04/2012 -> 21`` + ``ddd`` Three letter string which indicates the day of the week. | ``Saturday -> Sat`` + | ``Monday -> Mon`` + ``dddd`` Full string for the day of the week. | ``Saturday -> Saturday`` + | ``Monday -> Monday`` + ``h`` The hours in one digit if possible. Ranging from 1-12. | ``5pm -> 5`` + | ``2am -> 2`` + ``hh`` The hours in two digits always. If the hour is one digit 0 is prepended. | ``5pm -> 05`` + | ``11am -> 11`` + ``H`` The hours in one digit if possible, ranging from 0-23. | ``5pm -> 17`` + | ``2am -> 2`` + ``HH`` The hours in two digits always. 0 is prepended if the hour is one digit. | ``5pm -> 17`` + | ``2am -> 02`` + ``m`` The minutes in 1 digit if possible. | ``5:30 -> 30`` + | ``2:01 -> 1`` + ``mm`` Same as above but always 2 digits, 0 is prepended if the minute is one digit. | ``5:30 -> 30`` + | ``2:01 -> 01`` + ``M`` The month in one digit if possible. | ``September -> 9`` + | ``December -> 12`` + ``MM`` The month in two digits always. 0 is prepended. | ``September -> 09`` + | ``December -> 12`` + ``MMM`` Abbreviated three-letter form of the month. | ``September -> Sep`` + | ``December -> Dec`` + ``MMMM`` Full month string, properly capitalized. | ``September -> September`` + ``s`` Seconds as one digit if possible. | ``00:00:06 -> 6`` + ``ss`` Same as above but always two digits. 0 is prepended. | ``00:00:06 -> 06`` + ``t`` ``A`` when time is in the AM. ``P`` when time is in the PM. | ``5pm -> P`` + | ``2am -> A`` + ``tt`` Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively. | ``5pm -> PM`` + | ``2am -> AM`` + ``yy`` The last two digits of the year. When parsing, the current century is assumed. | ``2012 AD -> 12`` + ``yyyy`` The year, padded to atleast four digits. | ``2012 AD -> 2012`` + Is always positive, even when the year is BC. | ``24 AD -> 0024`` + When the year is more than four digits, '+' is prepended. | ``24 BC -> 00024`` + | ``12345 AD -> +12345`` + ``YYYY`` The year without any padding. | ``2012 AD -> 2012`` + Is always positive, even when the year is BC. | ``24 AD -> 24`` + | ``24 BC -> 24`` + | ``12345 AD -> 12345`` + ``uuuu`` The year, padded to atleast four digits. Will be negative when the year is BC. | ``2012 AD -> 2012`` + When the year is more than four digits, '+' is prepended unless the year is BC. | ``24 AD -> 0024`` + | ``24 BC -> -0023`` + | ``12345 AD -> +12345`` + ``UUUU`` The year without any padding. Will be negative when the year is BC. | ``2012 AD -> 2012`` + | ``24 AD -> 24`` + | ``24 BC -> -23`` + | ``12345 AD -> 12345`` + ``z`` Displays the timezone offset from UTC. | ``GMT+7 -> +7`` + | ``GMT-5 -> -5`` + ``zz`` Same as above but with leading 0. | ``GMT+7 -> +07`` + | ``GMT-5 -> -05`` + ``zzz`` Same as above but with ``:mm`` where *mm* represents minutes. | ``GMT+7 -> +07:00`` + | ``GMT-5 -> -05:00`` + ``zzzz`` Same as above but with ``:ss`` where *ss* represents seconds. | ``GMT+7 -> +07:00:00`` + | ``GMT-5 -> -05:00:00`` + ``g`` Era: AD or BC | ``300 AD -> AD`` + | ``300 BC -> BC`` + ``fff`` Milliseconds display | ``1000000 nanoseconds -> 1`` + ``ffffff`` Microseconds display | ``1000000 nanoseconds -> 1000`` + ``fffffffff`` Nanoseconds display | ``1000000 nanoseconds -> 1000000`` + ============= ================================================================================= ================================================ + + Other strings can be inserted by putting them in ``''``. For example + ``hh'->'mm`` will give ``01->56``. The following characters can be + inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` + ``,``. A literal ``'`` can be specified with ``''``. + + However you don't need to necessarily separate format patterns, a + unambiguous format string like ``yyyyMMddhhmmss`` is valid too (although + only for years in the range 1..9999). +]## -## This module contains routines and types for dealing with time. -## This module is available for the `JavaScript target -## <backends.html#the-javascript-target>`_. The proleptic Gregorian calendar is the only calendar supported. -## -## Examples: -## -## .. code-block:: nim -## -## import times, os -## let time = cpuTime() -## -## sleep(100) # replace this with something to be timed -## echo "Time taken: ",cpuTime() - time -## -## echo "My formatted time: ", format(now(), "d MMMM yyyy HH:mm") -## echo "Using predefined formats: ", getClockStr(), " ", getDateStr() -## -## echo "epochTime() float value: ", epochTime() -## echo "cpuTime() float value: ", cpuTime() -## echo "An hour from now : ", now() + 1.hours -## echo "An hour from (UTC) now: ", getTime().utc + initInterval(0,0,0,1) {.push debugger:off.} # the user does not want to trace a part # of the standard library! import - strutils, parseutils + strutils, parseutils, algorithm, math, options, strformat include "system/inclrtl" +# This is really bad, but overflow checks are broken badly for +# ints on the JS backend. See #6752. +when defined(JS): + {.push overflowChecks: off.} + proc `*`(a, b: int64): int64 = + system.`* `(a, b) + proc `*`(a, b: int): int = + system.`* `(a, b) + proc `+`(a, b: int64): int64 = + system.`+ `(a, b) + proc `+`(a, b: int): int = + system.`+ `(a, b) + proc `-`(a, b: int64): int64 = + system.`- `(a, b) + proc `-`(a, b: int): int = + system.`- `(a, b) + proc inc(a: var int, b: int) = + system.inc(a, b) + proc inc(a: var int64, b: int) = + system.inc(a, b) + {.pop.} + when defined(posix): import posix type CTime = posix.Time - proc posix_gettimeofday(tp: var Timeval, unused: pointer = nil) {. + var CLOCK_REALTIME {.importc: "CLOCK_REALTIME", header: "<time.h>".}: Clockid + + proc gettimeofday(tp: var Timeval, unused: pointer = nil) {. importc: "gettimeofday", header: "<sys/time.h>".} when not defined(freebsd) and not defined(netbsd) and not defined(openbsd): @@ -53,34 +170,49 @@ when defined(posix): elif defined(windows): import winlean - # newest version of Visual C++ defines time_t to be of 64 bits - type CTime {.importc: "time_t", header: "<time.h>".} = distinct int64 + when defined(i386) and defined(gcc): + type CTime {.importc: "time_t", header: "<time.h>".} = distinct int32 + else: + # newest version of Visual C++ defines time_t to be of 64 bits + type CTime {.importc: "time_t", header: "<time.h>".} = distinct int64 # visual c's c runtime exposes these under a different name var timezone {.importc: "_timezone", header: "<time.h>".}: int type Month* = enum ## Represents a month. Note that the enum starts at ``1``, so ``ord(month)`` will give ## the month number in the range ``[1..12]``. - mJan = 1, mFeb, mMar, mApr, mMay, mJun, mJul, mAug, mSep, mOct, mNov, mDec + mJan = (1, "January") + mFeb = "February" + mMar = "March" + mApr = "April" + mMay = "May" + mJun = "June" + mJul = "July" + mAug = "August" + mSep = "September" + mOct = "October" + mNov = "November" + mDec = "December" WeekDay* = enum ## Represents a weekday. - dMon, dTue, dWed, dThu, dFri, dSat, dSun + dMon = "Monday" + dTue = "Tuesday" + dWed = "Wednesday" + dThu = "Thursday" + dFri = "Friday" + dSat = "Saturday" + dSun = "Sunday" MonthdayRange* = range[1..31] HourRange* = range[0..23] MinuteRange* = range[0..59] SecondRange* = range[0..60] YeardayRange* = range[0..365] + NanosecondRange* = range[0..999_999_999] - TimeImpl = int64 - - Time* = distinct TimeImpl ## Represents a point in time. - ## This is currently implemented as a ``int64`` representing - ## seconds since ``1970-01-01T00:00:00Z``, but don't - ## rely on this knowledge because it might change - ## in the future to allow for higher precision. - ## Use the procs ``toUnix`` and ``fromUnix`` to - ## work with unix timestamps instead. + Time* = object ## Represents a point in time. + seconds: int64 + nanosecond: NanosecondRange DateTime* = object of RootObj ## Represents a time in different parts. ## Although this type can represent leap @@ -89,6 +221,8 @@ type ## but the ``DateTime``'s returned by ## procedures in this module will never have ## a leap second. + nanosecond*: NanosecondRange ## The number of nanoseconds after the second, + ## in the range 0 to 999_999_999. second*: SecondRange ## The number of seconds after the minute, ## normally in the range 0 to 59, but can ## be up to 60 to allow for a leap second. @@ -111,33 +245,58 @@ type ## of the one in a formatted offset string like ``+01:00`` ## (which would be parsed into the UTC offset ``-3600``). - TimeInterval* = object ## Represents a duration of time. Can be used to add and subtract - ## from a ``DateTime`` or ``Time``. - ## Note that a ``TimeInterval`` doesn't represent a fixed duration of time, + TimeInterval* = object ## Represents a non-fixed duration of time. Can be used to add and subtract + ## non-fixed time units from a ``DateTime`` or ``Time``. + ## ``TimeInterval`` doesn't represent a fixed duration of time, ## since the duration of some units depend on the context (e.g a year ## can be either 365 or 366 days long). The non-fixed time units are years, ## months and days. + + nanoseconds*: int ## The number of nanoseconds + microseconds*: int ## The number of microseconds milliseconds*: int ## The number of milliseconds - seconds*: int ## The number of seconds - minutes*: int ## The number of minutes - hours*: int ## The number of hours - days*: int ## The number of days - months*: int ## The number of months - years*: int ## The number of years - - Timezone* = object ## Timezone interface for supporting ``DateTime``'s of arbritary timezones. - ## The ``times`` module only supplies implementations for the systems local time and UTC. - ## The members ``zoneInfoFromUtc`` and ``zoneInfoFromTz`` should not be accessed directly - ## and are only exported so that ``Timezone`` can be implemented by other modules. - zoneInfoFromUtc*: proc (time: Time): ZonedTime {.tags: [], raises: [], benign.} - zoneInfoFromTz*: proc (adjTime: Time): ZonedTime {.tags: [], raises: [], benign.} - name*: string ## The name of the timezone, f.ex 'Europe/Stockholm' or 'Etc/UTC'. Used for checking equality. - ## Se also: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - ZonedTime* = object ## Represents a zooned instant in time that is not associated with any calendar. - ## This type is only used for implementing timezones. - adjTime*: Time ## Time adjusted to a timezone. - utcOffset*: int - isDst*: bool + seconds*: int ## The number of seconds + minutes*: int ## The number of minutes + hours*: int ## The number of hours + days*: int ## The number of days + weeks*: int ## The number of weeks + months*: int ## The number of months + years*: int ## The number of years + + Duration* = object ## Represents a fixed duration of time. + ## Uses the same time resolution as ``Time``. + ## This type should be prefered over ``TimeInterval`` unless + ## non-static time units is needed. + seconds: int64 + nanosecond: NanosecondRange + + TimeUnit* = enum ## Different units of time. + Nanoseconds, Microseconds, Milliseconds, Seconds, Minutes, Hours, Days, Weeks, Months, Years + + FixedTimeUnit* = range[Nanoseconds..Weeks] ## Subrange of ``TimeUnit`` that only includes units of fixed duration. + ## These are the units that can be represented by a ``Duration``. + + Timezone* = ref object ## \ + ## Timezone interface for supporting ``DateTime``'s of arbritary + ## timezones. The ``times`` module only supplies implementations for the + ## systems local time and UTC. + zonedTimeFromTimeImpl: proc (x: Time): ZonedTime + {.tags: [], raises: [], benign.} + zonedTimeFromAdjTimeImpl: proc (x: Time): ZonedTime + {.tags: [], raises: [], benign.} + name: string + + ZonedTime* = object ## Represents a point in time with an associated + ## UTC offset and DST flag. This type is only used for + ## implementing timezones. + time*: Time ## The point in time being represented. + utcOffset*: int ## The offset in seconds west of UTC, + ## including any offset due to DST. + isDst*: bool ## Determines whether DST is in effect. + + DurationParts* = array[FixedTimeUnit, int64] # Array of Duration parts starts + TimeIntervalParts* = array[TimeUnit, int] # Array of Duration parts starts + TimesMutableTypes = DateTime | Time | Duration | TimeInterval {.deprecated: [TMonth: Month, TWeekDay: WeekDay, TTime: Time, TTimeInterval: TimeInterval, TTimeInfo: DateTime, TimeInfo: DateTime].} @@ -147,14 +306,136 @@ const secondsInHour = 60*60 secondsInDay = 60*60*24 minutesInHour = 60 + rateDiff = 10000000'i64 # 100 nsecs + # The number of hectonanoseconds between 1601/01/01 (windows epoch) + # and 1970/01/01 (unix epoch). + epochDiff = 116444736000000000'i64 + +const unitWeights: array[FixedTimeUnit, int64] = [ + 1'i64, + 1000, + 1_000_000, + 1e9.int64, + secondsInMin * 1e9.int64, + secondsInHour * 1e9.int64, + secondsInDay * 1e9.int64, + 7 * secondsInDay * 1e9.int64, +] + +proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T {.inline.} = + ## Convert a quantity of some duration unit to another duration unit. + runnableExamples: + doAssert convert(Days, Hours, 2) == 48 + doAssert convert(Days, Weeks, 13) == 1 # Truncated + doAssert convert(Seconds, Milliseconds, -1) == -1000 + if unitFrom < unitTo: + (quantity div (unitWeights[unitTo] div unitWeights[unitFrom])).T + else: + ((unitWeights[unitFrom] div unitWeights[unitTo]) * quantity).T + +proc normalize[T: Duration|Time](seconds, nanoseconds: int64): T = + ## Normalize a (seconds, nanoseconds) pair and return it as either + ## a ``Duration`` or ``Time``. A normalized ``Duration|Time`` has a + ## positive nanosecond part in the range ``NanosecondRange``. + result.seconds = seconds + convert(Nanoseconds, Seconds, nanoseconds) + var nanosecond = nanoseconds mod convert(Seconds, Nanoseconds, 1) + if nanosecond < 0: + nanosecond += convert(Seconds, Nanoseconds, 1) + result.seconds -= 1 + result.nanosecond = nanosecond.int + +# Forward declarations +proc utcTzInfo(time: Time): ZonedTime {.tags: [], raises: [], benign .} +proc localZonedTimeFromTime(time: Time): ZonedTime {.tags: [], raises: [], benign .} +proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.tags: [], raises: [], benign .} +proc initTime*(unix: int64, nanosecond: NanosecondRange): Time + {.tags: [], raises: [], benign noSideEffect.} + +proc initDuration*(nanoseconds, microseconds, milliseconds, + seconds, minutes, hours, days, weeks: int64 = 0): Duration + {.tags: [], raises: [], benign noSideEffect.} + +proc nanosecond*(time: Time): NanosecondRange = + ## Get the fractional part of a ``Time`` as the number + ## of nanoseconds of the second. + time.nanosecond + + +proc weeks*(dur: Duration): int64 {.inline.} = + ## Number of whole weeks represented by the duration. + convert(Seconds, Weeks, dur.seconds) + +proc days*(dur: Duration): int64 {.inline.} = + ## Number of whole days represented by the duration. + convert(Seconds, Days, dur.seconds) + +proc minutes*(dur: Duration): int64 {.inline.} = + ## Number of whole minutes represented by the duration. + convert(Seconds, Minutes, dur.seconds) + +proc hours*(dur: Duration): int64 {.inline.} = + ## Number of whole hours represented by the duration. + convert(Seconds, Hours, dur.seconds) + +proc seconds*(dur: Duration): int64 {.inline.} = + ## Number of whole seconds represented by the duration. + dur.seconds + +proc milliseconds*(dur: Duration): int {.inline.} = + ## Number of whole milliseconds represented by the **fractional** + ## part of the duration. + runnableExamples: + let dur = initDuration(seconds = 1, milliseconds = 1) + doAssert dur.milliseconds == 1 + convert(Nanoseconds, Milliseconds, dur.nanosecond) + +proc microseconds*(dur: Duration): int {.inline.} = + ## Number of whole microseconds represented by the **fractional** + ## part of the duration. + runnableExamples: + let dur = initDuration(seconds = 1, microseconds = 1) + doAssert dur.microseconds == 1 + convert(Nanoseconds, Microseconds, dur.nanosecond) + +proc nanoseconds*(dur: Duration): int {.inline.} = + ## Number of whole nanoseconds represented by the **fractional** + ## part of the duration. + runnableExamples: + let dur = initDuration(seconds = 1, nanoseconds = 1) + doAssert dur.nanoseconds == 1 + dur.nanosecond + +proc fractional*(dur: Duration): Duration {.inline.} = + ## The fractional part of duration, as a duration. + runnableExamples: + let dur = initDuration(seconds = 1, nanoseconds = 5) + doAssert dur.fractional == initDuration(nanoseconds = 5) + initDuration(nanoseconds = dur.nanosecond) + proc fromUnix*(unix: int64): Time {.benign, tags: [], raises: [], noSideEffect.} = ## Convert a unix timestamp (seconds since ``1970-01-01T00:00:00Z``) to a ``Time``. - Time(unix) + runnableExamples: + doAssert $fromUnix(0).utc == "1970-01-01T00:00:00Z" + initTime(unix, 0) proc toUnix*(t: Time): int64 {.benign, tags: [], raises: [], noSideEffect.} = ## Convert ``t`` to a unix timestamp (seconds since ``1970-01-01T00:00:00Z``). - t.int64 + runnableExamples: + doAssert fromUnix(0).toUnix() == 0 + t.seconds + +proc fromWinTime*(win: int64): Time = + ## Convert a Windows file time (100-nanosecond intervals since ``1601-01-01T00:00:00Z``) + ## to a ``Time``. + const hnsecsPerSec = convert(Seconds, Nanoseconds, 1) div 100 + let nanos = floorMod(win, hnsecsPerSec) * 100 + let seconds = floorDiv(win - epochDiff, hnsecsPerSec) + result = initTime(seconds, nanos) + +proc toWinTime*(t: Time): int64 = + ## Convert ``t`` to a Windows file time (100-nanosecond intervals since ``1601-01-01T00:00:00Z``). + result = t.seconds * rateDiff + epochDiff + t.nanosecond div 100 proc isLeapYear*(year: int): bool = ## Returns true if ``year`` is a leap year. @@ -174,7 +455,7 @@ proc getDaysInYear*(year: int): int = proc assertValidDate(monthday: MonthdayRange, month: Month, year: int) {.inline.} = assert monthday <= getDaysInMonth(month, year), - $year & "-" & $ord(month) & "-" & $monthday & " is not a valid date" + $year & "-" & intToStr(ord(month), 2) & "-" & $monthday & " is not a valid date" proc toEpochDay(monthday: MonthdayRange, month: Month, year: int): int64 = ## Get the epoch day from a year/month/day date. @@ -209,7 +490,7 @@ proc fromEpochDay(epochday: int64): tuple[monthday: MonthdayRange, month: Month, proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int): YeardayRange {.tags: [], raises: [], benign .} = ## Returns the day of the year. - ## Equivalent with ``initDateTime(day, month, year).yearday``. + ## Equivalent with ``initDateTime(monthday, month, year, 0, 0, 0).yearday``. assertValidDate monthday, month, year const daysUntilMonth: array[Month, int] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] const daysUntilMonthLeap: array[Month, int] = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335] @@ -221,63 +502,268 @@ proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int): YeardayRan proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay {.tags: [], raises: [], benign .} = ## Returns the day of the week enum from day, month and year. - ## Equivalent with ``initDateTime(day, month, year).weekday``. + ## Equivalent with ``initDateTime(monthday, month, year, 0, 0, 0).weekday``. assertValidDate monthday, month, year # 1970-01-01 is a Thursday, we adjust to the previous Monday let days = toEpochday(monthday, month, year) - 3 - let weeks = (if days >= 0: days else: days - 6) div 7 + let weeks = floorDiv(days, 7) let wd = days - weeks * 7 # The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc. # so we must correct for the WeekDay type. result = if wd == 0: dSun else: WeekDay(wd - 1) -# Forward declarations -proc utcZoneInfoFromUtc(time: Time): ZonedTime {.tags: [], raises: [], benign .} -proc utcZoneInfoFromTz(adjTime: Time): ZonedTime {.tags: [], raises: [], benign .} -proc localZoneInfoFromUtc(time: Time): ZonedTime {.tags: [], raises: [], benign .} -proc localZoneInfoFromTz(adjTime: Time): ZonedTime {.tags: [], raises: [], benign .} - -proc `-`*(a, b: Time): int64 {. - rtl, extern: "ntDiffTime", tags: [], raises: [], noSideEffect, benign, deprecated.} = - ## Computes the difference of two calendar times. Result is in seconds. - ## This is deprecated because it will need to change when sub second time resolution is implemented. - ## Use ``a.toUnix - b.toUnix`` instead. + +{. pragma: operator, rtl, noSideEffect, benign .} + +template subImpl[T: Duration|Time](a: Duration|Time, b: Duration|Time): T = + normalize[T](a.seconds - b.seconds, a.nanosecond - b.nanosecond) + +template addImpl[T: Duration|Time](a: Duration|Time, b: Duration|Time): T = + normalize[T](a.seconds + b.seconds, a.nanosecond + b.nanosecond) + +template ltImpl(a: Duration|Time, b: Duration|Time): bool = + a.seconds < b.seconds or ( + a.seconds == b.seconds and a.nanosecond < b.nanosecond) + +template lqImpl(a: Duration|Time, b: Duration|Time): bool = + a.seconds < b.seconds or ( + a.seconds == b.seconds and a.nanosecond <= b.nanosecond) + +template eqImpl(a: Duration|Time, b: Duration|Time): bool = + a.seconds == b.seconds and a.nanosecond == b.nanosecond + +proc initDuration*(nanoseconds, microseconds, milliseconds, + seconds, minutes, hours, days, weeks: int64 = 0): Duration = + runnableExamples: + let dur = initDuration(seconds = 1, milliseconds = 1) + doAssert dur.milliseconds == 1 + doAssert dur.seconds == 1 + + let seconds = convert(Weeks, Seconds, weeks) + + convert(Days, Seconds, days) + + convert(Minutes, Seconds, minutes) + + convert(Hours, Seconds, hours) + + convert(Seconds, Seconds, seconds) + + convert(Milliseconds, Seconds, milliseconds) + + convert(Microseconds, Seconds, microseconds) + + convert(Nanoseconds, Seconds, nanoseconds) + let nanoseconds = (convert(Milliseconds, Nanoseconds, milliseconds mod 1000) + + convert(Microseconds, Nanoseconds, microseconds mod 1_000_000) + + nanoseconds mod 1_000_000_000).int + # Nanoseconds might be negative so we must normalize. + result = normalize[Duration](seconds, nanoseconds) + +const DurationZero* = initDuration() ## \ + ## Zero value for durations. Useful for comparisons. ## ## .. code-block:: nim - ## let a = fromSeconds(1_000_000_000) - ## let b = fromSeconds(1_500_000_000) - ## echo initInterval(seconds=int(b - a)) - ## # (milliseconds: 0, seconds: 20, minutes: 53, hours: 0, days: 5787, months: 0, years: 0) - a.toUnix - b.toUnix - -proc `<`*(a, b: Time): bool {. - rtl, extern: "ntLtTime", tags: [], raises: [], noSideEffect, borrow.} + ## + ## doAssert initDuration(seconds = 1) > DurationZero + ## doAssert initDuration(seconds = 0) == DurationZero + +proc toParts*(dur: Duration): DurationParts = + ## Converts a duration into an array consisting of fixed time units. + ## + ## Each value in the array gives information about a specific unit of + ## time, for example ``result[Days]`` gives a count of days. + ## + ## This procedure is useful for converting ``Duration`` values to strings. + runnableExamples: + var dp = toParts(initDuration(weeks=2, days=1)) + doAssert dp[Days] == 1 + doAssert dp[Weeks] == 2 + dp = toParts(initDuration(days = -1)) + doAssert dp[Days] == -1 + + var remS = dur.seconds + var remNs = dur.nanosecond.int + + # Ensure the same sign for seconds and nanoseconds + if remS < 0 and remNs != 0: + remNs -= convert(Seconds, Nanoseconds, 1) + remS.inc 1 + + for unit in countdown(Weeks, Seconds): + let quantity = convert(Seconds, unit, remS) + remS = remS mod convert(unit, Seconds, 1) + + result[unit] = quantity + + for unit in countdown(Milliseconds, Nanoseconds): + let quantity = convert(Nanoseconds, unit, remNs) + remNs = remNs mod convert(unit, Nanoseconds, 1) + + result[unit] = quantity + +proc stringifyUnit(value: int | int64, unit: TimeUnit): string = + ## Stringify time unit with it's name, lowercased + let strUnit = $unit + result = "" + result.add($value) + result.add(" ") + if abs(value) != 1: + result.add(strUnit.toLowerAscii()) + else: + result.add(strUnit[0..^2].toLowerAscii()) + +proc humanizeParts(parts: seq[string]): string = + ## Make date string parts human-readable + result = "" + if parts.len == 0: + result.add "0 nanoseconds" + elif parts.len == 1: + result = parts[0] + elif parts.len == 2: + result = parts[0] & " and " & parts[1] + else: + for i in 0..high(parts)-1: + result.add parts[i] & ", " + result.add "and " & parts[high(parts)] + +proc `$`*(dur: Duration): string = + ## Human friendly string representation of ``Duration``. + runnableExamples: + doAssert $initDuration(seconds = 2) == "2 seconds" + doAssert $initDuration(weeks = 1, days = 2) == "1 week and 2 days" + doAssert $initDuration(hours = 1, minutes = 2, seconds = 3) == "1 hour, 2 minutes, and 3 seconds" + doAssert $initDuration(milliseconds = -1500) == "-1 second and -500 milliseconds" + var parts = newSeq[string]() + var numParts = toParts(dur) + + for unit in countdown(Weeks, Nanoseconds): + let quantity = numParts[unit] + if quantity != 0.int64: + parts.add(stringifyUnit(quantity, unit)) + + result = humanizeParts(parts) + +proc `+`*(a, b: Duration): Duration {.operator.} = + ## Add two durations together. + runnableExamples: + doAssert initDuration(seconds = 1) + initDuration(days = 1) == + initDuration(seconds = 1, days = 1) + addImpl[Duration](a, b) + +proc `-`*(a, b: Duration): Duration {.operator.} = + ## Subtract a duration from another. + runnableExamples: + doAssert initDuration(seconds = 1, days = 1) - initDuration(seconds = 1) == + initDuration(days = 1) + subImpl[Duration](a, b) + +proc `-`*(a: Duration): Duration {.operator.} = + ## Reverse a duration. + runnableExamples: + doAssert -initDuration(seconds = 1) == initDuration(seconds = -1) + normalize[Duration](-a.seconds, -a.nanosecond) + +proc `<`*(a, b: Duration): bool {.operator.} = + ## Note that a duration can be negative, + ## so even if ``a < b`` is true ``a`` might + ## represent a larger absolute duration. + ## Use ``abs(a) < abs(b)`` to compare the absolute + ## duration. + runnableExamples: + doAssert initDuration(seconds = 1) < initDuration(seconds = 2) + doAssert initDuration(seconds = -2) < initDuration(seconds = 1) + ltImpl(a, b) + +proc `<=`*(a, b: Duration): bool {.operator.} = + lqImpl(a, b) + +proc `==`*(a, b: Duration): bool {.operator.} = + eqImpl(a, b) + +proc `*`*(a: int64, b: Duration): Duration {.operator} = + ## Multiply a duration by some scalar. + runnableExamples: + doAssert 5 * initDuration(seconds = 1) == initDuration(seconds = 5) + normalize[Duration](a * b.seconds, a * b.nanosecond) + +proc `*`*(a: Duration, b: int64): Duration {.operator} = + ## Multiply a duration by some scalar. + runnableExamples: + doAssert initDuration(seconds = 1) * 5 == initDuration(seconds = 5) + b * a + +proc `div`*(a: Duration, b: int64): Duration {.operator} = + ## Integer division for durations. + runnableExamples: + doAssert initDuration(seconds = 3) div 2 == initDuration(milliseconds = 1500) + doAssert initDuration(nanoseconds = 3) div 2 == initDuration(nanoseconds = 1) + let carryOver = convert(Seconds, Nanoseconds, a.seconds mod b) + normalize[Duration](a.seconds div b, (a.nanosecond + carryOver) div b) + +proc initTime*(unix: int64, nanosecond: NanosecondRange): Time = + ## Create a ``Time`` from a unix timestamp and a nanosecond part. + result.seconds = unix + result.nanosecond = nanosecond + +proc `-`*(a, b: Time): Duration {.operator, extern: "ntDiffTime".} = + ## Computes the duration between two points in time. + subImpl[Duration](a, b) + +proc `+`*(a: Time, b: Duration): Time {.operator, extern: "ntAddTime".} = + ## Add a duration of time to a ``Time``. + runnableExamples: + doAssert (fromUnix(0) + initDuration(seconds = 1)) == fromUnix(1) + addImpl[Time](a, b) + +proc `-`*(a: Time, b: Duration): Time {.operator, extern: "ntSubTime".} = + ## Subtracts a duration of time from a ``Time``. + runnableExamples: + doAssert (fromUnix(0) - initDuration(seconds = 1)) == fromUnix(-1) + subImpl[Time](a, b) + +proc `<`*(a, b: Time): bool {.operator, extern: "ntLtTime".} = ## Returns true iff ``a < b``, that is iff a happened before b. + ltImpl(a, b) -proc `<=` * (a, b: Time): bool {. - rtl, extern: "ntLeTime", tags: [], raises: [], noSideEffect, borrow.} +proc `<=` * (a, b: Time): bool {.operator, extern: "ntLeTime".} = ## Returns true iff ``a <= b``. + lqImpl(a, b) -proc `==`*(a, b: Time): bool {. - rtl, extern: "ntEqTime", tags: [], raises: [], noSideEffect, borrow.} +proc `==`*(a, b: Time): bool {.operator, extern: "ntEqTime".} = ## Returns true if ``a == b``, that is if both times represent the same point in time. + eqImpl(a, b) + +proc high*(typ: typedesc[Time]): Time = + initTime(high(int64), high(NanosecondRange)) + +proc low*(typ: typedesc[Time]): Time = + initTime(low(int64), 0) + +proc high*(typ: typedesc[Duration]): Duration = + ## Get the longest representable duration. + initDuration(seconds = high(int64), nanoseconds = high(NanosecondRange)) + +proc low*(typ: typedesc[Duration]): Duration = + ## Get the longest representable duration of negative direction. + initDuration(seconds = low(int64)) + +proc abs*(a: Duration): Duration = + runnableExamples: + doAssert initDuration(milliseconds = -1500).abs == + initDuration(milliseconds = 1500) + initDuration(seconds = abs(a.seconds), nanoseconds = -a.nanosecond) proc toTime*(dt: DateTime): Time {.tags: [], raises: [], benign.} = ## Converts a broken-down time structure to ## calendar time representation. let epochDay = toEpochday(dt.monthday, dt.month, dt.year) - result = Time(epochDay * secondsInDay) - result.inc dt.hour * secondsInHour - result.inc dt.minute * 60 - result.inc dt.second - # The code above ignores the UTC offset of `timeInfo`, - # so we need to compensate for that here. - result.inc dt.utcOffset + var seconds = epochDay * secondsInDay + seconds.inc dt.hour * secondsInHour + seconds.inc dt.minute * 60 + seconds.inc dt.second + seconds.inc dt.utcOffset + result = initTime(seconds, dt.nanosecond) proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime = - let adjTime = zt.adjTime.int64 - let epochday = (if adjTime >= 0: adjTime else: adjTime - (secondsInDay - 1)) div secondsInDay - var rem = zt.adjTime.int64 - epochday * secondsInDay + ## Create a new ``DateTime`` using ``ZonedTime`` in the specified timezone. + let adjTime = zt.time - initDuration(seconds = zt.utcOffset) + let s = adjTime.seconds + let epochday = floorDiv(s, secondsInDay) + var rem = s - epochday * secondsInDay let hour = rem div secondsInHour rem = rem - hour * secondsInHour let minute = rem div secondsInMin @@ -293,6 +779,7 @@ proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime = hour: hour, minute: minute, second: second, + nanosecond: zt.time.nanosecond, weekday: getDayOfWeek(d, m, y), yearday: getDayOfYear(d, m, y), isDst: zt.isDst, @@ -300,14 +787,55 @@ proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime = utcOffset: zt.utcOffset ) -proc inZone*(time: Time, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = - ## Break down ``time`` into a ``DateTime`` using ``zone`` as the timezone. - let zoneInfo = zone.zoneInfoFromUtc(time) - result = initDateTime(zoneInfo, zone) +proc newTimezone*( + name: string, + zonedTimeFromTimeImpl: proc (time: Time): ZonedTime {.tags: [], raises: [], benign.}, + zonedTimeFromAdjTimeImpl: proc (adjTime: Time): ZonedTime {.tags: [], raises: [], benign.} + ): Timezone = + ## Create a new ``Timezone``. + ## + ## ``zonedTimeFromTimeImpl`` and ``zonedTimeFromAdjTimeImpl`` is used + ## as the underlying implementations for ``zonedTimeFromTime`` and + ## ``zonedTimeFromAdjTime``. + ## + ## If possible, the name parameter should match the name used in the + ## tz database. If the timezone doesn't exist in the tz database, or if the + ## timezone name is unknown, then any string that describes the timezone + ## unambiguously can be used. Note that the timezones name is used for + ## checking equality! + runnableExamples: + proc utcTzInfo(time: Time): ZonedTime = + ZonedTime(utcOffset: 0, isDst: false, time: time) + let utc = newTimezone("Etc/UTC", utcTzInfo, utcTzInfo) + Timezone( + name: name, + zonedTimeFromTimeImpl: zonedTimeFromTimeImpl, + zonedTimeFromAdjTimeImpl: zonedTimeFromAdjTimeImpl + ) -proc inZone*(dt: DateTime, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = - ## Convert ``dt`` into a ``DateTime`` using ``zone`` as the timezone. - dt.toTime.inZone(zone) +proc name*(zone: Timezone): string = + ## The name of the timezone. + ## + ## If possible, the name will be the name used in the tz database. + ## If the timezone doesn't exist in the tz database, or if the timezone + ## name is unknown, then any string that describes the timezone + ## unambiguously might be used. For example, the string "LOCAL" is used + ## for the systems local timezone. + ## + ## See also: https://en.wikipedia.org/wiki/Tz_database + zone.name + +proc zonedTimeFromTime*(zone: Timezone, time: Time): ZonedTime = + ## Returns the ``ZonedTime`` for some point in time. + zone.zonedTimeFromTimeImpl(time) + +proc zonedTimeFromAdjTime*(zone: TimeZone, adjTime: Time): ZonedTime = + ## Returns the ``ZonedTime`` for some local time. + ## + ## Note that the ``Time`` argument does not represent a point in time, it + ## represent a local time! E.g if ``adjTime`` is ``fromUnix(0)``, it should be + ## interpreted as 1970-01-01T00:00:00 in the ``zone`` timezone, not in UTC. + zone.zonedTimeFromAdjTimeImpl(adjTime) proc `$`*(zone: Timezone): string = ## Returns the name of the timezone. @@ -315,14 +843,27 @@ proc `$`*(zone: Timezone): string = proc `==`*(zone1, zone2: Timezone): bool = ## Two ``Timezone``'s are considered equal if their name is equal. + runnableExamples: + doAssert local() == local() + doAssert local() != utc() zone1.name == zone2.name +proc inZone*(time: Time, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = + ## Convert ``time`` into a ``DateTime`` using ``zone`` as the timezone. + result = initDateTime(zone.zonedTimeFromTime(time), zone) + +proc inZone*(dt: DateTime, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = + ## Returns a ``DateTime`` representing the same point in time as ``dt`` but + ## using ``zone`` as the timezone. + dt.toTime.inZone(zone) + proc toAdjTime(dt: DateTime): Time = let epochDay = toEpochday(dt.monthday, dt.month, dt.year) - result = Time(epochDay * secondsInDay) - result.inc dt.hour * secondsInHour - result.inc dt.minute * secondsInMin - result.inc dt.second + var seconds = epochDay * secondsInDay + seconds.inc dt.hour * secondsInHour + seconds.inc dt.minute * secondsInMin + seconds.inc dt.second + result = initTime(seconds, dt.nanosecond) when defined(JS): type JsDate = object @@ -350,15 +891,15 @@ when defined(JS): proc getYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} proc setFullYear(js: JsDate, year: int): void {.tags: [], raises: [], benign, importcpp.} - proc localZoneInfoFromUtc(time: Time): ZonedTime = - let jsDate = newDate(time.float * 1000) + proc localZonedTimeFromTime(time: Time): ZonedTime = + let jsDate = newDate(time.seconds.float * 1000) let offset = jsDate.getTimezoneOffset() * secondsInMin - result.adjTime = Time(time.int64 - offset) + result.time = time result.utcOffset = offset result.isDst = false - proc localZoneInfoFromTz(adjTime: Time): ZonedTime = - let utcDate = newDate(adjTime.float * 1000) + proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime = + let utcDate = newDate(adjTime.seconds.float * 1000) let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), utcDate.getUTCDate(), utcDate.getUTCHours(), utcDate.getUTCMinutes(), utcDate.getUTCSeconds(), 0) @@ -368,8 +909,8 @@ when defined(JS): if utcDate.getUTCFullYear() in 0 .. 99: localDate.setFullYear(utcDate.getUTCFullYear()) - result.adjTime = adjTime result.utcOffset = localDate.getTimezoneOffset() * secondsInMin + result.time = adjTime + initDuration(seconds = result.utcOffset) result.isDst = false else: @@ -399,7 +940,7 @@ else: weekday {.importc: "tm_wday".}, yearday {.importc: "tm_yday".}, isdst {.importc: "tm_isdst".}: cint - when defined(linux) and defined(amd64): + when defined(linux) and defined(amd64) or defined(haiku): gmtoff {.importc: "tm_gmtoff".}: clong zone {.importc: "tm_zone".}: cstring type @@ -407,82 +948,88 @@ else: proc localtime(timer: ptr CTime): StructTmPtr {. importc: "localtime", header: "<time.h>", tags: [].} - proc toAdjTime(tm: StructTm): Time = + proc toAdjUnix(tm: StructTm): int64 = let epochDay = toEpochday(tm.monthday, (tm.month + 1).Month, tm.year.int + 1900) - result = Time(epochDay * secondsInDay) + result = epochDay * secondsInDay result.inc tm.hour * secondsInHour result.inc tm.minute * 60 result.inc tm.second - proc getStructTm(time: Time | int64): StructTm = - let timei64 = time.int64 - var a = - if timei64 < low(CTime): - CTime(low(CTime)) - elif timei64 > high(CTime): - CTime(high(CTime)) - else: - CTime(timei64) - result = localtime(addr(a))[] - - proc localZoneInfoFromUtc(time: Time): ZonedTime = - let tm = getStructTm(time) - let adjTime = tm.toAdjTime - result.adjTime = adjTime - result.utcOffset = (time.toUnix - adjTime.toUnix).int - result.isDst = tm.isdst > 0 - - proc localZoneInfoFromTz(adjTime: Time): ZonedTime = - var adjTimei64 = adjTime.int64 - let past = adjTimei64 - secondsInDay - var tm = getStructTm(past) - let pastOffset = past - tm.toAdjTime.int64 - - let future = adjTimei64 + secondsInDay - tm = getStructTm(future) - let futureOffset = future - tm.toAdjTime.int64 + proc getLocalOffsetAndDst(unix: int64): tuple[offset: int, dst: bool] = + # Windows can't handle unix < 0, so we fall back to unix = 0. + # FIXME: This should be improved by falling back to the WinAPI instead. + when defined(windows): + if unix < 0: + var a = 0.CTime + let tmPtr = localtime(addr(a)) + if not tmPtr.isNil: + let tm = tmPtr[] + return ((0 - tm.toAdjUnix).int, false) + return (0, false) + + var a = unix.CTime + let tmPtr = localtime(addr(a)) + if not tmPtr.isNil: + let tm = tmPtr[] + return ((unix - tm.toAdjUnix).int, tm.isdst > 0) + return (0, false) + + proc localZonedTimeFromTime(time: Time): ZonedTime = + let (offset, dst) = getLocalOffsetAndDst(time.seconds) + result.time = time + result.utcOffset = offset + result.isDst = dst + + proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime = + var adjUnix = adjTime.seconds + let past = adjUnix - secondsInDay + let (pastOffset, _) = getLocalOffsetAndDst(past) + + let future = adjUnix + secondsInDay + let (futureOffset, _) = getLocalOffsetAndDst(future) var utcOffset: int if pastOffset == futureOffset: utcOffset = pastOffset.int else: if pastOffset > futureOffset: - adjTimei64 -= secondsInHour + adjUnix -= secondsInHour - adjTimei64 += pastOffset - utcOffset = (adjTimei64 - getStructTm(adjTimei64).toAdjTime.int64).int + adjUnix += pastOffset + utcOffset = getLocalOffsetAndDst(adjUnix).offset # This extra roundtrip is needed to normalize any impossible datetimes # as a result of offset changes (normally due to dst) - let utcTime = adjTime.int64 + utcOffset - tm = getStructTm(utcTime) - result.adjTime = tm.toAdjTime - result.utcOffset = (utcTime - result.adjTime.int64).int - result.isDst = tm.isdst > 0 + let utcUnix = adjTime.seconds + utcOffset + let (finalOffset, dst) = getLocalOffsetAndDst(utcUnix) + result.time = initTime(utcUnix, adjTime.nanosecond) + result.utcOffset = finalOffset + result.isDst = dst -proc utcZoneInfoFromUtc(time: Time): ZonedTime = - result.adjTime = time - result.utcOffset = 0 - result.isDst = false +proc utcTzInfo(time: Time): ZonedTime = + ZonedTime(utcOffset: 0, isDst: false, time: time) -proc utcZoneInfoFromTz(adjTime: Time): ZonedTime = - utcZoneInfoFromUtc(adjTime) # adjTime == time since we are in UTC +var utcInstance {.threadvar.}: Timezone +var localInstance {.threadvar.}: Timezone proc utc*(): TimeZone = ## Get the ``Timezone`` implementation for the UTC timezone. - ## - ## .. code-block:: nim - ## doAssert now().utc.timezone == utc() - ## doAssert utc().name == "Etc/UTC" - Timezone(zoneInfoFromUtc: utcZoneInfoFromUtc, zoneInfoFromTz: utcZoneInfoFromTz, name: "Etc/UTC") + runnableExamples: + doAssert now().utc.timezone == utc() + doAssert utc().name == "Etc/UTC" + if utcInstance.isNil: + utcInstance = newTimezone("Etc/UTC", utcTzInfo, utcTzInfo) + result = utcInstance proc local*(): TimeZone = ## Get the ``Timezone`` implementation for the local timezone. - ## - ## .. code-block:: nim - ## doAssert now().timezone == local() - ## doAssert local().name == "LOCAL" - Timezone(zoneInfoFromUtc: localZoneInfoFromUtc, zoneInfoFromTz: localZoneInfoFromTz, name: "LOCAL") + runnableExamples: + doAssert now().timezone == local() + doAssert local().name == "LOCAL" + if localInstance.isNil: + localInstance = newTimezone("LOCAL", localZonedTimeFromTime, + localZonedTimeFromAdjTime) + result = localInstance proc utc*(dt: DateTime): DateTime = ## Shorthand for ``dt.inZone(utc())``. @@ -500,9 +1047,27 @@ proc local*(t: Time): DateTime = ## Shorthand for ``t.inZone(local())``. t.inZone(local()) -proc getTime*(): Time {.tags: [TimeEffect], benign.} - ## Gets the current time as a ``Time`` with second resolution. Use epochTime for higher - ## resolution. +proc getTime*(): Time {.tags: [TimeEffect], benign.} = + ## Gets the current time as a ``Time`` with nanosecond resolution. + when defined(JS): + let millis = newDate().getTime() + let seconds = convert(Milliseconds, Seconds, millis) + let nanos = convert(Milliseconds, Nanoseconds, + millis mod convert(Seconds, Milliseconds, 1).int) + result = initTime(seconds, nanos) + # I'm not entirely certain if freebsd needs to use `gettimeofday`. + elif defined(macosx) or defined(freebsd): + var a: Timeval + gettimeofday(a) + result = initTime(a.tv_sec.int64, convert(Microseconds, Nanoseconds, a.tv_usec.int)) + elif defined(posix): + var ts: Timespec + discard clock_gettime(CLOCK_REALTIME, ts) + result = initTime(ts.tv_sec.int64, ts.tv_nsec.int) + elif defined(windows): + var f: FILETIME + getSystemTimeAsFileTime(f) + result = fromWinTime(rdFileTime(f)) proc now*(): DateTime {.tags: [TimeEffect], benign.} = ## Get the current time as a ``DateTime`` in the local timezone. @@ -510,51 +1075,57 @@ proc now*(): DateTime {.tags: [TimeEffect], benign.} = ## Shorthand for ``getTime().local``. getTime().local -proc initInterval*(milliseconds, seconds, minutes, hours, days, months, - years: int = 0): TimeInterval = +proc initTimeInterval*(nanoseconds, microseconds, milliseconds, + seconds, minutes, hours, + days, weeks, months, years: int = 0): TimeInterval = ## Creates a new ``TimeInterval``. ## ## You can also use the convenience procedures called ``milliseconds``, ## ``seconds``, ``minutes``, ``hours``, ``days``, ``months``, and ``years``. ## - ## Example: - ## - ## .. code-block:: nim - ## - ## let day = initInterval(hours=24) - ## let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) - ## doAssert $(dt + day) == "2000-01-02T12-00-00+00:00" + runnableExamples: + let day = initTimeInterval(hours=24) + let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) + doAssert $(dt + day) == "2000-01-02T12:00:00Z" + result.nanoseconds = nanoseconds + result.microseconds = microseconds result.milliseconds = milliseconds result.seconds = seconds result.minutes = minutes result.hours = hours result.days = days + result.weeks = weeks result.months = months result.years = years proc `+`*(ti1, ti2: TimeInterval): TimeInterval = ## Adds two ``TimeInterval`` objects together. + result.nanoseconds = ti1.nanoseconds + ti2.nanoseconds + result.microseconds = ti1.microseconds + ti2.microseconds result.milliseconds = ti1.milliseconds + ti2.milliseconds result.seconds = ti1.seconds + ti2.seconds result.minutes = ti1.minutes + ti2.minutes result.hours = ti1.hours + ti2.hours result.days = ti1.days + ti2.days + result.weeks = ti1.weeks + ti2.weeks result.months = ti1.months + ti2.months result.years = ti1.years + ti2.years proc `-`*(ti: TimeInterval): TimeInterval = ## Reverses a time interval - ## - ## .. code-block:: nim - ## - ## let day = -initInterval(hours=24) - ## echo day # -> (milliseconds: 0, seconds: 0, minutes: 0, hours: -24, days: 0, months: 0, years: 0) + runnableExamples: + let day = -initTimeInterval(hours=24) + doAssert day.hours == -24 + result = TimeInterval( + nanoseconds: -ti.nanoseconds, + microseconds: -ti.microseconds, milliseconds: -ti.milliseconds, seconds: -ti.seconds, minutes: -ti.minutes, hours: -ti.hours, days: -ti.days, + weeks: -ti.weeks, months: -ti.months, years: -ti.years ) @@ -562,48 +1133,179 @@ proc `-`*(ti: TimeInterval): TimeInterval = proc `-`*(ti1, ti2: TimeInterval): TimeInterval = ## Subtracts TimeInterval ``ti1`` from ``ti2``. ## - ## Time components are compared one-by-one, see output: - ## - ## .. code-block:: nim - ## let a = fromUnix(1_000_000_000) - ## let b = fromUnix(1_500_000_000) - ## echo b.toTimeInterval - a.toTimeInterval - ## # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: 5, months: -2, years: 16) + ## Time components are subtracted one-by-one, see output: + runnableExamples: + let ti1 = initTimeInterval(hours=24) + let ti2 = initTimeInterval(hours=4) + doAssert (ti1 - ti2) == initTimeInterval(hours=20) + result = ti1 + (-ti2) -proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDiff, absDiff: int64] = - ## Evaluates how many seconds the interval is worth - ## in the context of ``dt``. - ## The result in split into an adjusted diff and an absolute diff. +proc getDateStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} = + ## Gets the current date as a string of the format ``YYYY-MM-DD``. + var ti = now() + result = $ti.year & '-' & intToStr(ord(ti.month), 2) & + '-' & intToStr(ti.monthday, 2) - var anew = dt - var newinterv = interval +proc getClockStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} = + ## Gets the current clock time as a string of the format ``HH:MM:SS``. + var ti = now() + result = intToStr(ti.hour, 2) & ':' & intToStr(ti.minute, 2) & + ':' & intToStr(ti.second, 2) + +proc toParts* (ti: TimeInterval): TimeIntervalParts = + ## Converts a `TimeInterval` into an array consisting of its time units, + ## starting with nanoseconds and ending with years + ## + ## This procedure is useful for converting ``TimeInterval`` values to strings. + ## E.g. then you need to implement custom interval printing + runnableExamples: + var tp = toParts(initTimeInterval(years=1, nanoseconds=123)) + doAssert tp[Years] == 1 + doAssert tp[Nanoseconds] == 123 + + var index = 0 + for name, value in fieldPairs(ti): + result[index.TimeUnit()] = value + index += 1 + +proc `$`*(ti: TimeInterval): string = + ## Get string representation of `TimeInterval` + runnableExamples: + doAssert $initTimeInterval(years=1, nanoseconds=123) == "1 year and 123 nanoseconds" + doAssert $initTimeInterval() == "0 nanoseconds" + + var parts: seq[string] = @[] + var tiParts = toParts(ti) + for unit in countdown(Years, Nanoseconds): + if tiParts[unit] != 0: + parts.add(stringifyUnit(tiParts[unit], unit)) + + result = humanizeParts(parts) + +proc nanoseconds*(nanos: int): TimeInterval {.inline.} = + ## TimeInterval of ``nanos`` nanoseconds. + initTimeInterval(nanoseconds = nanos) + +proc microseconds*(micros: int): TimeInterval {.inline.} = + ## TimeInterval of ``micros`` microseconds. + initTimeInterval(microseconds = micros) + +proc milliseconds*(ms: int): TimeInterval {.inline.} = + ## TimeInterval of ``ms`` milliseconds. + initTimeInterval(milliseconds = ms) + +proc seconds*(s: int): TimeInterval {.inline.} = + ## TimeInterval of ``s`` seconds. + ## + ## ``echo getTime() + 5.second`` + initTimeInterval(seconds = s) + +proc minutes*(m: int): TimeInterval {.inline.} = + ## TimeInterval of ``m`` minutes. + ## + ## ``echo getTime() + 5.minutes`` + initTimeInterval(minutes = m) + +proc hours*(h: int): TimeInterval {.inline.} = + ## TimeInterval of ``h`` hours. + ## + ## ``echo getTime() + 2.hours`` + initTimeInterval(hours = h) - newinterv.months += interval.years * 12 - var curMonth = anew.month +proc days*(d: int): TimeInterval {.inline.} = + ## TimeInterval of ``d`` days. + ## + ## ``echo getTime() + 2.days`` + initTimeInterval(days = d) + +proc weeks*(w: int): TimeInterval {.inline.} = + ## TimeInterval of ``w`` weeks. + ## + ## ``echo getTime() + 2.weeks`` + initTimeInterval(weeks = w) + +proc months*(m: int): TimeInterval {.inline.} = + ## TimeInterval of ``m`` months. + ## + ## ``echo getTime() + 2.months`` + initTimeInterval(months = m) + +proc years*(y: int): TimeInterval {.inline.} = + ## TimeInterval of ``y`` years. + ## + ## ``echo getTime() + 2.years`` + initTimeInterval(years = y) + +proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDur, absDur: Duration] = + ## Evaluates how many nanoseconds the interval is worth + ## in the context of ``dt``. + ## The result in split into an adjusted diff and an absolute diff. + var months = interval.years * 12 + interval.months + var curYear = dt.year + var curMonth = dt.month # Subtracting - if newinterv.months < 0: - for mth in countDown(-1 * newinterv.months, 1): + if months < 0: + for mth in countDown(-1 * months, 1): if curMonth == mJan: curMonth = mDec - anew.year.dec() + curYear.dec else: curMonth.dec() - result.adjDiff -= getDaysInMonth(curMonth, anew.year) * secondsInDay + let days = getDaysInMonth(curMonth, curYear) + result.adjDur = result.adjDur - initDuration(days = days) # Adding else: - for mth in 1 .. newinterv.months: - result.adjDiff += getDaysInMonth(curMonth, anew.year) * secondsInDay + for mth in 1 .. months: + let days = getDaysInMonth(curMonth, curYear) + result.adjDur = result.adjDur + initDuration(days = days) if curMonth == mDec: curMonth = mJan - anew.year.inc() + curYear.inc else: curMonth.inc() - result.adjDiff += newinterv.days * secondsInDay - result.absDiff += newinterv.hours * secondsInHour - result.absDiff += newinterv.minutes * secondsInMin - result.absDiff += newinterv.seconds - result.absDiff += newinterv.milliseconds div 1000 + + result.adjDur = result.adjDur + initDuration( + days = interval.days, + weeks = interval.weeks) + result.absDur = initDuration( + nanoseconds = interval.nanoseconds, + microseconds = interval.microseconds, + milliseconds = interval.milliseconds, + seconds = interval.seconds, + minutes = interval.minutes, + hours = interval.hours) + + +proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, + hour: HourRange, minute: MinuteRange, second: SecondRange, + nanosecond: NanosecondRange, zone: Timezone = local()): DateTime = + ## Create a new ``DateTime`` in the specified timezone. + runnableExamples: + let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, 00, utc()) + doAssert $dt1 == "2017-03-30T00:00:00Z" + + assertValidDate monthday, month, year + let dt = DateTime( + monthday: monthday, + year: year, + month: month, + hour: hour, + minute: minute, + second: second, + nanosecond: nanosecond + ) + result = initDateTime(zone.zonedTimeFromAdjTime(dt.toAdjTime), zone) + +proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, + hour: HourRange, minute: MinuteRange, second: SecondRange, + zone: Timezone = local()): DateTime = + ## Create a new ``DateTime`` in the specified timezone. + runnableExamples: + let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) + doAssert $dt1 == "2017-03-30T00:00:00Z" + initDateTime(monthday, month, year, hour, minute, second, 0, zone) + proc `+`*(dt: DateTime, interval: TimeInterval): DateTime = ## Adds ``interval`` to ``dt``. Components from ``interval`` are added @@ -615,618 +1317,970 @@ proc `+`*(dt: DateTime, interval: TimeInterval): DateTime = ## set to the number of days overflowed. So adding one month to `31 October` will result in `31 November`, ## which will overflow and result in `1 December`. ## - ## .. code-block:: nim - ## let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) - ## doAssert $(dt + 1.months) == "2017-04-30T00:00:00+00:00" - ## # This is correct and happens due to monthday overflow. - ## doAssert $(dt - 1.months) == "2017-03-02T00:00:00+00:00" - let (adjDiff, absDiff) = evaluateInterval(dt, interval) - - if adjDiff.int64 != 0: - let zInfo = dt.timezone.zoneInfoFromTz(Time(dt.toAdjTime.int64 + adjDiff)) - - if absDiff != 0: - let time = Time(zInfo.adjTime.int64 + zInfo.utcOffset + absDiff) - result = initDateTime(dt.timezone.zoneInfoFromUtc(time), dt.timezone) + runnableExamples: + let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) + doAssert $(dt + 1.months) == "2017-04-30T00:00:00Z" + # This is correct and happens due to monthday overflow. + doAssert $(dt - 1.months) == "2017-03-02T00:00:00Z" + let (adjDur, absDur) = evaluateInterval(dt, interval) + + if adjDur != DurationZero: + var zt = dt.timezone.zonedTimeFromAdjTime(dt.toAdjTime + adjDur) + if absDur != DurationZero: + zt = dt.timezone.zonedTimeFromTime(zt.time + absDur) + result = initDateTime(zt, dt.timezone) else: - result = initDateTime(zInfo, dt.timezone) + result = initDateTime(zt, dt.timezone) else: - result = initDateTime(dt.timezone.zoneInfoFromUtc(Time(dt.toTime.int64 + absDiff)), dt.timezone) + var zt = dt.timezone.zonedTimeFromTime(dt.toTime + absDur) + result = initDateTime(zt, dt.timezone) proc `-`*(dt: DateTime, interval: TimeInterval): DateTime = ## Subtract ``interval`` from ``dt``. Components from ``interval`` are subtracted ## in the order of their size, i.e first the ``years`` component, then the ``months`` ## component and so on. The returned ``DateTime`` will have the same timezone as the input. + runnableExamples: + let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) + doAssert $(dt - 5.days) == "2017-03-25T00:00:00Z" + dt + (-interval) -proc getDateStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} = - ## Gets the current date as a string of the format ``YYYY-MM-DD``. - var ti = now() - result = $ti.year & '-' & intToStr(ord(ti.month), 2) & - '-' & intToStr(ti.monthday, 2) +proc `+`*(dt: DateTime, dur: Duration): DateTime = + runnableExamples: + let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) + let dur = initDuration(hours = 5) + doAssert $(dt + dur) == "2017-03-30T05:00:00Z" -proc getClockStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} = - ## Gets the current clock time as a string of the format ``HH:MM:SS``. - var ti = now() - result = intToStr(ti.hour, 2) & ':' & intToStr(ti.minute, 2) & - ':' & intToStr(ti.second, 2) + (dt.toTime + dur).inZone(dt.timezone) -proc `$`*(day: WeekDay): string = - ## Stringify operator for ``WeekDay``. - const lookup: array[WeekDay, string] = ["Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday", "Sunday"] - return lookup[day] +proc `-`*(dt: DateTime, dur: Duration): DateTime = + runnableExamples: + let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) + let dur = initDuration(days = 5) + doAssert $(dt - dur) == "2017-03-25T00:00:00Z" -proc `$`*(m: Month): string = - ## Stringify operator for ``Month``. - const lookup: array[Month, string] = ["January", "February", "March", - "April", "May", "June", "July", "August", "September", "October", - "November", "December"] - return lookup[m] + (dt.toTime - dur).inZone(dt.timezone) -proc milliseconds*(ms: int): TimeInterval {.inline.} = - ## TimeInterval of `ms` milliseconds - ## - ## Note: not all time procedures have millisecond resolution - initInterval(milliseconds = ms) +proc `-`*(dt1, dt2: DateTime): Duration = + ## Compute the duration between ``dt1`` and ``dt2``. + runnableExamples: + let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) + let dt2 = initDateTime(25, mMar, 2017, 00, 00, 00, utc()) -proc seconds*(s: int): TimeInterval {.inline.} = - ## TimeInterval of `s` seconds - ## - ## ``echo getTime() + 5.second`` - initInterval(seconds = s) + doAssert dt1 - dt2 == initDuration(days = 5) -proc minutes*(m: int): TimeInterval {.inline.} = - ## TimeInterval of `m` minutes - ## - ## ``echo getTime() + 5.minutes`` - initInterval(minutes = m) + dt1.toTime - dt2.toTime -proc hours*(h: int): TimeInterval {.inline.} = - ## TimeInterval of `h` hours - ## - ## ``echo getTime() + 2.hours`` - initInterval(hours = h) +proc `<`*(a, b: DateTime): bool = + ## Returns true iff ``a < b``, that is iff a happened before b. + return a.toTime < b.toTime -proc days*(d: int): TimeInterval {.inline.} = - ## TimeInterval of `d` days - ## - ## ``echo getTime() + 2.days`` - initInterval(days = d) +proc `<=` * (a, b: DateTime): bool = + ## Returns true iff ``a <= b``. + return a.toTime <= b.toTime -proc months*(m: int): TimeInterval {.inline.} = - ## TimeInterval of `m` months - ## - ## ``echo getTime() + 2.months`` - initInterval(months = m) +proc `==`*(a, b: DateTime): bool = + ## Returns true if ``a == b``, that is if both dates represent the same point in time. + return a.toTime == b.toTime -proc years*(y: int): TimeInterval {.inline.} = - ## TimeInterval of `y` years - ## - ## ``echo getTime() + 2.years`` - initInterval(years = y) -proc `+=`*(time: var Time, interval: TimeInterval) = - ## Modifies `time` by adding `interval`. - time = toTime(time.local + interval) +proc isStaticInterval(interval: TimeInterval): bool = + interval.years == 0 and interval.months == 0 and + interval.days == 0 and interval.weeks == 0 -proc `+`*(time: Time, interval: TimeInterval): Time = - ## Adds `interval` to `time` - ## by converting to a ``DateTime`` in the local timezone, - ## adding the interval, and converting back to ``Time``. +proc evaluateStaticInterval(interval: TimeInterval): Duration = + assert interval.isStaticInterval + initDuration(nanoseconds = interval.nanoseconds, + microseconds = interval.microseconds, + milliseconds = interval.milliseconds, + seconds = interval.seconds, + minutes = interval.minutes, + hours = interval.hours) + +proc between*(startDt, endDt: DateTime): TimeInterval = + ## Evaluate difference between two dates in ``TimeInterval`` format, so, it + ## will be relative. ## - ## ``echo getTime() + 1.day`` - result = toTime(time.local + interval) + ## **Warning:** It's not recommended to use ``between`` for ``DateTime's`` in + ## different ``TimeZone's``. + ## ``a + between(a, b) == b`` is only guaranteed when ``a`` and ``b`` are in UTC. + runnableExamples: + var a = initDateTime(year = 2018, month = Month(3), monthday = 25, + hour = 0, minute = 59, second = 59, nanosecond = 1, + zone = utc()).local + var b = initDateTime(year = 2018, month = Month(3), monthday = 25, + hour = 1, minute = 1, second = 1, nanosecond = 0, + zone = utc()).local + doAssert between(a, b) == initTimeInterval( + nanoseconds=999, milliseconds=999, microseconds=999, seconds=1, minutes=1) + + a = parse("2018-01-09T00:00:00+00:00", "yyyy-MM-dd'T'HH:mm:sszzz", utc()) + b = parse("2018-01-10T23:00:00-02:00", "yyyy-MM-dd'T'HH:mm:sszzz") + doAssert between(a, b) == initTimeInterval(hours=1, days=2) + ## Though, here correct answer should be 1 day 25 hours (cause this day in + ## this tz is actually 26 hours). That's why operating different TZ is + ## discouraged + + var startDt = startDt.utc() + var endDt = endDt.utc() + + if endDt == startDt: + return initTimeInterval() + elif endDt < startDt: + return -between(endDt, startDt) + + var coeffs: array[FixedTimeUnit, int64] = unitWeights + var timeParts: array[FixedTimeUnit, int] + for unit in Nanoseconds..Weeks: + timeParts[unit] = 0 + + for unit in Seconds..Days: + coeffs[unit] = coeffs[unit] div unitWeights[Seconds] + + var startTimepart = initTime( + nanosecond = startDt.nanosecond, + unix = startDt.hour * coeffs[Hours] + startDt.minute * coeffs[Minutes] + + startDt.second + ) + var endTimepart = initTime( + nanosecond = endDt.nanosecond, + unix = endDt.hour * coeffs[Hours] + endDt.minute * coeffs[Minutes] + + endDt.second + ) + # We wand timeParts for Seconds..Hours be positive, so we'll borrow one day + if endTimepart < startTimepart: + timeParts[Days] = -1 + + let diffTime = endTimepart - startTimepart + timeParts[Seconds] = diffTime.seconds.int() + #Nanoseconds - preliminary count + timeParts[Nanoseconds] = diffTime.nanoseconds + for unit in countdown(Milliseconds, Microseconds): + timeParts[unit] += timeParts[Nanoseconds] div coeffs[unit].int() + timeParts[Nanoseconds] -= timeParts[unit] * coeffs[unit].int() + + #Counting Seconds .. Hours - final, Days - preliminary + for unit in countdown(Days, Minutes): + timeParts[unit] += timeParts[Seconds] div coeffs[unit].int() + # Here is accounted the borrowed day + timeParts[Seconds] -= timeParts[unit] * coeffs[unit].int() + + # Set Nanoseconds .. Hours in result + result.nanoseconds = timeParts[Nanoseconds] + result.microseconds = timeParts[Microseconds] + result.milliseconds = timeParts[Milliseconds] + result.seconds = timeParts[Seconds] + result.minutes = timeParts[Minutes] + result.hours = timeParts[Hours] + + #Days + if endDt.monthday.int + timeParts[Days] < startDt.monthday.int(): + if endDt.month > 1.Month: + endDt.month -= 1.Month + else: + endDt.month = 12.Month + endDt.year -= 1 + timeParts[Days] += endDt.monthday.int() + getDaysInMonth( + endDt.month, endDt.year) - startDt.monthday.int() + else: + timeParts[Days] += endDt.monthday.int() - + startDt.monthday.int() -proc `-=`*(time: var Time, interval: TimeInterval) = - ## Modifies `time` by subtracting `interval`. - time = toTime(time.local - interval) + result.days = timeParts[Days] -proc `-`*(time: Time, interval: TimeInterval): Time = - ## Subtracts `interval` from Time `time`. - ## - ## ``echo getTime() - 1.day`` - result = toTime(time.local - interval) + #Months + if endDt.month < startDt.month: + result.months = endDt.month.int() + 12 - startDt.month.int() + endDt.year -= 1 + else: + result.months = endDt.month.int() - + startDt.month.int() -proc formatToken(dt: DateTime, token: string, buf: var string) = - ## Helper of the format proc to parse individual tokens. - ## - ## Pass the found token in the user input string, and the buffer where the - ## final string is being built. This has to be a var value because certain - ## formatting tokens require modifying the previous characters. - case token - of "d": - buf.add($dt.monthday) - of "dd": - if dt.monthday < 10: - buf.add("0") - buf.add($dt.monthday) - of "ddd": - buf.add(($dt.weekday)[0 .. 2]) - of "dddd": - buf.add($dt.weekday) - of "h": - buf.add($(if dt.hour > 12: dt.hour - 12 else: dt.hour)) - of "hh": - let amerHour = if dt.hour > 12: dt.hour - 12 else: dt.hour - if amerHour < 10: - buf.add('0') - buf.add($amerHour) - of "H": - buf.add($dt.hour) - of "HH": - if dt.hour < 10: - buf.add('0') - buf.add($dt.hour) - of "m": - buf.add($dt.minute) - of "mm": - if dt.minute < 10: - buf.add('0') - buf.add($dt.minute) - of "M": - buf.add($ord(dt.month)) - of "MM": - if dt.month < mOct: - buf.add('0') - buf.add($ord(dt.month)) - of "MMM": - buf.add(($dt.month)[0..2]) - of "MMMM": - buf.add($dt.month) - of "s": - buf.add($dt.second) - of "ss": - if dt.second < 10: - buf.add('0') - buf.add($dt.second) - of "t": - if dt.hour >= 12: - buf.add('P') - else: buf.add('A') - of "tt": - if dt.hour >= 12: - buf.add("PM") - else: buf.add("AM") - of "y": - var fr = ($dt.year).len()-1 - if fr < 0: fr = 0 - buf.add(($dt.year)[fr .. ($dt.year).len()-1]) - of "yy": - var fr = ($dt.year).len()-2 - if fr < 0: fr = 0 - var fyear = ($dt.year)[fr .. ($dt.year).len()-1] - if fyear.len != 2: fyear = repeat('0', 2-fyear.len()) & fyear - buf.add(fyear) - of "yyy": - var fr = ($dt.year).len()-3 - if fr < 0: fr = 0 - var fyear = ($dt.year)[fr .. ($dt.year).len()-1] - if fyear.len != 3: fyear = repeat('0', 3-fyear.len()) & fyear - buf.add(fyear) - of "yyyy": - var fr = ($dt.year).len()-4 - if fr < 0: fr = 0 - var fyear = ($dt.year)[fr .. ($dt.year).len()-1] - if fyear.len != 4: fyear = repeat('0', 4-fyear.len()) & fyear - buf.add(fyear) - of "yyyyy": - var fr = ($dt.year).len()-5 - if fr < 0: fr = 0 - var fyear = ($dt.year)[fr .. ($dt.year).len()-1] - if fyear.len != 5: fyear = repeat('0', 5-fyear.len()) & fyear - buf.add(fyear) - of "z": - let - nonDstTz = dt.utcOffset - hours = abs(nonDstTz) div secondsInHour - if nonDstTz <= 0: buf.add('+') - else: buf.add('-') - buf.add($hours) - of "zz": - let - nonDstTz = dt.utcOffset - hours = abs(nonDstTz) div secondsInHour - if nonDstTz <= 0: buf.add('+') - else: buf.add('-') - if hours < 10: buf.add('0') - buf.add($hours) - of "zzz": - let - nonDstTz = dt.utcOffset - hours = abs(nonDstTz) div secondsInHour - minutes = (abs(nonDstTz) div secondsInMin) mod minutesInHour - if nonDstTz <= 0: buf.add('+') - else: buf.add('-') - if hours < 10: buf.add('0') - buf.add($hours) - buf.add(':') - if minutes < 10: buf.add('0') - buf.add($minutes) - of "": - discard + # Years + result.years = endDt.year - startDt.year + +proc `+`*(time: Time, interval: TimeInterval): Time = + ## Adds `interval` to `time`. + ## If `interval` contains any years, months, weeks or days the operation + ## is performed in the local timezone. + runnableExamples: + let tm = fromUnix(0) + doAssert tm + 5.seconds == fromUnix(5) + + if interval.isStaticInterval: + time + evaluateStaticInterval(interval) else: - raise newException(ValueError, "Invalid format string: " & token) + toTime(time.local + interval) +proc `-`*(time: Time, interval: TimeInterval): Time = + ## Subtracts `interval` from Time `time`. + ## If `interval` contains any years, months, weeks or days the operation + ## is performed in the local timezone. + runnableExamples: + let tm = fromUnix(5) + doAssert tm - 5.seconds == fromUnix(0) + + if interval.isStaticInterval: + time - evaluateStaticInterval(interval) + else: + toTime(time.local - interval) + +proc `+=`*[T, U: TimesMutableTypes](a: var T, b: U) = + ## Modify ``a`` in place by adding ``b``. + runnableExamples: + var tm = fromUnix(0) + tm += initDuration(seconds = 1) + doAssert tm == fromUnix(1) + a = a + b + +proc `-=`*[T, U: TimesMutableTypes](a: var T, b: U) = + ## Modify ``a`` in place by subtracting ``b``. + runnableExamples: + var tm = fromUnix(5) + tm -= initDuration(seconds = 5) + doAssert tm == fromUnix(0) + a = a - b + +proc `*=`*[T: TimesMutableTypes, U](a: var T, b: U) = + # Mutable type is often multiplied by number + runnableExamples: + var dur = initDuration(seconds = 1) + dur *= 5 + doAssert dur == initDuration(seconds = 5) + + a = a * b -proc format*(dt: DateTime, f: string): string {.tags: [].}= - ## This procedure formats `dt` as specified by `f`. The following format - ## specifiers are available: - ## - ## ========== ================================================================================= ================================================ - ## Specifier Description Example - ## ========== ================================================================================= ================================================ - ## d Numeric value of the day of the month, it will be one or two digits long. ``1/04/2012 -> 1``, ``21/04/2012 -> 21`` - ## dd Same as above, but always two digits. ``1/04/2012 -> 01``, ``21/04/2012 -> 21`` - ## ddd Three letter string which indicates the day of the week. ``Saturday -> Sat``, ``Monday -> Mon`` - ## dddd Full string for the day of the week. ``Saturday -> Saturday``, ``Monday -> Monday`` - ## h The hours in one digit if possible. Ranging from 0-12. ``5pm -> 5``, ``2am -> 2`` - ## hh The hours in two digits always. If the hour is one digit 0 is prepended. ``5pm -> 05``, ``11am -> 11`` - ## H The hours in one digit if possible, randing from 0-24. ``5pm -> 17``, ``2am -> 2`` - ## HH The hours in two digits always. 0 is prepended if the hour is one digit. ``5pm -> 17``, ``2am -> 02`` - ## m The minutes in 1 digit if possible. ``5:30 -> 30``, ``2:01 -> 1`` - ## mm Same as above but always 2 digits, 0 is prepended if the minute is one digit. ``5:30 -> 30``, ``2:01 -> 01`` - ## M The month in one digit if possible. ``September -> 9``, ``December -> 12`` - ## MM The month in two digits always. 0 is prepended. ``September -> 09``, ``December -> 12`` - ## MMM Abbreviated three-letter form of the month. ``September -> Sep``, ``December -> Dec`` - ## MMMM Full month string, properly capitalized. ``September -> September`` - ## s Seconds as one digit if possible. ``00:00:06 -> 6`` - ## ss Same as above but always two digits. 0 is prepended. ``00:00:06 -> 06`` - ## t ``A`` when time is in the AM. ``P`` when time is in the PM. - ## tt Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively. - ## y(yyyy) This displays the year to different digits. You most likely only want 2 or 4 'y's - ## yy Displays the year to two digits. ``2012 -> 12`` - ## yyyy Displays the year to four digits. ``2012 -> 2012`` - ## z Displays the timezone offset from UTC. ``GMT+7 -> +7``, ``GMT-5 -> -5`` - ## zz Same as above but with leading 0. ``GMT+7 -> +07``, ``GMT-5 -> -05`` - ## zzz Same as above but with ``:mm`` where *mm* represents minutes. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00`` - ## ========== ================================================================================= ================================================ - ## - ## Other strings can be inserted by putting them in ``''``. For example - ## ``hh'->'mm`` will give ``01->56``. The following characters can be - ## inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` - ## ``,``. However you don't need to necessarily separate format specifiers, a - ## unambiguous format string like ``yyyyMMddhhmmss`` is valid too. +# +# Parse & format implementation +# - result = "" +type + AmPm = enum + apUnknown, apAm, apPm + + Era = enum + eraUnknown, eraAd, eraBc + + ParsedTime = object + amPm: AmPm + era: Era + year: Option[int] + month: Option[int] + monthday: Option[int] + utcOffset: Option[int] + + # '0' as default for these work fine + # so no need for `Option`. + hour: int + minute: int + second: int + nanosecond: int + + FormatTokenKind = enum + tkPattern, tkLiteral + + FormatPattern {.pure.} = enum + d, dd, ddd, dddd + h, hh, H, HH + m, mm, M, MM, MMM, MMMM + s, ss + fff, ffffff, fffffffff + t, tt + y, yy, yyy, yyyy, yyyyy + YYYY + uuuu + UUUU + z, zz, zzz, zzzz + g + + # This is a special value used to mark literal format values. + # See the doc comment for ``TimeFormat.patterns``. + Lit + + TimeFormat* = object ## Represents a format for parsing and printing + ## time types. + patterns: seq[byte] ## \ + ## Contains the patterns encoded as bytes. + ## Literal values are encoded in a special way. + ## They start with ``Lit.byte``, then the length of the literal, then the + ## raw char values of the literal. For example, the literal `foo` would + ## be encoded as ``@[Lit.byte, 3.byte, 'f'.byte, 'o'.byte, 'o'.byte]``. + formatStr: string + +const FormatLiterals = { ' ', '-', '/', ':', '(', ')', '[', ']', ',' } + +proc `$`*(f: TimeFormat): string = + ## Returns the format string that was used to construct ``f``. + runnableExamples: + let f = initTimeFormat("yyyy-MM-dd") + doAssert $f == "yyyy-MM-dd" + f.formatStr + +proc raiseParseException(f: TimeFormat, input: string, msg: string) = + raise newException(ValueError, + &"Failed to parse '{input}' with format '{f}'. {msg}") + +iterator tokens(f: string): tuple[kind: FormatTokenKind, token: string] = var i = 0 - var currentF = "" - while true: - case f[i] - of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': - formatToken(dt, currentF, result) + var currToken = "" - currentF = "" - if f[i] == '\0': break + template yieldCurrToken() = + if currToken.len != 0: + yield (tkPattern, currToken) + currToken = "" - if f[i] == '\'': + while i < f.len: + case f[i] + of '\'': + yieldCurrToken() + if i.succ < f.len and f[i.succ] == '\'': + yield (tkLiteral, "'") + i.inc 2 + else: + var token = "" inc(i) # Skip ' - while f[i] != '\'' and f.len-1 > i: - result.add(f[i]) - inc(i) - else: result.add(f[i]) - + while i < f.len and f[i] != '\'': + token.add f[i] + i.inc + + if i > f.high: + raise newException(ValueError, + &"Unclosed ' in time format string. " & + "For a literal ', use ''.") + i.inc + yield (tkLiteral, token) + of FormatLiterals: + yieldCurrToken() + yield (tkLiteral, $f[i]) + i.inc else: # Check if the letter being added matches previous accumulated buffer. - if currentF.len < 1 or currentF[high(currentF)] == f[i]: - currentF.add(f[i]) + if currToken.len == 0 or currToken[0] == f[i]: + currToken.add(f[i]) + i.inc else: - formatToken(dt, currentF, result) - dec(i) # Move position back to re-process the character separately. - currentF = "" - - inc(i) - -proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} = - ## Converts a `DateTime` object to a string representation. - ## It uses the format ``yyyy-MM-dd'T'HH-mm-sszzz``. - try: - result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz") # todo: optimize this - except ValueError: assert false # cannot happen because format string is valid - -proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = - ## converts a `Time` value to a string representation. It will use the local - ## time zone and use the format ``yyyy-MM-dd'T'HH-mm-sszzz``. - $time.local - -{.pop.} - -proc parseToken(dt: var DateTime; token, value: string; j: var int) = - ## Helper of the parse proc to parse individual tokens. - - # Overwrite system.`[]` to raise a ValueError on index out of bounds. - proc `[]`[T, U](s: string, x: HSlice[T, U]): string = - if x.a >= s.len or x.b >= s.len: - raise newException(ValueError, "Value is missing required tokens, got: " & - s) - return system.`[]`(s, x) - - var sv: int - case token - of "d": - var pd = parseInt(value[j..j+1], sv) - dt.monthday = sv - j += pd - of "dd": - dt.monthday = value[j..j+1].parseInt() - j += 2 - of "ddd": - case value[j..j+2].toLowerAscii() - of "sun": dt.weekday = dSun - of "mon": dt.weekday = dMon - of "tue": dt.weekday = dTue - of "wed": dt.weekday = dWed - of "thu": dt.weekday = dThu - of "fri": dt.weekday = dFri - of "sat": dt.weekday = dSat + yield (tkPattern, currToken) + currToken = $f[i] + i.inc + + yieldCurrToken() + +proc stringToPattern(str: string): FormatPattern = + case str + of "d": result = d + of "dd": result = dd + of "ddd": result = ddd + of "dddd": result = dddd + of "h": result = h + of "hh": result = hh + of "H": result = H + of "HH": result = HH + of "m": result = m + of "mm": result = mm + of "M": result = M + of "MM": result = MM + of "MMM": result = MMM + of "MMMM": result = MMMM + of "s": result = s + of "ss": result = ss + of "fff": result = fff + of "ffffff": result = ffffff + of "fffffffff": result = fffffffff + of "t": result = t + of "tt": result = tt + of "y": result = y + of "yy": result = yy + of "yyy": result = yyy + of "yyyy": result = yyyy + of "yyyyy": result = yyyyy + of "YYYY": result = YYYY + of "uuuu": result = uuuu + of "UUUU": result = UUUU + of "z": result = z + of "zz": result = zz + of "zzz": result = zzz + of "zzzz": result = zzzz + of "g": result = g + else: raise newException(ValueError, &"'{str}' is not a valid pattern") + +proc initTimeFormat*(format: string): TimeFormat = + ## Construct a new time format for parsing & formatting time types. + ## + ## See `Parsing and formatting dates`_ for documentation of the + ## ``format`` argument. + runnableExamples: + let f = initTimeFormat("yyyy-MM-dd") + doAssert "2000-01-01" == "2000-01-01".parse(f).format(f) + result.formatStr = format + result.patterns = @[] + for kind, token in format.tokens: + case kind + of tkLiteral: + case token + else: + result.patterns.add(FormatPattern.Lit.byte) + if token.len > 255: + raise newException(ValueError, + "Format literal is to long:" & token) + result.patterns.add(token.len.byte) + for c in token: + result.patterns.add(c.byte) + of tkPattern: + result.patterns.add(stringToPattern(token).byte) + +proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) = + template yearOfEra(dt: DateTime): int = + if dt.year <= 0: abs(dt.year) + 1 else: dt.year + + case pattern + of d: + result.add $dt.monthday + of dd: + result.add dt.monthday.intToStr(2) + of ddd: + result.add ($dt.weekday)[0..2] + of dddd: + result.add $dt.weekday + of h: + result.add( + if dt.hour == 0: "12" + elif dt.hour > 12: $(dt.hour - 12) + else: $dt.hour + ) + of hh: + result.add( + if dt.hour == 0: "12" + elif dt.hour > 12: (dt.hour - 12).intToStr(2) + else: dt.hour.intToStr(2) + ) + of H: + result.add $dt.hour + of HH: + result.add dt.hour.intToStr(2) + of m: + result.add $dt.minute + of mm: + result.add dt.minute.intToStr(2) + of M: + result.add $ord(dt.month) + of MM: + result.add ord(dt.month).intToStr(2) + of MMM: + result.add ($dt.month)[0..2] + of MMMM: + result.add $dt.month + of s: + result.add $dt.second + of ss: + result.add dt.second.intToStr(2) + of fff: + result.add(intToStr(convert(Nanoseconds, Milliseconds, dt.nanosecond), 3)) + of ffffff: + result.add(intToStr(convert(Nanoseconds, Microseconds, dt.nanosecond), 6)) + of fffffffff: + result.add(intToStr(dt.nanosecond, 9)) + of t: + result.add if dt.hour >= 12: "P" else: "A" + of tt: + result.add if dt.hour >= 12: "PM" else: "AM" + of y: # Deprecated + result.add $(dt.yearOfEra mod 10) + of yy: + result.add (dt.yearOfEra mod 100).intToStr(2) + of yyy: # Deprecated + result.add (dt.yearOfEra mod 1000).intToStr(3) + of yyyy: + let year = dt.yearOfEra + if year < 10000: + result.add year.intToStr(4) + else: + result.add '+' & $year + of yyyyy: # Deprecated + result.add (dt.yearOfEra mod 100_000).intToStr(5) + of YYYY: + if dt.year < 1: + result.add $(abs(dt.year) + 1) else: - raise newException(ValueError, - "Couldn't parse day of week (ddd), got: " & value[j..j+2]) - j += 3 - of "dddd": - if value.len >= j+6 and value[j..j+5].cmpIgnoreCase("sunday") == 0: - dt.weekday = dSun - j += 6 - elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("monday") == 0: - dt.weekday = dMon - j += 6 - elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("tuesday") == 0: - dt.weekday = dTue - j += 7 - elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("wednesday") == 0: - dt.weekday = dWed - j += 9 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("thursday") == 0: - dt.weekday = dThu - j += 8 - elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("friday") == 0: - dt.weekday = dFri - j += 6 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("saturday") == 0: - dt.weekday = dSat - j += 8 + result.add $dt.year + of uuuu: + let year = dt.year + if year < 10000 or year < 0: + result.add year.intToStr(4) else: - raise newException(ValueError, - "Couldn't parse day of week (dddd), got: " & value) - of "h", "H": - var pd = parseInt(value[j..j+1], sv) - dt.hour = sv - j += pd - of "hh", "HH": - dt.hour = value[j..j+1].parseInt() - j += 2 - of "m": - var pd = parseInt(value[j..j+1], sv) - dt.minute = sv - j += pd - of "mm": - dt.minute = value[j..j+1].parseInt() - j += 2 - of "M": - var pd = parseInt(value[j..j+1], sv) - dt.month = sv.Month - j += pd - of "MM": - var month = value[j..j+1].parseInt() - j += 2 - dt.month = month.Month - of "MMM": - case value[j..j+2].toLowerAscii(): - of "jan": dt.month = mJan - of "feb": dt.month = mFeb - of "mar": dt.month = mMar - of "apr": dt.month = mApr - of "may": dt.month = mMay - of "jun": dt.month = mJun - of "jul": dt.month = mJul - of "aug": dt.month = mAug - of "sep": dt.month = mSep - of "oct": dt.month = mOct - of "nov": dt.month = mNov - of "dec": dt.month = mDec + result.add '+' & $year + of UUUU: + result.add $dt.year + of z, zz, zzz, zzzz: + if dt.timezone.name == "Etc/UTC": + result.add 'Z' + else: + result.add if -dt.utcOffset >= 0: '+' else: '-' + let absOffset = abs(dt.utcOffset) + case pattern: + of z: + result.add $(absOffset div 3600) + of zz: + result.add (absOffset div 3600).intToStr(2) + of zzz: + let h = (absOffset div 3600).intToStr(2) + let m = ((absOffset div 60) mod 60).intToStr(2) + result.add h & ":" & m + of zzzz: + let absOffset = abs(dt.utcOffset) + let h = (absOffset div 3600).intToStr(2) + let m = ((absOffset div 60) mod 60).intToStr(2) + let s = (absOffset mod 60).intToStr(2) + result.add h & ":" & m & ":" & s + else: assert false + of g: + result.add if dt.year < 1: "BC" else: "AD" + of Lit: assert false # Can't happen + +proc parsePattern(input: string, pattern: FormatPattern, i: var int, + parsed: var ParsedTime): bool = + template takeInt(allowedWidth: Slice[int]): int = + var sv: int + let max = i + allowedWidth.b - 1 + var pd = + if max > input.high: + parseInt(input, sv, i) + else: + parseInt(input[i..max], sv) + if pd notin allowedWidth: + return false + i.inc pd + sv + + template contains[T](t: typedesc[T], i: int): bool = + i in low(t)..high(t) + + result = true + + case pattern + of d: + parsed.monthday = some(takeInt(1..2)) + result = parsed.monthday.get() in MonthdayRange + of dd: + parsed.monthday = some(takeInt(2..2)) + result = parsed.monthday.get() in MonthdayRange + of ddd: + result = input.substr(i, i+2).toLowerAscii() in [ + "sun", "mon", "tue", "wed", "thu", "fri", "sat"] + if result: + i.inc 3 + of dddd: + if input.substr(i, i+5).cmpIgnoreCase("sunday") == 0: + i.inc 6 + elif input.substr(i, i+5).cmpIgnoreCase("monday") == 0: + i.inc 6 + elif input.substr(i, i+6).cmpIgnoreCase("tuesday") == 0: + i.inc 7 + elif input.substr(i, i+8).cmpIgnoreCase("wednesday") == 0: + i.inc 9 + elif input.substr(i, i+7).cmpIgnoreCase("thursday") == 0: + i.inc 8 + elif input.substr(i, i+5).cmpIgnoreCase("friday") == 0: + i.inc 6 + elif input.substr(i, i+7).cmpIgnoreCase("saturday") == 0: + i.inc 8 else: - raise newException(ValueError, - "Couldn't parse month (MMM), got: " & value) - j += 3 - of "MMMM": - if value.len >= j+7 and value[j..j+6].cmpIgnoreCase("january") == 0: - dt.month = mJan - j += 7 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("february") == 0: - dt.month = mFeb - j += 8 - elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("march") == 0: - dt.month = mMar - j += 5 - elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("april") == 0: - dt.month = mApr - j += 5 - elif value.len >= j+3 and value[j..j+2].cmpIgnoreCase("may") == 0: - dt.month = mMay - j += 3 - elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("june") == 0: - dt.month = mJun - j += 4 - elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("july") == 0: - dt.month = mJul - j += 4 - elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("august") == 0: - dt.month = mAug - j += 6 - elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("september") == 0: - dt.month = mSep - j += 9 - elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("october") == 0: - dt.month = mOct - j += 7 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("november") == 0: - dt.month = mNov - j += 8 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("december") == 0: - dt.month = mDec - j += 8 + result = false + of h, H: + parsed.hour = takeInt(1..2) + result = parsed.hour in HourRange + of hh, HH: + parsed.hour = takeInt(2..2) + result = parsed.hour in HourRange + of m: + parsed.minute = takeInt(1..2) + result = parsed.hour in MinuteRange + of mm: + parsed.minute = takeInt(2..2) + result = parsed.hour in MinuteRange + of M: + let month = takeInt(1..2) + result = month in 1..12 + parsed.month = some(month) + of MM: + let month = takeInt(2..2) + result = month in 1..12 + parsed.month = some(month) + of MMM: + case input.substr(i, i+2).toLowerAscii() + of "jan": parsed.month = some(1) + of "feb": parsed.month = some(2) + of "mar": parsed.month = some(3) + of "apr": parsed.month = some(4) + of "may": parsed.month = some(5) + of "jun": parsed.month = some(6) + of "jul": parsed.month = some(7) + of "aug": parsed.month = some(8) + of "sep": parsed.month = some(9) + of "oct": parsed.month = some(10) + of "nov": parsed.month = some(11) + of "dec": parsed.month = some(12) else: - raise newException(ValueError, - "Couldn't parse month (MMMM), got: " & value) - of "s": - var pd = parseInt(value[j..j+1], sv) - dt.second = sv - j += pd - of "ss": - dt.second = value[j..j+1].parseInt() - j += 2 - of "t": - if value[j] == 'P' and dt.hour > 0 and dt.hour < 12: - dt.hour += 12 - j += 1 - of "tt": - if value[j..j+1] == "PM" and dt.hour > 0 and dt.hour < 12: - dt.hour += 12 - j += 2 - of "yy": + result = false + if result: + i.inc 3 + of MMMM: + if input.substr(i, i+6).cmpIgnoreCase("january") == 0: + parsed.month = some(1) + i.inc 7 + elif input.substr(i, i+7).cmpIgnoreCase("february") == 0: + parsed.month = some(2) + i.inc 8 + elif input.substr(i, i+4).cmpIgnoreCase("march") == 0: + parsed.month = some(3) + i.inc 5 + elif input.substr(i, i+4).cmpIgnoreCase("april") == 0: + parsed.month = some(4) + i.inc 5 + elif input.substr(i, i+2).cmpIgnoreCase("may") == 0: + parsed.month = some(5) + i.inc 3 + elif input.substr(i, i+3).cmpIgnoreCase("june") == 0: + parsed.month = some(6) + i.inc 4 + elif input.substr(i, i+3).cmpIgnoreCase("july") == 0: + parsed.month = some(7) + i.inc 4 + elif input.substr(i, i+5).cmpIgnoreCase("august") == 0: + parsed.month = some(8) + i.inc 6 + elif input.substr(i, i+8).cmpIgnoreCase("september") == 0: + parsed.month = some(9) + i.inc 9 + elif input.substr(i, i+6).cmpIgnoreCase("october") == 0: + parsed.month = some(10) + i.inc 7 + elif input.substr(i, i+7).cmpIgnoreCase("november") == 0: + parsed.month = some(11) + i.inc 8 + elif input.substr(i, i+7).cmpIgnoreCase("december") == 0: + parsed.month = some(12) + i.inc 8 + else: + result = false + of s: + parsed.second = takeInt(1..2) + of ss: + parsed.second = takeInt(2..2) + of fff, ffffff, fffffffff: + let len = ($pattern).len + let v = takeInt(len..len) + parsed.nanosecond = v * 10^(9 - len) + result = parsed.nanosecond in NanosecondRange + of t: + case input[i]: + of 'P': + parsed.amPm = apPm + of 'A': + parsed.amPm = apAm + else: + result = false + i.inc 1 + of tt: + if input.substr(i, i+1).cmpIgnoreCase("AM") == 0: + parsed.amPm = apAM + i.inc 2 + elif input.substr(i, i+1).cmpIgnoreCase("PM") == 0: + parsed.amPm = apPm + i.inc 2 + else: + result = false + of yy: # Assumes current century - var year = value[j..j+1].parseInt() + var year = takeInt(2..2) var thisCen = now().year div 100 - dt.year = thisCen*100 + year - j += 2 - of "yyyy": - dt.year = value[j..j+3].parseInt() - j += 4 - of "z": - dt.isDst = false - if value[j] == '+': - dt.utcOffset = 0 - parseInt($value[j+1]) * secondsInHour - elif value[j] == '-': - dt.utcOffset = parseInt($value[j+1]) * secondsInHour - elif value[j] == 'Z': - dt.utcOffset = 0 - j += 1 - return + parsed.year = some(thisCen*100 + year) + result = year > 0 + of yyyy: + let year = + if input[i] in { '+', '-' }: + takeInt(4..high(int)) + else: + takeInt(4..4) + result = year > 0 + parsed.year = some(year) + of YYYY: + let year = takeInt(1..high(int)) + parsed.year = some(year) + result = year > 0 + of uuuu: + let year = + if input[i] in { '+', '-' }: + takeInt(4..high(int)) + else: + takeInt(4..4) + parsed.year = some(year) + of UUUU: + parsed.year = some(takeInt(1..high(int))) + of z, zz, zzz, zzzz: + case input[i] + of '+', '-': + let sign = if input[i] == '-': 1 else: -1 + i.inc + var offset = 0 + case pattern + of z: + offset = takeInt(1..2) * -3600 + of zz: + offset = takeInt(2..2) * -3600 + of zzz: + offset.inc takeInt(2..2) * 3600 + if input[i] != ':': + return false + i.inc + offset.inc takeInt(2..2) * 60 + of zzzz: + offset.inc takeInt(2..2) * 3600 + if input[i] != ':': + return false + i.inc + offset.inc takeInt(2..2) * 60 + if input[i] != ':': + return false + i.inc + offset.inc takeInt(2..2) + else: assert false + parsed.utcOffset = some(offset * sign) + of 'Z': + parsed.utcOffset = some(0) + i.inc else: - raise newException(ValueError, - "Couldn't parse timezone offset (z), got: " & value[j]) - j += 2 - of "zz": - dt.isDst = false - if value[j] == '+': - dt.utcOffset = 0 - value[j+1..j+2].parseInt() * secondsInHour - elif value[j] == '-': - dt.utcOffset = value[j+1..j+2].parseInt() * secondsInHour - elif value[j] == 'Z': - dt.utcOffset = 0 - j += 1 - return + result = false + of g: + if input.substr(i, i+1).cmpIgnoreCase("BC") == 0: + parsed.era = eraBc + i.inc 2 + elif input.substr(i, i+1).cmpIgnoreCase("AD") == 0: + parsed.era = eraAd + i.inc 2 else: - raise newException(ValueError, - "Couldn't parse timezone offset (zz), got: " & value[j]) - j += 3 - of "zzz": - dt.isDst = false - var factor = 0 - if value[j] == '+': factor = -1 - elif value[j] == '-': factor = 1 - elif value[j] == 'Z': - dt.utcOffset = 0 - j += 1 - return + result = false + of y, yyy, yyyyy: + raise newException(ValueError, + &"The pattern '{pattern}' is only valid for formatting") + of Lit: assert false # Can't happen + +proc toDateTime(p: ParsedTime, zone: Timezone, f: TimeFormat, + input: string): DateTime = + var month = mJan + var year: int + var monthday: int + # `now()` is an expensive call, so we avoid it when possible + (year, month, monthday) = + if p.year.isNone or p.month.isNone or p.monthday.isNone: + let n = now() + (p.year.get(n.year), + p.month.get(n.month.int).Month, + p.monthday.get(n.monthday)) else: - raise newException(ValueError, - "Couldn't parse timezone offset (zzz), got: " & value[j]) - dt.utcOffset = factor * value[j+1..j+2].parseInt() * secondsInHour - j += 4 - dt.utcOffset += factor * value[j..j+1].parseInt() * 60 - j += 2 + (p.year.get(), p.month.get().Month, p.monthday.get()) + + year = + case p.era + of eraUnknown: + year + of eraBc: + if year < 1: + raiseParseException(f, input, + "Expected year to be positive " & + "(use 'UUUU' or 'uuuu' for negative years).") + -year + 1 + of eraAd: + if year < 1: + raiseParseException(f, input, + "Expected year to be positive " & + "(use 'UUUU' or 'uuuu' for negative years).") + year + + let hour = + case p.amPm + of apUnknown: + p.hour + of apAm: + if p.hour notin 1..12: + raiseParseException(f, input, + "AM/PM time must be in the interval 1..12") + if p.hour == 12: 0 else: p.hour + of apPm: + if p.hour notin 1..12: + raiseParseException(f, input, + "AM/PM time must be in the interval 1..12") + if p.hour == 12: p.hour else: p.hour + 12 + let minute = p.minute + let second = p.second + let nanosecond = p.nanosecond + + if monthday > getDaysInMonth(month, year): + raiseParseException(f, input, + $year & "-" & ord(month).intToStr(2) & + "-" & $monthday & " is not a valid date") + + result = DateTime( + year: year, month: month, monthday: monthday, + hour: hour, minute: minute, second: second, nanosecond: nanosecond + ) + + if p.utcOffset.isNone: + # No timezone parsed - assume timezone is `zone` + result = initDateTime(zone.zonedTimeFromAdjTime(result.toAdjTime), zone) else: - # Ignore the token and move forward in the value string by the same length - j += token.len + # Otherwise convert to `zone` + result.utcOffset = p.utcOffset.get() + result = result.toTime.inZone(zone) + +proc format*(dt: DateTime, f: TimeFormat): string {.raises: [].} = + ## Format ``dt`` using the format specified by ``f``. + runnableExamples: + let f = initTimeFormat("yyyy-MM-dd") + let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) + doAssert "2000-01-01" == dt.format(f) + var idx = 0 + while idx <= f.patterns.high: + case f.patterns[idx].FormatPattern + of Lit: + idx.inc + let len = f.patterns[idx] + for i in 1'u8..len: + idx.inc + result.add f.patterns[idx].char + idx.inc + else: + formatPattern(dt, f.patterns[idx].FormatPattern, result = result) + idx.inc -proc parse*(value, layout: string, zone: Timezone = local()): DateTime = - ## This procedure parses a date/time string using the standard format - ## identifiers as listed below. The procedure defaults information not provided - ## in the format string from the running program (month, year, etc). - ## - ## The return value will always be in the `zone` timezone. If no UTC offset was - ## parsed, then the input will be assumed to be specified in the `zone` timezone - ## already, so no timezone conversion will be done in that case. +proc format*(dt: DateTime, f: string): string = + ## Shorthand for constructing a ``TimeFormat`` and using it to format ``dt``. ## - ## ========== ================================================================================= ================================================ - ## Specifier Description Example - ## ========== ================================================================================= ================================================ - ## d Numeric value of the day of the month, it will be one or two digits long. ``1/04/2012 -> 1``, ``21/04/2012 -> 21`` - ## dd Same as above, but always two digits. ``1/04/2012 -> 01``, ``21/04/2012 -> 21`` - ## ddd Three letter string which indicates the day of the week. ``Saturday -> Sat``, ``Monday -> Mon`` - ## dddd Full string for the day of the week. ``Saturday -> Saturday``, ``Monday -> Monday`` - ## h The hours in one digit if possible. Ranging from 0-12. ``5pm -> 5``, ``2am -> 2`` - ## hh The hours in two digits always. If the hour is one digit 0 is prepended. ``5pm -> 05``, ``11am -> 11`` - ## H The hours in one digit if possible, randing from 0-24. ``5pm -> 17``, ``2am -> 2`` - ## HH The hours in two digits always. 0 is prepended if the hour is one digit. ``5pm -> 17``, ``2am -> 02`` - ## m The minutes in 1 digit if possible. ``5:30 -> 30``, ``2:01 -> 1`` - ## mm Same as above but always 2 digits, 0 is prepended if the minute is one digit. ``5:30 -> 30``, ``2:01 -> 01`` - ## M The month in one digit if possible. ``September -> 9``, ``December -> 12`` - ## MM The month in two digits always. 0 is prepended. ``September -> 09``, ``December -> 12`` - ## MMM Abbreviated three-letter form of the month. ``September -> Sep``, ``December -> Dec`` - ## MMMM Full month string, properly capitalized. ``September -> September`` - ## s Seconds as one digit if possible. ``00:00:06 -> 6`` - ## ss Same as above but always two digits. 0 is prepended. ``00:00:06 -> 06`` - ## t ``A`` when time is in the AM. ``P`` when time is in the PM. - ## tt Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively. - ## yy Displays the year to two digits. ``2012 -> 12`` - ## yyyy Displays the year to four digits. ``2012 -> 2012`` - ## z Displays the timezone offset from UTC. ``Z`` is parsed as ``+0`` ``GMT+7 -> +7``, ``GMT-5 -> -5`` - ## zz Same as above but with leading 0. ``GMT+7 -> +07``, ``GMT-5 -> -05`` - ## zzz Same as above but with ``:mm`` where *mm* represents minutes. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00`` - ## ========== ================================================================================= ================================================ + ## See `Parsing and formatting dates`_ for documentation of the + ## ``format`` argument. + runnableExamples: + let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) + doAssert "2000-01-01" == format(dt, "yyyy-MM-dd") + let dtFormat = initTimeFormat(f) + result = dt.format(dtFormat) + +proc format*(dt: DateTime, f: static[string]): string {.raises: [].} = + ## Overload that validates ``format`` at compile time. + const f2 = initTimeFormat(f) + result = dt.format(f2) + +proc format*(time: Time, f: string, zone: Timezone = local()): string {.tags: [].} = + ## Shorthand for constructing a ``TimeFormat`` and using it to format + ## ``time``. Will use the timezone specified by ``zone``. ## - ## Other strings can be inserted by putting them in ``''``. For example - ## ``hh'->'mm`` will give ``01->56``. The following characters can be - ## inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` - ## ``,``. However you don't need to necessarily separate format specifiers, a - ## unambiguous format string like ``yyyyMMddhhmmss`` is valid too. - var i = 0 # pointer for format string - var j = 0 # pointer for value string - var token = "" - # Assumes current day of month, month and year, but time is reset to 00:00:00. Weekday will be reset after parsing. - var dt = now() - dt.hour = 0 - dt.minute = 0 - dt.second = 0 - dt.isDst = true # using this is flag for checking whether a timezone has \ - # been read (because DST is always false when a tz is parsed) - while true: - case layout[i] - of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': - if token.len > 0: - parseToken(dt, token, value, j) - # Reset token - token = "" - # Break if at end of line - if layout[i] == '\0': break - # Skip separator and everything between single quotes - # These are literals in both the layout and the value string - if layout[i] == '\'': - inc(i) - while layout[i] != '\'' and layout.len-1 > i: - inc(i) - inc(j) - inc(i) - else: - inc(i) - inc(j) + ## See `Parsing and formatting dates`_ for documentation of the + ## ``f`` argument. + runnableExamples: + var dt = initDateTime(01, mJan, 1970, 00, 00, 00, utc()) + var tm = dt.toTime() + doAssert format(tm, "yyyy-MM-dd'T'HH:mm:ss", utc()) == "1970-01-01T00:00:00" + time.inZone(zone).format(f) + +proc format*(time: Time, f: static[string], + zone: Timezone = local()): string {.tags: [].} = + ## Overload that validates ``f`` at compile time. + const f2 = initTimeFormat(f) + result = time.inZone(zone).format(f2) + +proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime = + ## Parses ``input`` as a ``DateTime`` using the format specified by ``f``. + ## If no UTC offset was parsed, then ``input`` is assumed to be specified in + ## the ``zone`` timezone. If a UTC offset was parsed, the result will be + ## converted to the ``zone`` timezone. + runnableExamples: + let f = initTimeFormat("yyyy-MM-dd") + let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) + doAssert dt == "2000-01-01".parse(f, utc()) + var inpIdx = 0 # Input index + var patIdx = 0 # Pattern index + var parsed: ParsedTime + while inpIdx <= input.high and patIdx <= f.patterns.high: + let pattern = f.patterns[patIdx].FormatPattern + case pattern + of Lit: + patIdx.inc + let len = f.patterns[patIdx] + patIdx.inc + for _ in 1'u8..len: + if input[inpIdx] != f.patterns[patIdx].char: + raiseParseException(f, input, + "Unexpected character: " & input[inpIdx]) + inpIdx.inc + patIdx.inc else: - # Check if the letter being added matches previous accumulated buffer. - if token.len < 1 or token[high(token)] == layout[i]: - token.add(layout[i]) - inc(i) - else: - parseToken(dt, token, value, j) - token = "" + if not parsePattern(input, pattern, inpIdx, parsed): + raiseParseException(f, input, &"Failed on pattern '{pattern}'") + patIdx.inc - if dt.isDst: - # No timezone parsed - assume timezone is `zone` - result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone) - else: - # Otherwise convert to `zone` - result = dt.toTime.inZone(zone) + if inpIdx <= input.high: + raiseParseException(f, input, + "Parsing ended but there was still input remaining") + + if patIdx <= f.patterns.high: + raiseParseException(f, input, + "Parsing ended but there was still patterns remaining") + + result = toDateTime(parsed, zone, f, input) + +proc parse*(input, f: string, tz: Timezone = local()): DateTime = + ## Shorthand for constructing a ``TimeFormat`` and using it to parse + ## ``input`` as a ``DateTime``. + ## + ## See `Parsing and formatting dates`_ for documentation of the + ## ``f`` argument. + runnableExamples: + let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) + doAssert dt == parse("2000-01-01", "yyyy-MM-dd", utc()) + let dtFormat = initTimeFormat(f) + result = input.parse(dtFormat, tz) + +proc parse*(input: string, f: static[string], zone: Timezone = local()): DateTime = + ## Overload that validates ``f`` at compile time. + const f2 = initTimeFormat(f) + result = input.parse(f2, zone) + +proc parseTime*(input, f: string, zone: Timezone): Time = + ## Shorthand for constructing a ``TimeFormat`` and using it to parse + ## ``input`` as a ``DateTime``, then converting it a ``Time``. + ## + ## See `Parsing and formatting dates`_ for documentation of the + ## ``format`` argument. + runnableExamples: + let tStr = "1970-01-01T00:00:00+00:00" + doAssert parseTime(tStr, "yyyy-MM-dd'T'HH:mm:sszzz", utc()) == fromUnix(0) + parse(input, f, zone).toTime() + +proc parseTime*(input: string, f: static[string], zone: Timezone): Time = + ## Overload that validates ``format`` at compile time. + const f2 = initTimeFormat(f) + result = input.parse(f2, zone).toTime() + +# +# End of parse & format implementation +# + +proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} = + ## Converts a `DateTime` object to a string representation. + ## It uses the format ``yyyy-MM-dd'T'HH-mm-sszzz``. + runnableExamples: + let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) + doAssert $dt == "2000-01-01T12:00:00Z" + result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz") + +proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = + ## converts a `Time` value to a string representation. It will use the local + ## time zone and use the format ``yyyy-MM-dd'T'HH-mm-sszzz``. + runnableExamples: + let dt = initDateTime(01, mJan, 1970, 00, 00, 00, local()) + let tm = dt.toTime() + doAssert $tm == "1970-01-01T00:00:00" & format(dt, "zzz") + $time.local + +{.pop.} proc countLeapYears*(yearSpan: int): int = ## Returns the number of leap years spanned by a given number of years. @@ -1255,93 +2309,53 @@ proc countYearsAndDays*(daySpan: int): tuple[years: int, days: int] = proc toTimeInterval*(time: Time): TimeInterval = ## Converts a Time to a TimeInterval. ## - ## To be used when diffing times. - ## - ## .. code-block:: nim - ## let a = fromSeconds(1_000_000_000) - ## let b = fromSeconds(1_500_000_000) - ## echo a, " ", b # real dates - ## echo a.toTimeInterval # meaningless value, don't use it by itself - ## echo b.toTimeInterval - a.toTimeInterval - ## # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: 5, months: -2, years: 16) - # Milliseconds not available from Time + ## To be used when diffing times. Consider using `between` instead. + runnableExamples: + let a = fromUnix(10) + let b = fromUnix(1_500_000_000) + let ti = b.toTimeInterval() - a.toTimeInterval() + doAssert a + ti == b var dt = time.local - initInterval(0, dt.second, dt.minute, dt.hour, dt.monthday, dt.month.ord - 1, dt.year) - -proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, - hour: HourRange, minute: MinuteRange, second: SecondRange, zone: Timezone = local()): DateTime = - ## Create a new ``DateTime`` in the specified timezone. - assertValidDate monthday, month, year - doAssert monthday <= getDaysInMonth(month, year), "Invalid date: " & $month & " " & $monthday & ", " & $year - let dt = DateTime( - monthday: monthday, - year: year, - month: month, - hour: hour, - minute: minute, - second: second - ) - result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone) + initTimeInterval(dt.nanosecond, 0, 0, dt.second, dt.minute, dt.hour, + dt.monthday, 0, dt.month.ord - 1, dt.year) when not defined(JS): - proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} - ## gets time after the UNIX epoch (1970) in seconds. It is a float - ## because sub-second resolution is likely to be supported (depending - ## on the hardware/OS). - - proc cpuTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} - ## gets time spent that the CPU spent to run the current process in - ## seconds. This may be more useful for benchmarking than ``epochTime``. - ## However, it may measure the real time instead (depending on the OS). - ## The value of the result has no meaning. - ## To generate useful timing values, take the difference between - ## the results of two ``cpuTime`` calls: - ## - ## .. code-block:: nim - ## var t0 = cpuTime() - ## doWork() - ## echo "CPU time [s] ", cpuTime() - t0 - -when defined(JS): - proc getTime(): Time = - (newDate().getTime() div 1000).Time - - proc epochTime*(): float {.tags: [TimeEffect].} = - newDate().getTime() / 1000 - -else: type Clock {.importc: "clock_t".} = distinct int - proc timec(timer: ptr CTime): CTime {. - importc: "time", header: "<time.h>", tags: [].} - proc getClock(): Clock {.importc: "clock", header: "<time.h>", tags: [TimeEffect].} var clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int - proc getTime(): Time = - timec(nil).Time - - const - epochDiff = 116444736000000000'i64 - rateDiff = 10000000'i64 # 100 nsecs - - proc unixTimeToWinTime*(time: CTime): int64 = - ## converts a UNIX `Time` (``time_t``) to a Windows file time - result = int64(time) * rateDiff + epochDiff - - proc winTimeToUnixTime*(time: int64): CTime = - ## converts a Windows time to a UNIX `Time` (``time_t``) - result = CTime((time - epochDiff) div rateDiff) - when not defined(useNimRtl): - proc epochTime(): float = + proc cpuTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} = + ## gets time spent that the CPU spent to run the current process in + ## seconds. This may be more useful for benchmarking than ``epochTime``. + ## However, it may measure the real time instead (depending on the OS). + ## The value of the result has no meaning. + ## To generate useful timing values, take the difference between + ## the results of two ``cpuTime`` calls: + runnableExamples: + var t0 = cpuTime() + # some useless work here (calculate fibonacci) + var fib = @[0, 1, 1] + for i in 1..10: + fib.add(fib[^1] + fib[^2]) + echo "CPU time [s] ", cpuTime() - t0 + echo "Fib is [s] ", fib + result = toFloat(int(getClock())) / toFloat(clocksPerSec) + + proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} = + ## gets time after the UNIX epoch (1970) in seconds. It is a float + ## because sub-second resolution is likely to be supported (depending + ## on the hardware/OS). + ## + ## ``getTime`` should generally be prefered over this proc. when defined(posix): var a: Timeval - posix_gettimeofday(a) - result = toFloat(a.tv_sec) + toFloat(a.tv_usec)*0.00_0001 + gettimeofday(a) + result = toBiggestFloat(a.tv_sec.int64) + toFloat(a.tv_usec)*0.00_0001 elif defined(windows): var f: winlean.FILETIME getSystemTimeAsFileTime(f) @@ -1352,30 +2366,50 @@ else: else: {.error: "unknown OS".} - proc cpuTime(): float = - result = toFloat(int(getClock())) / toFloat(clocksPerSec) +when defined(JS): + proc epochTime*(): float {.tags: [TimeEffect].} = + newDate().getTime() / 1000 # Deprecated procs +when not defined(JS): + proc unixTimeToWinTime*(time: CTime): int64 {.deprecated: "Use toWinTime instead".} = + ## Converts a UNIX `Time` (``time_t``) to a Windows file time + ## + ## **Deprecated:** use ``toWinTime`` instead. + result = int64(time) * rateDiff + epochDiff + + proc winTimeToUnixTime*(time: int64): CTime {.deprecated: "Use fromWinTime instead".} = + ## Converts a Windows time to a UNIX `Time` (``time_t``) + ## + ## **Deprecated:** use ``fromWinTime`` instead. + result = CTime((time - epochDiff) div rateDiff) + +proc initInterval*(seconds, minutes, hours, days, months, + years: int = 0): TimeInterval {.deprecated.} = + ## **Deprecated since v0.18.0:** use ``initTimeInterval`` instead. + initTimeInterval(0, 0, 0, seconds, minutes, hours, days, 0, months, years) + proc fromSeconds*(since1970: float): Time {.tags: [], raises: [], benign, deprecated.} = ## Takes a float which contains the number of seconds since the unix epoch and ## returns a time object. ## ## **Deprecated since v0.18.0:** use ``fromUnix`` instead - Time(since1970) + let nanos = ((since1970 - since1970.int64.float) * convert(Seconds, Nanoseconds, 1).float).int + initTime(since1970.int64, nanos) proc fromSeconds*(since1970: int64): Time {.tags: [], raises: [], benign, deprecated.} = ## Takes an int which contains the number of seconds since the unix epoch and ## returns a time object. ## ## **Deprecated since v0.18.0:** use ``fromUnix`` instead - Time(since1970) + fromUnix(since1970) proc toSeconds*(time: Time): float {.tags: [], raises: [], benign, deprecated.} = ## Returns the time in seconds since the unix epoch. ## ## **Deprecated since v0.18.0:** use ``toUnix`` instead - float(time) + time.seconds.float + time.nanosecond / convert(Seconds, Nanoseconds, 1) proc getLocalTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} = ## Converts the calendar time `time` to broken-time representation, @@ -1386,7 +2420,7 @@ proc getLocalTime*(time: Time): DateTime {.tags: [], raises: [], benign, depreca proc getGMTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} = ## Converts the calendar time `time` to broken-down time representation, - ## expressed in Coordinated Universal Time (UTC). + ## expressed in Coordinated Universal Time (UTC). ## ## **Deprecated since v0.18.0:** use ``utc`` instead time.utc @@ -1399,7 +2433,8 @@ proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign, deprecated.} when defined(JS): return newDate().getTimezoneOffset() * 60 elif defined(freebsd) or defined(netbsd) or defined(openbsd): - var a = timec(nil) + var a: CTime + discard time(a) let lt = localtime(addr(a)) # BSD stores in `gmtoff` offset east of UTC in seconds, # but posix systems using west of UTC in seconds @@ -1410,79 +2445,41 @@ proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign, deprecated.} proc timeInfoToTime*(dt: DateTime): Time {.tags: [], benign, deprecated.} = ## Converts a broken-down time structure to calendar time representation. ## - ## **Warning:** This procedure is deprecated since version 0.14.0. - ## Use ``toTime`` instead. + ## **Deprecated since v0.14.0:** use ``toTime`` instead. dt.toTime when defined(JS): - var startMilsecs = getTime() + var start = getTime() proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} = - ## get the milliseconds from the start of the program. **Deprecated since - ## version 0.8.10.** Use ``epochTime`` or ``cpuTime`` instead. - when defined(JS): - ## get the milliseconds from the start of the program - return int(getTime() - startMilsecs) + let dur = getTime() - start + result = (convert(Seconds, Milliseconds, dur.seconds) + + convert(Nanoseconds, Milliseconds, dur.nanosecond)).int else: proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} = + ## get the milliseconds from the start of the program. + ## + ## **Deprecated since v0.8.10:** use ``epochTime`` or ``cpuTime`` instead. when defined(macosx): result = toInt(toFloat(int(getClock())) / (toFloat(clocksPerSec) / 1000.0)) else: result = int(getClock()) div (clocksPerSec div 1000) -proc miliseconds*(t: TimeInterval): int {.deprecated.} = - t.milliseconds - proc timeToTimeInterval*(t: Time): TimeInterval {.deprecated.} = ## Converts a Time to a TimeInterval. ## - ## **Warning:** This procedure is deprecated since version 0.14.0. - ## Use ``toTimeInterval`` instead. + ## **Deprecated since v0.14.0:** use ``toTimeInterval`` instead. # Milliseconds not available from Time t.toTimeInterval() -proc timeToTimeInfo*(t: Time): DateTime {.deprecated.} = - ## Converts a Time to DateTime. - ## - ## **Warning:** This procedure is deprecated since version 0.14.0. - ## Use ``inZone`` instead. - const epochStartYear = 1970 - - let - secs = t.toSeconds().int - daysSinceEpoch = secs div secondsInDay - (yearsSinceEpoch, daysRemaining) = countYearsAndDays(daysSinceEpoch) - daySeconds = secs mod secondsInDay - - y = yearsSinceEpoch + epochStartYear - - var - mon = mJan - days = daysRemaining - daysInMonth = getDaysInMonth(mon, y) - - # calculate month and day remainder - while days > daysInMonth and mon <= mDec: - days -= daysInMonth - mon.inc - daysInMonth = getDaysInMonth(mon, y) - - let - yd = daysRemaining - m = mon # month is zero indexed enum - md = days - # NB: month is zero indexed but dayOfWeek expects 1 indexed. - wd = getDayOfWeek(days, mon, y).Weekday - h = daySeconds div secondsInHour + 1 - mi = (daySeconds mod secondsInHour) div secondsInMin - s = daySeconds mod secondsInMin - result = DateTime(year: y, yearday: yd, month: m, monthday: md, weekday: wd, hour: h, minute: mi, second: s) - proc getDayOfWeek*(day, month, year: int): WeekDay {.tags: [], raises: [], benign, deprecated.} = + ## **Deprecated since v0.18.0:** use + ## ``getDayOfWeek(monthday: MonthdayRange; month: Month; year: int)`` instead. getDayOfWeek(day, month.Month, year) proc getDayOfWeekJulian*(day, month, year: int): WeekDay {.deprecated.} = ## Returns the day of the week enum from day, month and year, ## according to the Julian calendar. + ## **Deprecated since v0.18.0** # Day & month start from one. let a = (14 - month) div 12 @@ -1490,3 +2487,23 @@ proc getDayOfWeekJulian*(day, month, year: int): WeekDay {.deprecated.} = m = month + (12*a) - 2 d = (5 + day + y + (y div 4) + (31*m) div 12) mod 7 result = d.WeekDay + +proc adjTime*(zt: ZonedTime): Time + {.deprecated: "Use zt.time instead".} = + ## **Deprecated since v0.19.0:** use the ``time`` field instead. + zt.time - initDuration(seconds = zt.utcOffset) + +proc `adjTime=`*(zt: var ZonedTime, adjTime: Time) + {.deprecated: "Use zt.time instead".} = + ## **Deprecated since v0.19.0:** use the ``time`` field instead. + zt.time = adjTime + initDuration(seconds = zt.utcOffset) + +proc zoneInfoFromUtc*(zone: Timezone, time: Time): ZonedTime + {.deprecated: "Use zonedTimeFromTime instead".} = + ## **Deprecated since v0.19.0:** use ``zonedTimeFromTime`` instead. + zone.zonedTimeFromTime(time) + +proc zoneInfoFromTz*(zone: Timezone, adjTime: Time): ZonedTime + {.deprecated: "Use zonedTimeFromAdjTime instead".} = + ## **Deprecated since v0.19.0:** use the ``zonedTimeFromAdjTime`` instead. + zone.zonedTimeFromAdjTime(adjTime) \ No newline at end of file diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index 2047abda4..e9579e824 100644 --- a/lib/pure/typetraits.nim +++ b/lib/pure/typetraits.nim @@ -59,9 +59,4 @@ proc supportsCopyMem*(t: typedesc): bool {.magic: "TypeTrait".} when isMainModule: - # echo type(42) - import streams - var ss = newStringStream() - ss.write($type(42)) # needs `$` - ss.setPosition(0) - doAssert ss.readAll() == "int" + doAssert $type(42) == "int" diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index 257c620f7..978f569ac 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -9,7 +9,7 @@ ## This module provides support to handle the Unicode UTF-8 encoding. -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated include "system/inclrtl" @@ -1392,7 +1392,7 @@ proc isCombining*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = (c >= 0xfe20 and c <= 0xfe2f)) template runeCheck(s, runeProc) = - ## Common code for rune.isLower, rune.isUpper, etc + ## Common code for isAlpha and isSpace. result = if len(s) == 0: false else: true var @@ -1403,16 +1403,6 @@ template runeCheck(s, runeProc) = fastRuneAt(s, i, rune, doInc=true) result = runeProc(rune) and result -proc isUpper*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nuc$1Str".} = - ## Returns true iff `s` contains all upper case unicode characters. - runeCheck(s, isUpper) - -proc isLower*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nuc$1Str".} = - ## Returns true iff `s` contains all lower case unicode characters. - runeCheck(s, isLower) - proc isAlpha*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nuc$1Str".} = ## Returns true iff `s` contains all alphabetic unicode characters. @@ -1423,6 +1413,56 @@ proc isSpace*(s: string): bool {.noSideEffect, procvar, ## Returns true iff `s` contains all whitespace unicode characters. runeCheck(s, isWhiteSpace) +template runeCaseCheck(s, runeProc, skipNonAlpha) = + ## Common code for rune.isLower and rune.isUpper. + if len(s) == 0: return false + + var + i = 0 + rune: Rune + hasAtleastOneAlphaRune = false + + while i < len(s): + fastRuneAt(s, i, rune, doInc=true) + if skipNonAlpha: + var runeIsAlpha = isAlpha(rune) + if not hasAtleastOneAlphaRune: + hasAtleastOneAlphaRune = runeIsAlpha + if runeIsAlpha and (not runeProc(rune)): + return false + else: + if not runeProc(rune): + return false + return if skipNonAlpha: hasAtleastOneAlphaRune else: true + +proc isLower*(s: string, skipNonAlpha: bool): bool = + ## Checks whether ``s`` is lower case. + ## + ## If ``skipNonAlpha`` is true, returns true if all alphabetical + ## runes in ``s`` are lower case. Returns false if none of the + ## runes in ``s`` are alphabetical. + ## + ## If ``skipNonAlpha`` is false, returns true only if all runes in + ## ``s`` are alphabetical and lower case. + ## + ## For either value of ``skipNonAlpha``, returns false if ``s`` is + ## an empty string. + runeCaseCheck(s, isLower, skipNonAlpha) + +proc isUpper*(s: string, skipNonAlpha: bool): bool = + ## Checks whether ``s`` is upper case. + ## + ## If ``skipNonAlpha`` is true, returns true if all alphabetical + ## runes in ``s`` are upper case. Returns false if none of the + ## runes in ``s`` are alphabetical. + ## + ## If ``skipNonAlpha`` is false, returns true only if all runes in + ## ``s`` are alphabetical and upper case. + ## + ## For either value of ``skipNonAlpha``, returns false if ``s`` is + ## an empty string. + runeCaseCheck(s, isUpper, skipNonAlpha) + template convertRune(s, runeProc) = ## Convert runes in `s` using `runeProc` as the converter. result = newString(len(s)) @@ -1755,25 +1795,39 @@ when isMainModule: doAssert(not isSpace("")) doAssert(not isSpace("ΑΓc \td")) - doAssert isLower("a") - doAssert isLower("γ") - doAssert(not isLower("Γ")) - doAssert(not isLower("4")) - doAssert(not isLower("")) - - doAssert isLower("abcdγ") - doAssert(not isLower("abCDΓ")) - doAssert(not isLower("33aaΓ")) - - doAssert isUpper("Γ") - doAssert(not isUpper("b")) - doAssert(not isUpper("α")) - doAssert(not isUpper("✓")) - doAssert(not isUpper("")) - - doAssert isUpper("ΑΒΓ") - doAssert(not isUpper("AAccβ")) - doAssert(not isUpper("A#$β")) + doAssert(not isLower(' '.Rune)) + + doAssert isLower("a", false) + doAssert isLower("γ", true) + doAssert(not isLower("Γ", false)) + doAssert(not isLower("4", true)) + doAssert(not isLower("", false)) + doAssert isLower("abcdγ", false) + doAssert(not isLower("33aaΓ", false)) + doAssert(not isLower("a b", false)) + + doAssert(not isLower("abCDΓ", true)) + doAssert isLower("a b", true) + doAssert isLower("1, 2, 3 go!", true) + doAssert(not isLower(" ", true)) + doAssert(not isLower("(*&#@(^#$✓ ", true)) # None of the string runes are alphabets + + doAssert(not isUpper(' '.Rune)) + + doAssert isUpper("Γ", false) + doAssert(not isUpper("α", false)) + doAssert(not isUpper("", false)) + doAssert isUpper("ΑΒΓ", false) + doAssert(not isUpper("A#$β", false)) + doAssert(not isUpper("A B", false)) + + doAssert(not isUpper("b", true)) + doAssert(not isUpper("✓", true)) + doAssert(not isUpper("AAccβ", true)) + doAssert isUpper("A B", true) + doAssert isUpper("1, 2, 3 GO!", true) + doAssert(not isUpper(" ", true)) + doAssert(not isUpper("(*&#@(^#$✓ ", true)) # None of the string runes are alphabets doAssert toUpper("Γ") == "Γ" doAssert toUpper("b") == "B" @@ -1826,8 +1880,8 @@ when isMainModule: doAssert(runeSubStr(s, 17, 1) == "€") # echo runeStrAtPos(s, 18) # index error - doAssert(runeSubStr(s, 0) == "Hänsel ««: 10,00€") - doAssert(runeSubStr(s, -18) == "Hänsel ««: 10,00€") + doAssert(runeSubStr(s, 0) == "Hänsel ««: 10,00€") + doAssert(runeSubStr(s, -18) == "Hänsel ««: 10,00€") doAssert(runeSubStr(s, 10) == ": 10,00€") doAssert(runeSubStr(s, 18) == "") doAssert(runeSubStr(s, 0, 10) == "Hänsel ««") @@ -1840,7 +1894,7 @@ when isMainModule: doAssert(runeSubStr(s, -6, 5) == "10,00") doAssert(runeSubStr(s, -6, -1) == "10,00") - doAssert(runeSubStr(s, 0, 100) == "Hänsel ««: 10,00€") - doAssert(runeSubStr(s, -100, 100) == "Hänsel ««: 10,00€") + doAssert(runeSubStr(s, 0, 100) == "Hänsel ««: 10,00€") + doAssert(runeSubStr(s, -100, 100) == "Hänsel ««: 10,00€") doAssert(runeSubStr(s, 0, -100) == "") doAssert(runeSubStr(s, 100, -100) == "") diff --git a/lib/pure/unidecode/gen.py b/lib/pure/unidecode/gen.py index 8da0136ff..f0647ea6c 100644 --- a/lib/pure/unidecode/gen.py +++ b/lib/pure/unidecode/gen.py @@ -1,26 +1,30 @@ -#! usr/bin/env python +#! usr/bin/env python3 # -*- coding: utf-8 -*- # Generates the unidecode.dat module # (c) 2010 Andreas Rumpf from unidecode import unidecode +try: + import warnings + warnings.simplefilter("ignore") +except ImportError: + pass -def main2(): - data = [] - for x in xrange(128, 0xffff + 1): - u = eval("u'\u%04x'" % x) - - val = unidecode(u) - data.append(val) - - - f = open("unidecode.dat", "wb+") - for d in data: - f.write("%s\n" % d) - f.close() +def main2(): + f = open("unidecode.dat", "wb+") + for x in range(128, 0xffff + 1): + u = eval("u'\\u%04x'" % x) + val = unidecode(u) -main2() + # f.write("%x | " % x) + if x==0x2028: # U+2028 = LINE SEPARATOR + val = "" + elif x==0x2029: # U+2028 = PARAGRAPH SEPARATOR + val = "" + f.write("%s\n" % val) + f.close() +main2() \ No newline at end of file diff --git a/lib/pure/unidecode/unidecode.dat b/lib/pure/unidecode/unidecode.dat index 9dff0a4a9..5f4c075d8 100644 --- a/lib/pure/unidecode/unidecode.dat +++ b/lib/pure/unidecode/unidecode.dat @@ -58,9 +58,9 @@ P 1 o >> -1/4 -1/2 -3/4 + 1/4 + 1/2 + 3/4 ? A A @@ -91,7 +91,7 @@ U U U U -U +Y Th ss a @@ -177,7 +177,7 @@ i I i IJ - +ij J j K @@ -368,7 +368,7 @@ ZH zh j DZ -D +Dz dz G g @@ -414,8 +414,8 @@ Y y H h -[?] -[?] +N +d OU ou Z @@ -434,34 +434,34 @@ O o Y y +l +n +t +j +db +qp +A +C +c +L +T +s +z [?] [?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] +B +U +^ +E +e +J +j +q +q +R +r +Y +y a a a @@ -503,13 +503,13 @@ o OE O F -R -R -R -R r r -R +r +r +r +r +r R R s @@ -519,12 +519,12 @@ S S t t -U +u U v ^ -W -Y +w +y Y z z @@ -556,9 +556,9 @@ ls lz WW ]] -[?] -[?] -k +h +h +h h j r @@ -737,19 +737,19 @@ V -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] +a +e +i +o +u +c +d +h +m +r +t +v +x [?] [?] [?] @@ -1287,7 +1287,7 @@ o f ew [?] -. +: - [?] [?] @@ -1340,9 +1340,9 @@ o u ' +- - - +| : @@ -7402,41 +7402,41 @@ bh +b +d +f +m +n +p +r +r +s +t +z +g +p +b +d +f +g +k +l +m +n +p +r +s - - - - - - - - - - - - - - - - - - - - - - - - - - - +v +x +z @@ -7708,7 +7708,7 @@ a S [?] [?] -[?] +Ss [?] A a @@ -8109,9 +8109,6 @@ _ - - - %0 %00 @@ -8136,19 +8133,23 @@ _ / -[ ]- -[?] +?? ?! !? 7 PP (] [) +* [?] [?] [?] +% +~ [?] [?] [?] +'''' [?] [?] [?] @@ -8156,12 +8157,8 @@ PP [?] [?] [?] -[?] -[?] -[?] -[?] -[?] -[?] + + [?] [?] [?] @@ -8178,7 +8175,7 @@ PP 0 - +i 4 @@ -8209,19 +8206,19 @@ n ( ) [?] +a +e +o +x [?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] +h +k +l +m +n +p +s +t [?] [?] [?] @@ -8237,26 +8234,26 @@ Rs W NS D -EU +EUR K T Dr -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] +Pf +P +G +A +UAH +C| +L +Sm +T +Rs +L +M +m +R +l +BTC [?] [?] [?] @@ -8294,6 +8291,7 @@ Dr [?] + [?] [?] [?] @@ -8319,63 +8317,67 @@ Dr [?] [?] [?] -[?] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + a/c + a/s +C + c/o + c/u +g +H +H +H +h +I +I +L +l +N +No. +P +Q +R +R +R +(sm) +TEL +(tm) +Z +Z +K +A +B +C +e +e +E +F +F +M +o +i +FAX @@ -8385,25 +8387,20 @@ Dr [?] [?] [?] +D +d +e +i +j [?] [?] [?] [?] +F [?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] + 1/7 + 1/9 + 1/10 1/3 2/3 1/5 @@ -8458,7 +8455,7 @@ D) [?] [?] [?] -[?] + 0/3 [?] [?] [?] @@ -8595,8 +8592,12 @@ V [?] [?] [?] +- [?] [?] +/ +\ +* [?] [?] [?] @@ -8608,6 +8609,7 @@ V [?] [?] [?] +| [?] [?] [?] @@ -8626,11 +8628,13 @@ V [?] [?] [?] +: [?] [?] [?] [?] [?] +~ [?] [?] [?] @@ -8670,17 +8674,10 @@ V [?] [?] [?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] +<= +>= +<= +>= [?] [?] [?] @@ -8836,6 +8833,7 @@ V [?] [?] [?] +^ [?] [?] [?] @@ -8873,9 +8871,8 @@ V [?] [?] [?] -[?] -[?] -[?] +< +> [?] [?] [?] @@ -9185,166 +9182,166 @@ V [?] [?] [?] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] - +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +(1) +(2) +(3) +(4) +(5) +(6) +(7) +(8) +(9) +(10) +(11) +(12) +(13) +(14) +(15) +(16) +(17) +(18) +(19) +(20) +1. +2. +3. +4. +5. +6. +7. +8. +9. +10. +11. +12. +13. +14. +15. +16. +17. +18. +19. +20. +(a) +(b) +(c) +(d) +(e) +(f) +(g) +(h) +(i) +(j) +(k) +(l) +(m) +(n) +(o) +(p) +(q) +(r) +(s) +(t) +(u) +(v) +(w) +(x) +(y) +(z) +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +0 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +0 - - | @@ -9712,7 +9709,7 @@ O - +# [?] @@ -9906,6 +9903,7 @@ O +* @@ -9944,8 +9942,7 @@ O - - +| @@ -9955,7 +9952,7 @@ O [?] [?] - +! @@ -10087,10 +10084,10 @@ O [?] [?] [?] +[ [?] -[?] -[?] -[?] +< +> [?] [?] [?] @@ -10500,6 +10497,8 @@ y +{ +} @@ -10739,6 +10738,9 @@ y +::= +== +=== @@ -11228,27 +11230,22 @@ y +L +l +L +P +R +a +t +H +h +K +k +Z +z - - - - - - - - - - - - - - - - - - - - +M +A @@ -12754,21 +12751,21 @@ H [?] [?] [?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 (g) (n) (d) @@ -12850,21 +12847,21 @@ KIS (Zi) (Xie) (Ye) -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] -[?] +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 1M 2M 3M @@ -12877,10 +12874,10 @@ KIS 10M 11M 12M -[?] -[?] -[?] -[?] +Hg +erg +eV +LTD a i u @@ -13042,16 +13039,16 @@ watt 22h 23h 24h -HPA +hPa da AU bar oV pc -[?] -[?] -[?] -[?] +dm +dm^2 +dm^3 +IU Heisei Syouwa Taisyou @@ -13092,7 +13089,7 @@ mm^2 cm^2 m^2 km^2 -mm^4 +mm^3 cm^3 m^3 km^3 @@ -13184,7 +13181,7 @@ Wb 29d 30d 31d - +gal @@ -19841,7 +19838,7 @@ Wb [?] [?] -[?] +Yi Ding Kao Qi diff --git a/lib/pure/unidecode/unidecode.nim b/lib/pure/unidecode/unidecode.nim index 9d8843f06..e0b8d3946 100644 --- a/lib/pure/unidecode/unidecode.nim +++ b/lib/pure/unidecode/unidecode.nim @@ -22,14 +22,14 @@ ## strictly one-way transformation. However a human reader will probably ## still be able to guess what original string was meant from the context. ## -## This module needs the data file "unidecode.dat" to work: You can either -## ship this file with your application and initialize this module with the -## `loadUnidecodeTable` proc or you can define the ``embedUnidecodeTable`` -## symbol to embed the file as a resource into your application. +## This module needs the data file "unidecode.dat" to work: This file is +## embedded as a resource into your application by default. But you an also +## define the symbol ``--define:noUnidecodeTable`` during compile time and +## use the `loadUnidecodeTable` proc to initialize this module. import unicode -when defined(embedUnidecodeTable): +when not defined(noUnidecodeTable): import strutils const translationTable = splitLines(slurp"unidecode/unidecode.dat") @@ -38,11 +38,11 @@ else: var translationTable: seq[string] proc loadUnidecodeTable*(datafile = "unidecode.dat") = - ## loads the datafile that `unidecode` to work. Unless this module is - ## compiled with the ``embedUnidecodeTable`` symbol defined, this needs - ## to be called by the main thread before any thread can make a call - ## to `unidecode`. - when not defined(embedUnidecodeTable): + ## loads the datafile that `unidecode` to work. This is only required if + ## the module was compiled with the ``--define:noUnidecodeTable`` switch. + ## This needs to be called by the main thread before any thread can make a + ## call to `unidecode`. + when defined(noUnidecodeTable): newSeq(translationTable, 0xffff) var i = 0 for line in lines(datafile): @@ -61,7 +61,6 @@ proc unidecode*(s: string): string = ## ## Results in: "Bei Jing" ## - assert(not isNil(translationTable)) result = "" for r in runes(s): var c = int(r) @@ -69,6 +68,6 @@ proc unidecode*(s: string): string = elif c <% translationTable.len: add(result, translationTable[c-128]) when isMainModule: - loadUnidecodeTable("lib/pure/unidecode/unidecode.dat") - assert unidecode("Äußerst") == "Ausserst" - + #loadUnidecodeTable("lib/pure/unidecode/unidecode.dat") + doAssert unidecode("Äußerst") == "Ausserst" + doAssert unidecode("北京") == "Bei Jing " diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index fbce087ff..757bf4745 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -121,9 +121,16 @@ type ConsoleOutputFormatter* = ref object of OutputFormatter colorOutput: bool ## Have test results printed in color. - ## Default is true for the non-js target - ## unless, the environment variable - ## ``NIMTEST_NO_COLOR`` is set. + ## Default is true for the non-js target, + ## for which ``stdout`` is a tty. + ## Setting the environment variable + ## ``NIMTEST_COLOR`` to ``always`` or + ## ``never`` changes the default for the + ## non-js target to true or false respectively. + ## The deprecated environment variable + ## ``NIMTEST_NO_COLOR``, when set, + ## changes the defualt to true, if + ## ``NIMTEST_COLOR`` is undefined. outputLevel: OutputLevel ## Set the verbosity of test results. ## Default is ``PRINT_ALL``, unless @@ -150,6 +157,7 @@ var checkpoints {.threadvar.}: seq[string] formatters {.threadvar.}: seq[OutputFormatter] testsFilters {.threadvar.}: HashSet[string] + disabledParamFiltering {.threadvar.}: bool when declared(stdout): abortOnError = existsEnv("NIMTEST_ABORT_ON_ERROR") @@ -168,10 +176,7 @@ method suiteEnded*(formatter: OutputFormatter) {.base, gcsafe.} = discard proc addOutputFormatter*(formatter: OutputFormatter) = - if formatters == nil: - formatters = @[formatter] - else: - formatters.add(formatter) + formatters.add(formatter) proc newConsoleOutputFormatter*(outputLevel: OutputLevel = PRINT_ALL, colorOutput = true): ConsoleOutputFormatter = @@ -185,7 +190,15 @@ proc defaultConsoleFormatter*(): ConsoleOutputFormatter = # Reading settings # On a terminal this branch is executed var envOutLvl = os.getEnv("NIMTEST_OUTPUT_LVL").string - var colorOutput = not existsEnv("NIMTEST_NO_COLOR") + var colorOutput = isatty(stdout) + if existsEnv("NIMTEST_COLOR"): + let colorEnv = getenv("NIMTEST_COLOR") + if colorEnv == "never": + colorOutput = false + elif colorEnv == "always": + colorOutput = true + elif existsEnv("NIMTEST_NO_COLOR"): + colorOutput = false var outputLevel = PRINT_ALL if envOutLvl.len > 0: for opt in countup(low(OutputLevel), high(OutputLevel)): @@ -209,7 +222,7 @@ method testStarted*(formatter: ConsoleOutputFormatter, testName: string) = formatter.isInTest = true method failureOccurred*(formatter: ConsoleOutputFormatter, checkpoints: seq[string], stackTrace: string) = - if stackTrace != nil: + if stackTrace.len > 0: echo stackTrace let prefix = if formatter.isInSuite: " " else: "" for msg in items(checkpoints): @@ -220,7 +233,7 @@ method testEnded*(formatter: ConsoleOutputFormatter, testResult: TestResult) = if formatter.outputLevel != PRINT_NONE and (formatter.outputLevel == PRINT_ALL or testResult.status == FAILED): - let prefix = if testResult.suiteName != nil: " " else: "" + let prefix = if testResult.suiteName.len > 0: " " else: "" template rawPrint() = echo(prefix, "[", $testResult.status, "] ", testResult.testName) when not defined(ECMAScript): if formatter.colorOutput and not defined(ECMAScript): @@ -285,7 +298,7 @@ method failureOccurred*(formatter: JUnitOutputFormatter, checkpoints: seq[string ## ``stackTrace`` is provided only if the failure occurred due to an exception. ## ``checkpoints`` is never ``nil``. formatter.testErrors.add(checkpoints) - if stackTrace != nil: + if stackTrace.len > 0: formatter.testStackTrace = stackTrace method testEnded*(formatter: JUnitOutputFormatter, testResult: TestResult) = @@ -376,10 +389,10 @@ proc shouldRun(currentSuiteName, testName: string): bool = return false proc ensureInitialized() = - if formatters == nil: + if formatters.len == 0: formatters = @[OutputFormatter(defaultConsoleFormatter())] - if not testsFilters.isValid: + if not disabledParamFiltering and not testsFilters.isValid: testsFilters.init() when declared(paramCount): # Read tests to run from the command line. @@ -446,6 +459,8 @@ template suite*(name, body) {.dirty.} = finally: suiteEnded() +template exceptionTypeName(e: typed): string = $e.name + template test*(name, body) {.dirty.} = ## Define a single test case identified by `name`. ## @@ -460,7 +475,7 @@ template test*(name, body) {.dirty.} = ## .. code-block:: ## ## [OK] roses are red - bind shouldRun, checkpoints, formatters, ensureInitialized, testEnded + bind shouldRun, checkpoints, formatters, ensureInitialized, testEnded, exceptionTypeName ensureInitialized() @@ -479,15 +494,17 @@ template test*(name, body) {.dirty.} = except: when not defined(js): - checkpoint("Unhandled exception: " & getCurrentExceptionMsg()) - var stackTrace {.inject.} = getCurrentException().getStackTrace() + let e = getCurrentException() + let eTypeDesc = "[" & exceptionTypeName(e) & "]" + checkpoint("Unhandled exception: " & getCurrentExceptionMsg() & " " & eTypeDesc) + var stackTrace {.inject.} = e.getStackTrace() fail() finally: if testStatusIMPL == FAILED: programResult += 1 let testResult = TestResult( - suiteName: when declared(testSuiteName): testSuiteName else: nil, + suiteName: when declared(testSuiteName): testSuiteName else: "", testName: name, status: testStatusIMPL ) @@ -505,8 +522,6 @@ proc checkpoint*(msg: string) = ## checkpoint("Checkpoint B") ## ## outputs "Checkpoint A" once it fails. - if checkpoints == nil: - checkpoints = @[] checkpoints.add(msg) # TODO: add support for something like SCOPED_TRACE from Google Test @@ -537,7 +552,7 @@ template fail* = when declared(stackTrace): formatter.failureOccurred(checkpoints, stackTrace) else: - formatter.failureOccurred(checkpoints, nil) + formatter.failureOccurred(checkpoints, "") when not defined(ECMAScript): if abortOnError: quit(programResult) @@ -701,3 +716,7 @@ macro expect*(exceptions: varargs[typed], body: untyped): untyped = errorTypes.add(exp[i]) result = getAst(expectBody(errorTypes, exp.lineinfo, body)) + +proc disableParamFiltering* = + ## disables filtering tests with the command line params + disabledParamFiltering = true diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index d2d11253a..dd8040928 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -18,14 +18,12 @@ type hostname*, port*, path*, query*, anchor*: string opaque*: bool -{.deprecated: [TUrl: Url, TUri: Uri].} - {.push warning[deprecated]: off.} -proc `$`*(url: Url): string {.deprecated.} = +proc `$`*(url: Url): string {.deprecated: "use Uri instead".} = ## **Deprecated since 0.9.6**: Use ``Uri`` instead. return string(url) -proc `/`*(a, b: Url): Url {.deprecated.} = +proc `/`*(a, b: Url): Url {.deprecated: "use Uri instead".} = ## Joins two URLs together, separating them with / if needed. ## ## **Deprecated since 0.9.6**: Use ``Uri`` instead. @@ -40,39 +38,50 @@ proc `/`*(a, b: Url): Url {.deprecated.} = urlS.add(bs) result = Url(urlS) -proc add*(url: var Url, a: Url) {.deprecated.} = +proc add*(url: var Url, a: Url) {.deprecated: "use Uri instead".} = ## Appends url to url. ## ## **Deprecated since 0.9.6**: Use ``Uri`` instead. url = url / a {.pop.} -proc encodeUrl*(s: string): string = - ## Encodes a value to be HTTP safe: This means that characters in the set - ## ``{'A'..'Z', 'a'..'z', '0'..'9', '_'}`` are carried over to the result, - ## a space is converted to ``'+'`` and every other character is encoded as - ## ``'%xx'`` where ``xx`` denotes its hexadecimal value. +proc encodeUrl*(s: string, usePlus=true): string = + ## Encodes a URL according to RFC3986. + ## + ## This means that characters in the set + ## ``{'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~'}`` are + ## carried over to the result. + ## All other characters are encoded as ``''%xx'`` where ``xx`` + ## denotes its hexadecimal value. + ## + ## As a special rule, when the value of ``usePlus`` is true, + ## spaces are encoded as ``'+'`` instead of ``'%20'``. result = newStringOfCap(s.len + s.len shr 2) # assume 12% non-alnum-chars - for i in 0..s.len-1: - case s[i] - of 'a'..'z', 'A'..'Z', '0'..'9', '_': add(result, s[i]) - of ' ': add(result, '+') + let fromSpace = if usePlus: "+" else: "%20" + for c in s: + case c + of 'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~': add(result, c) + of ' ': add(result, fromSpace) else: add(result, '%') - add(result, toHex(ord(s[i]), 2)) - -proc decodeUrl*(s: string): string = - ## Decodes a value from its HTTP representation: This means that a ``'+'`` - ## is converted to a space, ``'%xx'`` (where ``xx`` denotes a hexadecimal - ## value) is converted to the character with ordinal number ``xx``, and + add(result, toHex(ord(c), 2)) + +proc decodeUrl*(s: string, decodePlus=true): string = + ## Decodes a URL according to RFC3986. + ## + ## This means that any ``'%xx'`` (where ``xx`` denotes a hexadecimal + ## value) are converted to the character with ordinal number ``xx``, ## and every other character is carried over. + ## + ## As a special rule, when the value of ``decodePlus`` is true, ``'+'`` + ## characters are converted to a space. proc handleHexChar(c: char, x: var int) {.inline.} = case c of '0'..'9': x = (x shl 4) or (ord(c) - ord('0')) of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10) of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10) else: assert(false) - + result = newString(s.len) var i = 0 var j = 0 @@ -84,7 +93,11 @@ proc decodeUrl*(s: string): string = handleHexChar(s[i+2], x) inc(i, 2) result[j] = chr(x) - of '+': result[j] = ' ' + of '+': + if decodePlus: + result[j] = ' ' + else: + result[j] = s[i] else: result[j] = s[i] inc(i) inc(j) @@ -94,7 +107,7 @@ proc parseAuthority(authority: string, result: var Uri) = var i = 0 var inPort = false var inIPv6 = false - while true: + while i < authority.len: case authority[i] of '@': swap result.password, result.port @@ -111,7 +124,6 @@ proc parseAuthority(authority: string, result: var Uri) = inIPv6 = true of ']': inIPv6 = false - of '\0': break else: if inPort: result.port.add(authority[i]) @@ -128,11 +140,11 @@ proc parsePath(uri: string, i: var int, result: var Uri) = parseAuthority(result.path, result) result.path.setLen(0) - if uri[i] == '?': + if i < uri.len and uri[i] == '?': i.inc # Skip '?' i.inc parseUntil(uri, result.query, {'#'}, i) - if uri[i] == '#': + if i < uri.len and uri[i] == '#': i.inc # Skip '#' i.inc parseUntil(uri, result.anchor, {}, i) @@ -156,7 +168,7 @@ proc parseUri*(uri: string, result: var Uri) = # Check if this is a reference URI (relative URI) let doubleSlash = uri.len > 1 and uri[1] == '/' - if uri[i] == '/': + if i < uri.len and uri[i] == '/': # Make sure ``uri`` doesn't begin with '//'. if not doubleSlash: parsePath(uri, i, result) @@ -164,7 +176,7 @@ proc parseUri*(uri: string, result: var Uri) = # Scheme i.inc parseWhile(uri, result.scheme, Letters + Digits + {'+', '-', '.'}, i) - if uri[i] != ':' and not doubleSlash: + if (i >= uri.len or uri[i] != ':') and not doubleSlash: # Assume this is a reference URI (relative URI) i = 0 result.scheme.setLen(0) @@ -174,7 +186,7 @@ proc parseUri*(uri: string, result: var Uri) = i.inc # Skip ':' # Authority - if uri[i] == '/' and uri[i+1] == '/': + if i+1 < uri.len and uri[i] == '/' and uri[i+1] == '/': i.inc(2) # Skip // var authority = "" i.inc parseUntil(uri, authority, {'/', '?', '#'}, i) @@ -197,13 +209,13 @@ proc removeDotSegments(path: string): string = let endsWithSlash = path[path.len-1] == '/' var i = 0 var currentSegment = "" - while true: + while i < path.len: case path[i] of '/': collection.add(currentSegment) currentSegment = "" of '.': - if path[i+1] == '.' and path[i+2] == '/': + if i+2 < path.len and path[i+1] == '.' and path[i+2] == '/': if collection.len > 0: discard collection.pop() i.inc 3 @@ -212,13 +224,11 @@ proc removeDotSegments(path: string): string = i.inc 2 continue currentSegment.add path[i] - of '\0': - if currentSegment != "": - collection.add currentSegment - break else: currentSegment.add path[i] i.inc + if currentSegment != "": + collection.add currentSegment result = collection.join("/") if endsWithSlash: result.add '/' @@ -320,18 +330,18 @@ proc `/`*(x: Uri, path: string): Uri = result = x if result.path.len == 0: - if path[0] != '/': + if path.len == 0 or path[0] != '/': result.path = "/" result.path.add(path) return - if result.path[result.path.len-1] == '/': - if path[0] == '/': + if result.path.len > 0 and result.path[result.path.len-1] == '/': + if path.len > 0 and path[0] == '/': result.path.add(path[1 .. path.len-1]) else: result.path.add(path) else: - if path[0] != '/': + if path.len == 0 or path[0] != '/': result.path.add '/' result.path.add(path) @@ -373,7 +383,10 @@ when isMainModule: const test1 = "abc\L+def xyz" doAssert encodeUrl(test1) == "abc%0A%2Bdef+xyz" doAssert decodeUrl(encodeUrl(test1)) == test1 - + doAssert encodeUrl(test1, false) == "abc%0A%2Bdef%20xyz" + doAssert decodeUrl(encodeUrl(test1, false), false) == test1 + doAssert decodeUrl(encodeUrl(test1)) == test1 + block: let str = "http://localhost" let test = parseUri(str) @@ -464,7 +477,7 @@ when isMainModule: doAssert test.hostname == "github.com" doAssert test.port == "dom96" doAssert test.path == "/packages" - + block: let str = "file:///foo/bar/baz.txt" let test = parseUri(str) diff --git a/lib/pure/xmldom.nim b/lib/pure/xmldom.nim index 3c891c81b..82f88a996 100644 --- a/lib/pure/xmldom.nim +++ b/lib/pure/xmldom.nim @@ -172,34 +172,30 @@ proc documentElement*(doc: PDocument): PElement = proc findNodes(nl: PNode, name: string): seq[PNode] = # Made for getElementsByTagName var r: seq[PNode] = @[] - if isNil(nl.childNodes): return @[] - if nl.childNodes.len() == 0: return @[] + if nl.childNodes.len == 0: return @[] for i in items(nl.childNodes): if i.fNodeType == ElementNode: if i.fNodeName == name or name == "*": r.add(i) - if not isNil(i.childNodes): - if i.childNodes.len() != 0: - r.add(findNodes(i, name)) + if i.childNodes.len() != 0: + r.add(findNodes(i, name)) return r proc findNodesNS(nl: PNode, namespaceURI: string, localName: string): seq[PNode] = # Made for getElementsByTagNameNS var r: seq[PNode] = @[] - if isNil(nl.childNodes): return @[] - if nl.childNodes.len() == 0: return @[] + if nl.childNodes.len == 0: return @[] for i in items(nl.childNodes): if i.fNodeType == ElementNode: if (i.fNamespaceURI == namespaceURI or namespaceURI == "*") and (i.fLocalName == localName or localName == "*"): r.add(i) - if not isNil(i.childNodes): - if i.childNodes.len() != 0: - r.add(findNodesNS(i, namespaceURI, localName)) + if i.childNodes.len != 0: + r.add(findNodesNS(i, namespaceURI, localName)) return r @@ -217,9 +213,9 @@ proc createAttribute*(doc: PDocument, name: string): PAttr = new(attrNode) attrNode.fName = name attrNode.fNodeName = name - attrNode.fLocalName = nil - attrNode.prefix = nil - attrNode.fNamespaceURI = nil + attrNode.fLocalName = "" + attrNode.prefix = "" + attrNode.fNamespaceURI = "" attrNode.value = "" attrNode.fSpecified = false return attrNode @@ -232,10 +228,10 @@ proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: str raise newException(EInvalidCharacterErr, "Invalid character") # Exceptions if qualifiedName.contains(':'): - let qfnamespaces = qualifiedName.toLower().split(':') - if isNil(namespaceURI): - raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") - elif qfnamespaces[0] == "xml" and + let qfnamespaces = qualifiedName.toLowerAscii().split(':') + if namespaceURI.len == 0: + raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be empty") + elif qfnamespaces[0] == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace" and qfnamespaces[1] notin stdattrnames: raise newException(ENamespaceErr, @@ -254,7 +250,7 @@ proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: str attrNode.prefix = qualifiedName.split(':')[0] attrNode.fLocalName = qualifiedName.split(':')[1] else: - attrNode.prefix = nil + attrNode.prefix = "" attrNode.fLocalName = qualifiedName attrNode.value = "" @@ -298,9 +294,9 @@ proc createElement*(doc: PDocument, tagName: string): PElement = new(elNode) elNode.fTagName = tagName elNode.fNodeName = tagName - elNode.fLocalName = nil - elNode.prefix = nil - elNode.fNamespaceURI = nil + elNode.fLocalName = "" + elNode.prefix = "" + elNode.fNamespaceURI = "" elNode.childNodes = @[] elNode.attributes = @[] @@ -311,10 +307,10 @@ proc createElement*(doc: PDocument, tagName: string): PElement = proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PElement = ## Creates an element of the given qualified name and namespace URI. if qualifiedName.contains(':'): - let qfnamespaces = qualifiedName.toLower().split(':') - if isNil(namespaceURI): - raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") - elif qfnamespaces[0] == "xml" and + let qfnamespaces = qualifiedName.toLowerAscii().split(':') + if namespaceURI.len == 0: + raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be empty") + elif qfnamespaces[0] == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace" and qfnamespaces[1] notin stdattrnames: raise newException(ENamespaceErr, @@ -332,7 +328,7 @@ proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: strin elNode.prefix = qualifiedName.split(':')[0] elNode.fLocalName = qualifiedName.split(':')[1] else: - elNode.prefix = nil + elNode.prefix = "" elNode.fLocalName = qualifiedName elNode.fNamespaceURI = namespaceURI elNode.childNodes = @[] @@ -453,7 +449,7 @@ proc importNode*(doc: PDocument, importedNode: PNode, deep: bool): PNode = proc firstChild*(n: PNode): PNode = ## Returns this node's first child - if not isNil(n.childNodes) and n.childNodes.len() > 0: + if n.childNodes.len > 0: return n.childNodes[0] else: return nil @@ -461,8 +457,8 @@ proc firstChild*(n: PNode): PNode = proc lastChild*(n: PNode): PNode = ## Returns this node's last child - if not isNil(n.childNodes) and n.childNodes.len() > 0: - return n.childNodes[n.childNodes.len() - 1] + if n.childNodes.len > 0: + return n.childNodes[n.childNodes.len - 1] else: return nil @@ -482,7 +478,7 @@ proc `namespaceURI=`*(n: PNode, value: string) = proc nextSibling*(n: PNode): PNode = ## Returns the next sibling of this node - if isNil(n.fParentNode) or isNil(n.fParentNode.childNodes): + if isNil(n.fParentNode): return nil var nLow: int = low(n.fParentNode.childNodes) var nHigh: int = high(n.fParentNode.childNodes) @@ -514,7 +510,7 @@ proc parentNode*(n: PNode): PNode = proc previousSibling*(n: PNode): PNode = ## Returns the previous sibling of this node - if isNil(n.fParentNode) or isNil(n.fParentNode.childNodes): + if isNil(n.fParentNode): return nil var nLow: int = low(n.fParentNode.childNodes) var nHigh: int = high(n.fParentNode.childNodes) @@ -531,15 +527,15 @@ proc `prefix=`*(n: PNode, value: string) = if illegalChars in value: raise newException(EInvalidCharacterErr, "Invalid character") - if isNil(n.fNamespaceURI): - raise newException(ENamespaceErr, "namespaceURI cannot be nil") - elif value.toLower() == "xml" and n.fNamespaceURI != "http://www.w3.org/XML/1998/namespace": + if n.fNamespaceURI.len == 0: + raise newException(ENamespaceErr, "namespaceURI cannot be empty") + elif value.toLowerAscii() == "xml" and n.fNamespaceURI != "http://www.w3.org/XML/1998/namespace": raise newException(ENamespaceErr, "When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"") - elif value.toLower() == "xmlns" and n.fNamespaceURI != "http://www.w3.org/2000/xmlns/": + elif value.toLowerAscii() == "xmlns" and n.fNamespaceURI != "http://www.w3.org/2000/xmlns/": raise newException(ENamespaceErr, "When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"") - elif value.toLower() == "xmlns" and n.fNodeType == AttributeNode: + elif value.toLowerAscii() == "xmlns" and n.fNodeType == AttributeNode: raise newException(ENamespaceErr, "An AttributeNode cannot have a prefix of \"xmlns\"") n.fNodeName = value & ":" & n.fLocalName @@ -557,10 +553,9 @@ proc appendChild*(n: PNode, newChild: PNode) = ## If the newChild is already in the tree, it is first removed. # Check if n contains newChild - if not isNil(n.childNodes): - for i in low(n.childNodes)..high(n.childNodes): - if n.childNodes[i] == newChild: - raise newException(EHierarchyRequestErr, "The node to append is already in this nodes children.") + for i in low(n.childNodes)..high(n.childNodes): + if n.childNodes[i] == newChild: + raise newException(EHierarchyRequestErr, "The node to append is already in this nodes children.") # Check if newChild is from this nodes document if n.fOwnerDocument != newChild.fOwnerDocument: @@ -572,7 +567,8 @@ proc appendChild*(n: PNode, newChild: PNode) = if n.nodeType in childlessObjects: raise newException(ENoModificationAllowedErr, "Cannot append children to a childless node") - if isNil(n.childNodes): n.childNodes = @[] + when not defined(nimNoNilSeqs): + if isNil(n.childNodes): n.childNodes = @[] newChild.fParentNode = n for i in low(n.childNodes)..high(n.childNodes): @@ -597,10 +593,10 @@ proc cloneNode*(n: PNode, deep: bool): PNode = newNode = PElement(n) # Import the childNodes var tmp: seq[PNode] = n.childNodes - n.childNodes = @[] - if deep and not isNil(tmp): - for i in low(tmp.len())..high(tmp.len()): - n.childNodes.add(cloneNode(tmp[i], deep)) + newNode.childNodes = @[] + if deep: + for i in low(n.childNodes)..high(n.childNodes): + newNode.childNodes.add(cloneNode(n.childNodes[i], deep)) return newNode else: var newNode: PNode @@ -610,11 +606,11 @@ proc cloneNode*(n: PNode, deep: bool): PNode = proc hasAttributes*(n: PNode): bool = ## Returns whether this node (if it is an element) has any attributes. - return not isNil(n.attributes) and n.attributes.len() > 0 + return n.attributes.len > 0 proc hasChildNodes*(n: PNode): bool = ## Returns whether this node has any children. - return not isNil(n.childNodes) and n.childNodes.len() > 0 + return n.childNodes.len > 0 proc insertBefore*(n: PNode, newChild: PNode, refChild: PNode): PNode = ## Inserts the node ``newChild`` before the existing child node ``refChild``. @@ -624,9 +620,6 @@ proc insertBefore*(n: PNode, newChild: PNode, refChild: PNode): PNode = if n.fOwnerDocument != newChild.fOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") - if isNil(n.childNodes): - n.childNodes = @[] - for i in low(n.childNodes)..high(n.childNodes): if n.childNodes[i] == refChild: n.childNodes.insert(newChild, i - 1) @@ -641,7 +634,7 @@ proc isSupported*(n: PNode, feature: string, version: string): bool = proc isEmpty(s: string): bool = - if isNil(s) or s == "": + if s == "": return true for i in items(s): if i != ' ': @@ -655,7 +648,7 @@ proc normalize*(n: PNode) = var newChildNodes: seq[PNode] = @[] while true: - if isNil(n.childNodes) or i >= n.childNodes.len: + if i >= n.childNodes.len: break if n.childNodes[i].nodeType == TextNode: @@ -679,12 +672,11 @@ proc normalize*(n: PNode) = proc removeChild*(n: PNode, oldChild: PNode): PNode = ## Removes the child node indicated by ``oldChild`` from the list of children, and returns it. - if not isNil(n.childNodes): - for i in low(n.childNodes)..high(n.childNodes): - if n.childNodes[i] == oldChild: - result = n.childNodes[i] - n.childNodes.delete(i) - return + for i in low(n.childNodes)..high(n.childNodes): + if n.childNodes[i] == oldChild: + result = n.childNodes[i] + n.childNodes.delete(i) + return raise newException(ENotFoundErr, "Node not found") @@ -695,12 +687,11 @@ proc replaceChild*(n: PNode, newChild: PNode, oldChild: PNode): PNode = if n.fOwnerDocument != newChild.fOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") - if not isNil(n.childNodes): - for i in low(n.childNodes)..high(n.childNodes): - if n.childNodes[i] == oldChild: - result = n.childNodes[i] - n.childNodes[i] = newChild - return + for i in low(n.childNodes)..high(n.childNodes): + if n.childNodes[i] == oldChild: + result = n.childNodes[i] + n.childNodes[i] = newChild + return raise newException(ENotFoundErr, "Node not found") @@ -764,11 +755,10 @@ proc removeNamedItemNS*(nList: var seq[PNode], namespaceURI: string, localName: proc setNamedItem*(nList: var seq[PNode], arg: PNode): PNode = ## Adds ``arg`` as a ``Node`` to the ``NList`` ## If a node with the same name is already present in this map, it is replaced by the new one. - if not isNil(nList): - if nList.len() > 0: - #Check if newChild is from this nodes document - if nList[0].fOwnerDocument != arg.fOwnerDocument: - raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") + if nList.len > 0: + #Check if newChild is from this nodes document + if nList[0].fOwnerDocument != arg.fOwnerDocument: + raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") #Exceptions End var item: PNode = nList.getNamedItem(arg.nodeName()) @@ -788,11 +778,10 @@ proc setNamedItem*(nList: var seq[PNode], arg: PNode): PNode = proc setNamedItem*(nList: var seq[PAttr], arg: PAttr): PAttr = ## Adds ``arg`` as a ``Node`` to the ``NList`` ## If a node with the same name is already present in this map, it is replaced by the new one. - if not isNil(nList): - if nList.len() > 0: - # Check if newChild is from this nodes document - if nList[0].fOwnerDocument != arg.fOwnerDocument: - raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") + if nList.len > 0: + # Check if newChild is from this nodes document + if nList[0].fOwnerDocument != arg.fOwnerDocument: + raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") if not isNil(arg.fOwnerElement): raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") @@ -814,11 +803,10 @@ proc setNamedItem*(nList: var seq[PAttr], arg: PAttr): PAttr = proc setNamedItemNS*(nList: var seq[PNode], arg: PNode): PNode = ## Adds a node using its ``namespaceURI`` and ``localName`` - if not isNil(nList): - if nList.len() > 0: - # Check if newChild is from this nodes document - if nList[0].fOwnerDocument != arg.fOwnerDocument: - raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") + if nList.len > 0: + # Check if newChild is from this nodes document + if nList[0].fOwnerDocument != arg.fOwnerDocument: + raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") #Exceptions end var item: PNode = nList.getNamedItemNS(arg.namespaceURI(), arg.localName()) @@ -837,11 +825,10 @@ proc setNamedItemNS*(nList: var seq[PNode], arg: PNode): PNode = proc setNamedItemNS*(nList: var seq[PAttr], arg: PAttr): PAttr = ## Adds a node using its ``namespaceURI`` and ``localName`` - if not isNil(nList): - if nList.len() > 0: - # Check if newChild is from this nodes document - if nList[0].fOwnerDocument != arg.fOwnerDocument: - raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") + if nList.len > 0: + # Check if newChild is from this nodes document + if nList[0].fOwnerDocument != arg.fOwnerDocument: + raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") if not isNil(arg.fOwnerElement): raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") @@ -868,17 +855,14 @@ proc setNamedItemNS*(nList: var seq[PAttr], arg: PAttr): PAttr = # Attributes proc name*(a: PAttr): string = ## Returns the name of the Attribute - return a.fName proc specified*(a: PAttr): bool = ## Specifies whether this attribute was specified in the original document - return a.fSpecified proc ownerElement*(a: PAttr): PElement = ## Returns this Attributes owner element - return a.fOwnerElement # Element @@ -886,41 +870,32 @@ proc ownerElement*(a: PAttr): PElement = proc tagName*(el: PElement): string = ## Returns the Element Tag Name - return el.fTagName # Procedures proc getAttribute*(el: PNode, name: string): string = ## Retrieves an attribute value by ``name`` - if isNil(el.attributes): - return nil var attribute = el.attributes.getNamedItem(name) if not isNil(attribute): return attribute.value else: - return nil + return "" proc getAttributeNS*(el: PNode, namespaceURI: string, localName: string): string = ## Retrieves an attribute value by ``localName`` and ``namespaceURI`` - if isNil(el.attributes): - return nil var attribute = el.attributes.getNamedItemNS(namespaceURI, localName) if not isNil(attribute): return attribute.value else: - return nil + return "" proc getAttributeNode*(el: PElement, name: string): PAttr = ## Retrieves an attribute node by ``name`` ## To retrieve an attribute node by qualified name and namespace URI, use the `getAttributeNodeNS` method - if isNil(el.attributes): - return nil return el.attributes.getNamedItem(name) proc getAttributeNodeNS*(el: PElement, namespaceURI: string, localName: string): PAttr = ## Retrieves an `Attr` node by ``localName`` and ``namespaceURI`` - if isNil(el.attributes): - return nil return el.attributes.getNamedItemNS(namespaceURI, localName) proc getElementsByTagName*(el: PElement, name: string): seq[PNode] = @@ -938,41 +913,34 @@ proc getElementsByTagNameNS*(el: PElement, namespaceURI: string, localName: stri proc hasAttribute*(el: PElement, name: string): bool = ## Returns ``true`` when an attribute with a given ``name`` is specified ## on this element , ``false`` otherwise. - if isNil(el.attributes): - return false return not isNil(el.attributes.getNamedItem(name)) proc hasAttributeNS*(el: PElement, namespaceURI: string, localName: string): bool = ## Returns ``true`` when an attribute with a given ``localName`` and ## ``namespaceURI`` is specified on this element , ``false`` otherwise - if isNil(el.attributes): - return false return not isNil(el.attributes.getNamedItemNS(namespaceURI, localName)) proc removeAttribute*(el: PElement, name: string) = ## Removes an attribute by ``name`` - if not isNil(el.attributes): - for i in low(el.attributes)..high(el.attributes): - if el.attributes[i].fName == name: - el.attributes.delete(i) + for i in low(el.attributes)..high(el.attributes): + if el.attributes[i].fName == name: + el.attributes.delete(i) proc removeAttributeNS*(el: PElement, namespaceURI: string, localName: string) = ## Removes an attribute by ``localName`` and ``namespaceURI`` - if not isNil(el.attributes): - for i in low(el.attributes)..high(el.attributes): - if el.attributes[i].fNamespaceURI == namespaceURI and - el.attributes[i].fLocalName == localName: - el.attributes.delete(i) + for i in low(el.attributes)..high(el.attributes): + if el.attributes[i].fNamespaceURI == namespaceURI and + el.attributes[i].fLocalName == localName: + el.attributes.delete(i) proc removeAttributeNode*(el: PElement, oldAttr: PAttr): PAttr = ## Removes the specified attribute node ## If the attribute node cannot be found raises ``ENotFoundErr`` - if not isNil(el.attributes): - for i in low(el.attributes)..high(el.attributes): - if el.attributes[i] == oldAttr: - result = el.attributes[i] - el.attributes.delete(i) - return + for i in low(el.attributes)..high(el.attributes): + if el.attributes[i] == oldAttr: + result = el.attributes[i] + el.attributes.delete(i) + return raise newException(ENotFoundErr, "oldAttr is not a member of el's Attributes") @@ -991,7 +959,6 @@ proc setAttributeNode*(el: PElement, newAttr: PAttr): PAttr = "This attribute is in use by another element, use cloneNode") # Exceptions end - if isNil(el.attributes): el.attributes = @[] return el.attributes.setNamedItem(newAttr) proc setAttributeNodeNS*(el: PElement, newAttr: PAttr): PAttr = @@ -1009,7 +976,6 @@ proc setAttributeNodeNS*(el: PElement, newAttr: PAttr): PAttr = "This attribute is in use by another element, use cloneNode") # Exceptions end - if isNil(el.attributes): el.attributes = @[] return el.attributes.setNamedItemNS(newAttr) proc setAttribute*(el: PElement, name: string, value: string) = @@ -1057,9 +1023,9 @@ proc splitData*(textNode: PText, offset: int): PText = var left: string = textNode.data.substr(0, offset) textNode.data = left - var right: string = textNode.data.substr(offset, textNode.data.len()) + var right: string = textNode.data.substr(offset, textNode.data.len) - if not isNil(textNode.fParentNode) and not isNil(textNode.fParentNode.childNodes): + if not isNil(textNode.fParentNode) and textNode.fParentNode.childNodes.len > 0: for i in low(textNode.fParentNode.childNodes)..high(textNode.fParentNode.childNodes): if textNode.fParentNode.childNodes[i] == textNode: var newNode: PText = textNode.fOwnerDocument.createTextNode(right) @@ -1069,17 +1035,15 @@ proc splitData*(textNode: PText, offset: int): PText = var newNode: PText = textNode.fOwnerDocument.createTextNode(right) return newNode - # ProcessingInstruction proc target*(pi: PProcessingInstruction): string = ## Returns the Processing Instructions target return pi.fTarget - -# --Other stuff-- -# Writer -proc addEscaped(s: string): string = +proc escapeXml*(s: string; result: var string) = + ## Prepares a string for insertion into a XML document + ## by escaping the XML special characters. result = "" for c in items(s): case c @@ -1089,13 +1053,21 @@ proc addEscaped(s: string): string = of '"': result.add(""") else: result.add(c) +proc escapeXml*(s: string): string = + ## Prepares a string for insertion into a XML document + ## by escaping the XML special characters. + result = newStringOfCap(s.len + s.len shr 4) + escapeXml(s, result) + +# --Other stuff-- +# Writer + proc nodeToXml(n: PNode, indent: int = 0): string = result = spaces(indent) & "<" & n.nodeName - if not isNil(n.attributes): - for i in items(n.attributes): - result.add(" " & i.name & "=\"" & addEscaped(i.value) & "\"") + for i in items(n.attributes): + result.add(" " & i.name & "=\"" & escapeXml(i.value) & "\"") - if isNil(n.childNodes) or n.childNodes.len() == 0: + if n.childNodes.len == 0: result.add("/>") # No idea why this doesn't need a \n :O else: # End the beginning of this tag @@ -1106,7 +1078,7 @@ proc nodeToXml(n: PNode, indent: int = 0): string = result.add(nodeToXml(i, indent + 2)) of TextNode: result.add(spaces(indent * 2)) - result.add(addEscaped(i.nodeValue)) + result.add(escapeXml(i.nodeValue)) of CDataSectionNode: result.add(spaces(indent * 2)) result.add("<![CDATA[" & i.nodeValue & "]]>") @@ -1127,3 +1099,4 @@ proc `$`*(doc: PDocument): string = ## Converts a PDocument object into a string representation of it's XML result = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" result.add(nodeToXml(doc.documentElement)) + \ No newline at end of file diff --git a/lib/pure/xmldomparser.nim b/lib/pure/xmldomparser.nim index 7c7f7b99c..8d995102e 100644 --- a/lib/pure/xmldomparser.nim +++ b/lib/pure/xmldomparser.nim @@ -119,7 +119,7 @@ proc loadXMLStream*(stream: Stream): PDocument = ## a ``PDocument`` var x: XmlParser - open(x, stream, nil, {reportComments}) + open(x, stream, "", {reportComments}) var xmlDoc: PDocument var dom: PDOMImplementation = getDOM() @@ -161,7 +161,7 @@ when not defined(testing) and isMainModule: #echo(xml.getElementsByTagName("bla:test")[0].namespaceURI) #echo(xml.getElementsByTagName("test")[0].namespaceURI) for i in items(xml.getElementsByTagName("*")): - if i.namespaceURI != nil: + if i.namespaceURI.len > 0: echo(i.nodeName, "=", i.namespaceURI) diff --git a/lib/pure/xmlparser.nim b/lib/pure/xmlparser.nim index 22bd259b7..597b80eb5 100644 --- a/lib/pure/xmlparser.nim +++ b/lib/pure/xmlparser.nim @@ -12,11 +12,9 @@ import streams, parsexml, strtabs, xmltree type - XmlError* = object of ValueError ## exception that is raised - ## for invalid XML - errors*: seq[string] ## all detected parsing errors - -{.deprecated: [EInvalidXml: XmlError].} + XmlError* = object of ValueError ## Exception that is raised + ## for invalid XML. + errors*: seq[string] ## All detected parsing errors. proc raiseInvalidXml(errors: seq[string]) = var e: ref XmlError @@ -102,8 +100,8 @@ proc parse(x: var XmlParser, errors: var seq[string]): XmlNode = proc parseXml*(s: Stream, filename: string, errors: var seq[string]): XmlNode = - ## parses the XML from stream `s` and returns a ``PXmlNode``. Every - ## occurred parsing error is added to the `errors` sequence. + ## Parses the XML from stream ``s`` and returns a ``XmlNode``. Every + ## occurred parsing error is added to the ``errors`` sequence. var x: XmlParser open(x, s, filename, {reportComments}) while true: @@ -121,15 +119,20 @@ proc parseXml*(s: Stream, filename: string, close(x) proc parseXml*(s: Stream): XmlNode = - ## parses the XTML from stream `s` and returns a ``PXmlNode``. All parsing - ## errors are turned into an ``EInvalidXML`` exception. + ## Parses the XML from stream ``s`` and returns a ``XmlNode``. All parsing + ## errors are turned into an ``XmlError`` exception. var errors: seq[string] = @[] - result = parseXml(s, "unknown_html_doc", errors) + result = parseXml(s, "unknown_xml_doc", errors) if errors.len > 0: raiseInvalidXml(errors) +proc parseXml*(str: string): XmlNode = + ## Parses the XML from string ``str`` and returns a ``XmlNode``. All parsing + ## errors are turned into an ``XmlError`` exception. + parseXml(newStringStream(str)) + proc loadXml*(path: string, errors: var seq[string]): XmlNode = ## Loads and parses XML from file specified by ``path``, and returns - ## a ``PXmlNode``. Every occurred parsing error is added to the `errors` + ## a ``XmlNode``. Every occurred parsing error is added to the ``errors`` ## sequence. var s = newFileStream(path, fmRead) if s == nil: raise newException(IOError, "Unable to read file: " & path) @@ -137,7 +140,7 @@ proc loadXml*(path: string, errors: var seq[string]): XmlNode = proc loadXml*(path: string): XmlNode = ## Loads and parses XML from file specified by ``path``, and returns - ## a ``PXmlNode``. All parsing errors are turned into an ``EInvalidXML`` + ## a ``XmlNode``. All parsing errors are turned into an ``XmlError`` ## exception. var errors: seq[string] = @[] result = loadXml(path, errors) diff --git a/lib/pure/xmltree.nim b/lib/pure/xmltree.nim index 45696c80c..d536cfed0 100644 --- a/lib/pure/xmltree.nim +++ b/lib/pure/xmltree.nim @@ -9,12 +9,12 @@ ## A simple XML tree. More efficient and simpler than the DOM. -import macros, strtabs +import macros, strtabs, strutils type - XmlNode* = ref XmlNodeObj ## an XML tree consists of ``PXmlNode``'s. + XmlNode* = ref XmlNodeObj ## an XML tree consists of ``XmlNode``'s. - XmlNodeKind* = enum ## different kinds of ``PXmlNode``'s + XmlNodeKind* = enum ## different kinds of ``XmlNode``'s xnText, ## a text element xnElement, ## an element with 0 or more children xnCData, ## a CDATA node @@ -33,9 +33,6 @@ type fAttr: XmlAttributes fClientData: int ## for other clients -{.deprecated: [PXmlNode: XmlNode, TXmlNodeKind: XmlNodeKind, PXmlAttributes: - XmlAttributes, TXmlNode: XmlNodeObj].} - proc newXmlNode(kind: XmlNodeKind): XmlNode = ## creates a new ``XmlNode``. new(result) @@ -155,11 +152,6 @@ proc `[]`* (n: var XmlNode, i: int): var XmlNode {.inline.} = assert n.k == xnElement result = n.s[i] -proc mget*(n: var XmlNode, i: int): var XmlNode {.inline, deprecated.} = - ## returns the `i`'th child of `n` so that it can be modified. Use ```[]``` - ## instead. - n[i] - iterator items*(n: XmlNode): XmlNode {.inline.} = ## iterates over any child of `n`. assert n.k == xnElement @@ -225,8 +217,9 @@ proc escape*(s: string): string = result = newStringOfCap(s.len) addEscaped(result, s) -proc addIndent(result: var string, indent: int) = - result.add("\n") +proc addIndent(result: var string, indent: int, addNewLines: bool) = + if addNewLines: + result.add("\n") for i in 1..indent: result.add(' ') proc noWhitespace(n: XmlNode): bool = @@ -235,7 +228,8 @@ proc noWhitespace(n: XmlNode): bool = for i in 0..n.len-1: if n[i].kind in {xnText, xnEntity}: return true -proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2) = +proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2, + addNewLines=true) = ## adds the textual representation of `n` to `result`. proc addEscapedAttr(result: var string, s: string) = @@ -268,14 +262,15 @@ proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2) = # for mixed leaves, we cannot output whitespace for readability, # because this would be wrong. For example: ``a<b>b</b>`` is # different from ``a <b>b</b>``. - for i in 0..n.len-1: result.add(n[i], indent+indWidth, indWidth) + for i in 0..n.len-1: + result.add(n[i], indent+indWidth, indWidth, addNewLines) else: for i in 0..n.len-1: - result.addIndent(indent+indWidth) - result.add(n[i], indent+indWidth, indWidth) - result.addIndent(indent) + result.addIndent(indent+indWidth, addNewLines) + result.add(n[i], indent+indWidth, indWidth, addNewLines) + result.addIndent(indent, addNewLines) else: - result.add(n[0], indent+indWidth, indWidth) + result.add(n[0], indent+indWidth, indWidth, addNewLines) result.add("</") result.add(n.fTag) result.add(">") @@ -315,18 +310,19 @@ proc newXmlTree*(tag: string, children: openArray[XmlNode], for i in 0..children.len-1: result.s[i] = children[i] result.fAttr = attributes -proc xmlConstructor(e: NimNode): NimNode {.compileTime.} = - expectLen(e, 2) - var a = e[1] +proc xmlConstructor(a: NimNode): NimNode {.compileTime.} = if a.kind == nnkCall: result = newCall("newXmlTree", toStrLit(a[0])) var attrs = newNimNode(nnkBracket, a) - var newStringTabCall = newCall("newStringTable", attrs, - newIdentNode("modeCaseSensitive")) + var newStringTabCall = newCall(bindSym"newStringTable", attrs, + bindSym"modeCaseSensitive") var elements = newNimNode(nnkBracket, a) for i in 1..a.len-1: if a[i].kind == nnkExprEqExpr: - attrs.add(toStrLit(a[i][0])) + # In order to support attributes like `data-lang` we have to + # replace whitespace because `toStrLit` gives `data - lang`. + let attrName = toStrLit(a[i][0]).strVal.replace(" ", "") + attrs.add(newStrLitNode(attrName)) attrs.add(a[i][1]) #echo repr(attrs) else: @@ -348,7 +344,6 @@ macro `<>`*(x: untyped): untyped = ## ## <a href="http://nim-lang.org">Nim rules.</a> ## - let x = callsite() result = xmlConstructor(x) proc child*(n: XmlNode, name: string): XmlNode = @@ -382,7 +377,6 @@ proc findAll*(n: XmlNode, tag: string, result: var seq[XmlNode]) = ## findAll(html, "img", tags) ## for imgTag in tags: ## process(imgTag) - assert isNil(result) == false assert n.k == xnElement for child in n.items(): if child.k != xnElement: diff --git a/lib/std/sha1.nim b/lib/std/sha1.nim index b18095ff6..c0b1bffcf 100644 --- a/lib/std/sha1.nim +++ b/lib/std/sha1.nim @@ -7,6 +7,8 @@ # distribution, for details about the copyright. # +## Note: Import ``std/sha1`` to use this module + import strutils const Sha1DigestSize = 20 diff --git a/lib/std/varints.nim b/lib/std/varints.nim new file mode 100644 index 000000000..483d5c96c --- /dev/null +++ b/lib/std/varints.nim @@ -0,0 +1,152 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2018 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Note this API is still experimental! A variable length integer +## encoding implementation inspired by SQLite. + +const + maxVarIntLen* = 9 ## the maximal number of bytes a varint can take + +proc readVu64*(z: openArray[byte]; pResult: var uint64): int = + if z[0] <= 240: + pResult = z[0] + return 1 + if z[0] <= 248: + if z.len < 2: return 0 + pResult = (uint64 z[0] - 241) * 256 + z[1].uint64 + 240 + return 2 + if z.len < int(z[0]-246): return 0 + if z[0] == 249: + pResult = 2288u64 + 256u64*z[1].uint64 + z[2].uint64 + return 3 + if z[0] == 250: + pResult = (z[1].uint64 shl 16u64) + (z[2].uint64 shl 8u64) + z[3].uint64 + return 4 + let x = (z[1].uint64 shl 24) + (z[2].uint64 shl 16) + (z[3].uint64 shl 8) + z[4].uint64 + if z[0] == 251: + pResult = x + return 5 + if z[0] == 252: + pResult = (((uint64)x) shl 8) + z[5].uint64 + return 6 + if z[0] == 253: + pResult = (((uint64)x) shl 16) + (z[5].uint64 shl 8) + z[6].uint64 + return 7 + if z[0] == 254: + pResult = (((uint64)x) shl 24) + (z[5].uint64 shl 16) + (z[6].uint64 shl 8) + z[7].uint64 + return 8 + pResult = (((uint64)x) shl 32) + + (0xffffffff'u64 and ((z[5].uint64 shl 24) + + (z[6].uint64 shl 16) + (z[7].uint64 shl 8) + z[8].uint64)) + return 9 + +proc varintWrite32(z: var openArray[byte]; y: uint32) = + z[0] = uint8(y shr 24) + z[1] = uint8(y shr 16) + z[2] = uint8(y shr 8) + z[3] = uint8(y) + +proc writeVu64*(z: var openArray[byte], x: uint64): int = + ## Write a varint into z. The buffer z must be at least 9 characters + ## long to accommodate the largest possible varint. Returns the number of + ## bytes used. + if x <= 240: + z[0] = uint8 x + return 1 + if x <= 2287: + let y = uint32(x - 240) + z[0] = uint8(y shr 8 + 241) + z[1] = uint8(y and 255) + return 2 + if x <= 67823: + let y = uint32(x - 2288) + z[0] = 249 + z[1] = uint8(y shr 8) + z[2] = uint8(y and 255) + return 3 + let y = uint32 x + let w = uint32(x shr 32) + if w == 0: + if y <= 16777215: + z[0] = 250 + z[1] = uint8(y shr 16) + z[2] = uint8(y shr 8) + z[3] = uint8(y) + return 4 + z[0] = 251 + varintWrite32(toOpenArray(z, 1, z.high-1), y) + return 5 + if w <= 255: + z[0] = 252 + z[1] = uint8 w + varintWrite32(toOpenArray(z, 2, z.high-2), y) + return 6 + if w <= 65535: + z[0] = 253 + z[1] = uint8(w shr 8) + z[2] = uint8 w + varintWrite32(toOpenArray(z, 3, z.high-3), y) + return 7 + if w <= 16777215: + z[0] = 254 + z[1] = uint8(w shr 16) + z[2] = uint8(w shr 8) + z[3] = uint8 w + varintWrite32(toOpenArray(z, 4, z.high-4), y) + return 8 + z[0] = 255 + varintWrite32(toOpenArray(z, 1, z.high-1), w) + varintWrite32(toOpenArray(z, 5, z.high-5), y) + return 9 + +proc sar(a, b: int64): int64 = + {.emit: [result, " = ", a, " >> ", b, ";"].} + +proc sal(a, b: int64): int64 = + {.emit: [result, " = ", a, " << ", b, ";"].} + +proc encodeZigzag*(x: int64): uint64 {.inline.} = + uint64(sal(x, 1)) xor uint64(sar(x, 63)) + +proc decodeZigzag*(x: uint64): int64 {.inline.} = + let casted = cast[int64](x) + result = (`shr`(casted, 1)) xor (-(casted and 1)) + +when isMainModule: + #import random + + var dest: array[50, byte] + var got: uint64 + + for test in [0xFFFF_FFFF_FFFFF_FFFFu64, 77u64, 0u64, 10_000_000u64, uint64(high(int64)), + uint64(high(int32)),uint64(low(int32)),uint64(low(int64))]: + let wrLen = writeVu64(dest, test) + let rdLen = readVu64(dest, got) + assert wrLen == rdLen + echo(if got == test: "YES" else: "NO") + echo "number is ", got + + if encodeZigzag(decodeZigzag(test)) != test: + echo "Failure for ", test, " ", encodeZigzag(decodeZigzag(test)), " ", decodeZigzag(test) + + for test in 0u64..300u64: + let wrLen = writeVu64(dest, test) + let rdLen = readVu64(dest, got) + assert wrLen == rdLen + if got != test: + echo "BUG! expected: ", test, " got: ", got, " z0: ", dest[0] + + # check this also works for floats: + for test in [0.0, 0.1, 2.0, +Inf, Nan, NegInf]: + let t = cast[uint64](test) + let wrLenB = writeVu64(dest, t) + let rdLenB = readVu64(dest, got) + assert wrLenB == rdLenB + echo rdLenB + echo(if cast[float64](got) == test: "YES" else: "NO") diff --git a/lib/system.nim b/lib/system.nim index 8e6b997b6..a406c7811 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -59,13 +59,13 @@ const {.push warning[GcMem]: off, warning[Uninit]: off.} {.push hints: off.} -proc `or` *(a, b: typedesc): typedesc {.magic: "TypeTrait", noSideEffect.} +proc `or`*(a, b: typedesc): typedesc {.magic: "TypeTrait", noSideEffect.} ## Constructs an `or` meta class -proc `and` *(a, b: typedesc): typedesc {.magic: "TypeTrait", noSideEffect.} +proc `and`*(a, b: typedesc): typedesc {.magic: "TypeTrait", noSideEffect.} ## Constructs an `and` meta class -proc `not` *(a: typedesc): typedesc {.magic: "TypeTrait", noSideEffect.} +proc `not`*(a: typedesc): typedesc {.magic: "TypeTrait", noSideEffect.} ## Constructs an `not` meta class type @@ -79,11 +79,6 @@ type `nil` {.magic: "Nil".} - expr* {.magic: Expr, deprecated.} ## meta type to denote an expression (for templates) - ## **Deprecated** since version 0.15. Use ``untyped`` instead. - stmt* {.magic: Stmt, deprecated.} ## meta type to denote a statement (for templates) - ## **Deprecated** since version 0.15. Use ``typed`` instead. - void* {.magic: "VoidType".} ## meta type to denote the absence of any type auto* {.magic: Expr.} ## meta type for automatic type determination any* = distinct auto ## meta type for any supported type @@ -105,12 +100,14 @@ type ## type class matching all ordinal types; however this includes enums with ## holes. - SomeReal* = float|float32|float64 + SomeFloat* = float|float32|float64 ## type class matching all floating point number types - SomeNumber* = SomeInteger|SomeReal + SomeNumber* = SomeInteger|SomeFloat ## type class matching all number types +{.deprecated: [SomeReal: SomeFloat].} + proc defined*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.} ## Special compile-time procedure that checks whether `x` is ## defined. @@ -123,15 +120,6 @@ proc defined*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.} ## # Do here programmer friendly expensive sanity checks. ## # Put here the normal code -when defined(nimalias): - {.deprecated: [ - TSignedInt: SomeSignedInt, - TUnsignedInt: SomeUnsignedInt, - TInteger: SomeInteger, - TReal: SomeReal, - TNumber: SomeNumber, - TOrdinal: SomeOrdinal].} - proc declared*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.} ## Special compile-time procedure that checks whether `x` is ## declared. `x` has to be an identifier or a qualified identifier. @@ -144,11 +132,7 @@ proc declared*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.} ## # missing it. when defined(useNimRtl): - {.deadCodeElim: on.} - -proc definedInScope*(x: untyped): bool {. - magic: "DefinedInScope", noSideEffect, deprecated, compileTime.} - ## **Deprecated since version 0.9.6**: Use ``declaredInScope`` instead. + {.deadCodeElim: on.} # dce option deprecated proc declaredInScope*(x: untyped): bool {. magic: "DefinedInScope", noSideEffect, compileTime.} @@ -177,12 +161,26 @@ proc unsafeAddr*[T](x: T): ptr T {.magic: "Addr", noSideEffect.} = ## Cannot be overloaded. discard -proc `type`*(x: untyped): typeDesc {.magic: "TypeOf", noSideEffect, compileTime.} = - ## Builtin 'type' operator for accessing the type of an expression. - ## Cannot be overloaded. - discard +when defined(nimNewTypedesc): + type + `static`* {.magic: "Static".}[T] + ## meta type representing all values that can be evaluated at compile-time. + ## + ## The type coercion ``static(x)`` can be used to force the compile-time + ## evaluation of the given expression ``x``. -proc `not` *(x: bool): bool {.magic: "Not", noSideEffect.} + `type`* {.magic: "Type".}[T] + ## meta type representing the type of all type values. + ## + ## The coercion ``type(x)`` can be used to obtain the type of the given + ## expression ``x``. +else: + proc `type`*(x: untyped): typeDesc {.magic: "TypeOf", noSideEffect, compileTime.} = + ## Builtin 'type' operator for accessing the type of an expression. + ## Cannot be overloaded. + discard + +proc `not`*(x: bool): bool {.magic: "Not", noSideEffect.} ## Boolean not; returns true iff ``x == false``. proc `and`*(x, y: bool): bool {.magic: "And", noSideEffect.} @@ -213,6 +211,7 @@ proc new*(T: typedesc): auto = new(r) return r +const ThisIsSystem = true proc internalNew*[T](a: var ref T) {.magic: "New", noSideEffect.} ## leaked implementation detail. Do not use. @@ -231,6 +230,17 @@ proc reset*[T](obj: var T) {.magic: "Reset", noSideEffect.} ## resets an object `obj` to its initial (binary zero) value. This needs to ## be called before any possible `object branch transition`:idx:. +when defined(nimNewRuntime): + proc wasMoved*[T](obj: var T) {.magic: "WasMoved", noSideEffect.} = + ## resets an object `obj` to its initial (binary zero) value to signify + ## it was "moved" and to signify its destructor should do nothing and + ## ideally be optimized away. + discard + + proc move*[T](x: var T): T {.magic: "Move", noSideEffect.} = + result = x + wasMoved(x) + type range*{.magic: "Range".}[T] ## Generic type to construct range types. array*{.magic: "Array".}[I, T] ## Generic type to construct @@ -265,14 +275,14 @@ proc high*[T: Ordinal](x: T): T {.magic: "High", noSideEffect.} ## high(2) #=> 9223372036854775807 ## high(int) #=> 9223372036854775807 -proc high*[T: Ordinal](x: typeDesc[T]): T {.magic: "High", noSideEffect.} +proc high*[T: Ordinal|enum](x: typeDesc[T]): T {.magic: "High", noSideEffect.} proc high*[T](x: openArray[T]): int {.magic: "High", noSideEffect.} proc high*[I, T](x: array[I, T]): I {.magic: "High", noSideEffect.} proc high*[I, T](x: typeDesc[array[I, T]]): I {.magic: "High", noSideEffect.} proc high*(x: cstring): int {.magic: "High", noSideEffect.} proc high*(x: string): int {.magic: "High", noSideEffect.} -proc low*[T: Ordinal](x: typeDesc[T]): T {.magic: "Low", noSideEffect.} +proc low*[T: Ordinal|enum](x: typeDesc[T]): T {.magic: "Low", noSideEffect.} proc low*[T](x: openArray[T]): int {.magic: "Low", noSideEffect.} proc low*[I, T](x: array[I, T]): I {.magic: "Low", noSideEffect.} proc low*[T](x: T): T {.magic: "Low", noSideEffect.} @@ -303,6 +313,12 @@ when defined(nimArrIdx): proc `[]=`*[I: Ordinal;T,S](a: T; i: I; x: S) {.noSideEffect, magic: "ArrPut".} proc `=`*[T](dest: var T; src: T) {.noSideEffect, magic: "Asgn".} + + proc arrGet[I: Ordinal;T](a: T; i: I): T {. + noSideEffect, magic: "ArrGet".} + proc arrPut[I: Ordinal;T,S](a: T; i: I; + x: S) {.noSideEffect, magic: "ArrPut".} + when defined(nimNewRuntime): proc `=destroy`*[T](x: var T) {.inline, magic: "Asgn".} = ## generic `destructor`:idx: implementation that can be overriden. @@ -335,7 +351,7 @@ when not defined(nimunion): {.pragma: unchecked.} # comparison operators: -proc `==` *[Enum: enum](x, y: Enum): bool {.magic: "EqEnum", noSideEffect.} +proc `==`*[Enum: enum](x, y: Enum): bool {.magic: "EqEnum", noSideEffect.} ## Checks whether values within the *same enum* have the same underlying value ## ## .. code-block:: nim @@ -349,59 +365,59 @@ proc `==` *[Enum: enum](x, y: Enum): bool {.magic: "EqEnum", noSideEffect.} ## e2 = Enum1(Place2) ## echo (e1 == e2) # true ## echo (e1 == Place2) # raises error -proc `==` *(x, y: pointer): bool {.magic: "EqRef", noSideEffect.} +proc `==`*(x, y: pointer): bool {.magic: "EqRef", noSideEffect.} ## .. code-block:: nim ## var # this is a wildly dangerous example ## a = cast[pointer](0) ## b = cast[pointer](nil) ## echo (a == b) # true due to the special meaning of `nil`/0 as a pointer -proc `==` *(x, y: string): bool {.magic: "EqStr", noSideEffect.} +proc `==`*(x, y: string): bool {.magic: "EqStr", noSideEffect.} ## Checks for equality between two `string` variables -proc `==` *(x, y: char): bool {.magic: "EqCh", noSideEffect.} +proc `==`*(x, y: char): bool {.magic: "EqCh", noSideEffect.} ## Checks for equality between two `char` variables -proc `==` *(x, y: bool): bool {.magic: "EqB", noSideEffect.} +proc `==`*(x, y: bool): bool {.magic: "EqB", noSideEffect.} ## Checks for equality between two `bool` variables -proc `==` *[T](x, y: set[T]): bool {.magic: "EqSet", noSideEffect.} +proc `==`*[T](x, y: set[T]): bool {.magic: "EqSet", noSideEffect.} ## Checks for equality between two variables of type `set` ## ## .. code-block:: nim ## var a = {1, 2, 2, 3} # duplication in sets is ignored ## var b = {1, 2, 3} ## echo (a == b) # true -proc `==` *[T](x, y: ref T): bool {.magic: "EqRef", noSideEffect.} +proc `==`*[T](x, y: ref T): bool {.magic: "EqRef", noSideEffect.} ## Checks that two `ref` variables refer to the same item -proc `==` *[T](x, y: ptr T): bool {.magic: "EqRef", noSideEffect.} +proc `==`*[T](x, y: ptr T): bool {.magic: "EqRef", noSideEffect.} ## Checks that two `ptr` variables refer to the same item -proc `==` *[T: proc](x, y: T): bool {.magic: "EqProc", noSideEffect.} +proc `==`*[T: proc](x, y: T): bool {.magic: "EqProc", noSideEffect.} ## Checks that two `proc` variables refer to the same procedure -proc `<=` *[Enum: enum](x, y: Enum): bool {.magic: "LeEnum", noSideEffect.} -proc `<=` *(x, y: string): bool {.magic: "LeStr", noSideEffect.} -proc `<=` *(x, y: char): bool {.magic: "LeCh", noSideEffect.} -proc `<=` *[T](x, y: set[T]): bool {.magic: "LeSet", noSideEffect.} -proc `<=` *(x, y: bool): bool {.magic: "LeB", noSideEffect.} -proc `<=` *[T](x, y: ref T): bool {.magic: "LePtr", noSideEffect.} -proc `<=` *(x, y: pointer): bool {.magic: "LePtr", noSideEffect.} - -proc `<` *[Enum: enum](x, y: Enum): bool {.magic: "LtEnum", noSideEffect.} -proc `<` *(x, y: string): bool {.magic: "LtStr", noSideEffect.} -proc `<` *(x, y: char): bool {.magic: "LtCh", noSideEffect.} -proc `<` *[T](x, y: set[T]): bool {.magic: "LtSet", noSideEffect.} -proc `<` *(x, y: bool): bool {.magic: "LtB", noSideEffect.} -proc `<` *[T](x, y: ref T): bool {.magic: "LtPtr", noSideEffect.} -proc `<` *[T](x, y: ptr T): bool {.magic: "LtPtr", noSideEffect.} -proc `<` *(x, y: pointer): bool {.magic: "LtPtr", noSideEffect.} - -template `!=` * (x, y: untyped): untyped = +proc `<=`*[Enum: enum](x, y: Enum): bool {.magic: "LeEnum", noSideEffect.} +proc `<=`*(x, y: string): bool {.magic: "LeStr", noSideEffect.} +proc `<=`*(x, y: char): bool {.magic: "LeCh", noSideEffect.} +proc `<=`*[T](x, y: set[T]): bool {.magic: "LeSet", noSideEffect.} +proc `<=`*(x, y: bool): bool {.magic: "LeB", noSideEffect.} +proc `<=`*[T](x, y: ref T): bool {.magic: "LePtr", noSideEffect.} +proc `<=`*(x, y: pointer): bool {.magic: "LePtr", noSideEffect.} + +proc `<`*[Enum: enum](x, y: Enum): bool {.magic: "LtEnum", noSideEffect.} +proc `<`*(x, y: string): bool {.magic: "LtStr", noSideEffect.} +proc `<`*(x, y: char): bool {.magic: "LtCh", noSideEffect.} +proc `<`*[T](x, y: set[T]): bool {.magic: "LtSet", noSideEffect.} +proc `<`*(x, y: bool): bool {.magic: "LtB", noSideEffect.} +proc `<`*[T](x, y: ref T): bool {.magic: "LtPtr", noSideEffect.} +proc `<`*[T](x, y: ptr T): bool {.magic: "LtPtr", noSideEffect.} +proc `<`*(x, y: pointer): bool {.magic: "LtPtr", noSideEffect.} + +template `!=`*(x, y: untyped): untyped = ## unequals operator. This is a shorthand for ``not (x == y)``. not (x == y) -template `>=` * (x, y: untyped): untyped = +template `>=`*(x, y: untyped): untyped = ## "is greater or equals" operator. This is the same as ``y <= x``. y <= x -template `>` * (x, y: untyped): untyped = +template `>`*(x, y: untyped): untyped = ## "is greater" operator. This is the same as ``y < x``. y < x @@ -415,7 +431,7 @@ include "system/inclrtl" const NoFakeVars* = defined(nimscript) ## true if the backend doesn't support \ ## "fake variables" like 'var EBADF {.importc.}: cint'. -when not defined(JS): +when not defined(JS) and not defined(gcDestructors): type TGenericSeq {.compilerproc, pure, inheritable.} = object len, reserved: int @@ -428,8 +444,9 @@ when not defined(JS): NimString = ptr NimStringDesc when not defined(JS) and not defined(nimscript): - template space(s: PGenericSeq): int {.dirty.} = - s.reserved and not (seqShallowFlag or strlitFlag) + when not defined(gcDestructors): + template space(s: PGenericSeq): int {.dirty.} = + s.reserved and not (seqShallowFlag or strlitFlag) include "system/hti" type @@ -483,165 +500,112 @@ type trace: string else: trace: seq[StackTraceEntry] + raise_id: uint # set when exception is raised up: ref Exception # used for stacking exceptions. Not exported! - SystemError* = object of Exception ## \ - ## Abstract class for exceptions that the runtime system raises. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - IOError* = object of SystemError ## \ + Defect* = object of Exception ## \ + ## Abstract base class for all exceptions that Nim's runtime raises + ## but that are strictly uncatchable as they can also be mapped to + ## a ``quit`` / ``trap`` / ``exit`` operation. + + CatchableError* = object of Exception ## \ + ## Abstract class for all exceptions that are catchable. + IOError* = object of CatchableError ## \ ## Raised if an IO error occurred. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. EOFError* = object of IOError ## \ ## Raised if an IO "end of file" error occurred. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - OSError* = object of SystemError ## \ + OSError* = object of CatchableError ## \ ## Raised if an operating system service failed. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. errorCode*: int32 ## OS-defined error code describing this error. LibraryError* = object of OSError ## \ ## Raised if a dynamic library could not be loaded. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - ResourceExhaustedError* = object of SystemError ## \ + ResourceExhaustedError* = object of CatchableError ## \ ## Raised if a resource request could not be fulfilled. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - ArithmeticError* = object of Exception ## \ + ArithmeticError* = object of Defect ## \ ## Raised if any kind of arithmetic error occurred. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. DivByZeroError* = object of ArithmeticError ## \ ## Raised for runtime integer divide-by-zero errors. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. OverflowError* = object of ArithmeticError ## \ ## Raised for runtime integer overflows. ## ## This happens for calculations whose results are too large to fit in the - ## provided bits. See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - AccessViolationError* = object of Exception ## \ + ## provided bits. + AccessViolationError* = object of Defect ## \ ## Raised for invalid memory access errors - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - AssertionError* = object of Exception ## \ + AssertionError* = object of Defect ## \ ## Raised when assertion is proved wrong. ## - ## Usually the result of using the `assert() template <#assert>`_. See the - ## full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - ValueError* = object of Exception ## \ + ## Usually the result of using the `assert() template <#assert>`_. + ValueError* = object of Defect ## \ ## Raised for string and object conversion errors. KeyError* = object of ValueError ## \ ## Raised if a key cannot be found in a table. ## ## Mostly used by the `tables <tables.html>`_ module, it can also be raised ## by other collection modules like `sets <sets.html>`_ or `strtabs - ## <strtabs.html>`_. See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - OutOfMemError* = object of SystemError ## \ + ## <strtabs.html>`_. + OutOfMemError* = object of Defect ## \ ## Raised for unsuccessful attempts to allocate memory. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - IndexError* = object of Exception ## \ + IndexError* = object of Defect ## \ ## Raised if an array index is out of bounds. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - FieldError* = object of Exception ## \ + FieldError* = object of Defect ## \ ## Raised if a record field is not accessible because its dicriminant's ## value does not fit. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - RangeError* = object of Exception ## \ + RangeError* = object of Defect ## \ ## Raised if a range check error occurred. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - StackOverflowError* = object of SystemError ## \ + StackOverflowError* = object of Defect ## \ ## Raised if the hardware stack used for subroutine calls overflowed. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - ReraiseError* = object of Exception ## \ + ReraiseError* = object of Defect ## \ ## Raised if there is no exception to reraise. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - ObjectAssignmentError* = object of Exception ## \ + ObjectAssignmentError* = object of Defect ## \ ## Raised if an object gets assigned to its parent's object. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - ObjectConversionError* = object of Exception ## \ + ObjectConversionError* = object of Defect ## \ ## Raised if an object is converted to an incompatible object type. ## You can use ``of`` operator to check if conversion will succeed. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - FloatingPointError* = object of Exception ## \ + FloatingPointError* = object of Defect ## \ ## Base class for floating point exceptions. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. FloatInvalidOpError* = object of FloatingPointError ## \ ## Raised by invalid operations according to IEEE. ## - ## Raised by ``0.0/0.0``, for example. See the full `exception - ## hierarchy <manual.html#exception-handling-exception-hierarchy>`_. + ## Raised by ``0.0/0.0``, for example. FloatDivByZeroError* = object of FloatingPointError ## \ ## Raised by division by zero. ## - ## Divisor is zero and dividend is a finite nonzero number. See the full - ## `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. + ## Divisor is zero and dividend is a finite nonzero number. FloatOverflowError* = object of FloatingPointError ## \ ## Raised for overflows. ## ## The operation produced a result that exceeds the range of the exponent. - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. FloatUnderflowError* = object of FloatingPointError ## \ ## Raised for underflows. ## ## The operation produced a result that is too small to be represented as a - ## normal number. See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. + ## normal number. FloatInexactError* = object of FloatingPointError ## \ ## Raised for inexact results. ## ## The operation produced a result that cannot be represented with infinite ## precision -- for example: ``2.0 / 3.0, log(1.1)`` ## - ## **NOTE**: Nim currently does not detect these! See the full - ## `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - DeadThreadError* = object of Exception ## \ + ## **NOTE**: Nim currently does not detect these! + DeadThreadError* = object of Defect ## \ ## Raised if it is attempted to send a message to a dead thread. - ## - ## See the full `exception hierarchy <manual.html#exception-handling-exception-hierarchy>`_. - NilAccessError* = object of SystemError ## \ + NilAccessError* = object of Defect ## \ ## Raised on dereferences of ``nil`` pointers. ## ## This is only raised if the ``segfaults.nim`` module was imported! -{.deprecated: [TObject: RootObj, PObject: RootRef, TEffect: RootEffect, - FTime: TimeEffect, FIO: IOEffect, FReadIO: ReadIOEffect, - FWriteIO: WriteIOEffect, FExecIO: ExecIOEffect, - - E_Base: Exception, ESystem: SystemError, EIO: IOError, - EOS: OSError, EInvalidLibrary: LibraryError, - EResourceExhausted: ResourceExhaustedError, - EArithmetic: ArithmeticError, EDivByZero: DivByZeroError, - EOverflow: OverflowError, EAccessViolation: AccessViolationError, - EAssertionFailed: AssertionError, EInvalidValue: ValueError, - EInvalidKey: KeyError, EOutOfMemory: OutOfMemError, - EInvalidIndex: IndexError, EInvalidField: FieldError, - EOutOfRange: RangeError, EStackOverflow: StackOverflowError, - ENoExceptionToReraise: ReraiseError, - EInvalidObjectAssignment: ObjectAssignmentError, - EInvalidObjectConversion: ObjectConversionError, - EDeadThread: DeadThreadError, - EFloatInexact: FloatInexactError, - EFloatUnderflow: FloatUnderflowError, - EFloatingPoint: FloatingPointError, - EFloatInvalidOp: FloatInvalidOpError, - EFloatDivByZero: FloatDivByZeroError, - EFloatOverflow: FloatOverflowError, - ESynch: Exception -].} +when defined(nimNewRuntime): + type + MoveError* = object of Defect ## \ + ## Raised on attempts to re-sink an already consumed ``sink`` parameter. + +when defined(js) or defined(nimdoc): + type + JsRoot* = ref object of RootObj + ## Root type of the JavaScript object hierarchy proc unsafeNew*[T](a: var ref T, size: Natural) {.magic: "New", noSideEffect.} ## creates a new object of type ``T`` and returns a safe (traced) @@ -747,7 +711,8 @@ proc newSeqOfCap*[T](cap: Natural): seq[T] {. ## ``cap``. discard -when not defined(JS): +when not defined(JS) and not defined(gcDestructors): + # XXX enable this for --gc:destructors proc newSeqUninitialized*[T: SomeNumber](len: Natural): seq[T] = ## creates a new sequence of type ``seq[T]`` with length ``len``. ## @@ -809,7 +774,7 @@ proc card*[T](x: set[T]): int {.magic: "Card", noSideEffect.} ## var i = {1,2,3,4} ## card(i) #=> 4 -proc ord*[T](x: T): int {.magic: "Ord", noSideEffect.} +proc ord*[T: Ordinal|enum](x: T): int {.magic: "Ord", noSideEffect.} ## returns the internal int value of an ordinal value ``x``. ## ## .. code-block:: nim @@ -858,68 +823,68 @@ when not defined(JS): ## last 32 bits from `x`. # integer calculations: -proc `+` *(x: int): int {.magic: "UnaryPlusI", noSideEffect.} -proc `+` *(x: int8): int8 {.magic: "UnaryPlusI", noSideEffect.} -proc `+` *(x: int16): int16 {.magic: "UnaryPlusI", noSideEffect.} -proc `+` *(x: int32): int32 {.magic: "UnaryPlusI", noSideEffect.} -proc `+` *(x: int64): int64 {.magic: "UnaryPlusI", noSideEffect.} +proc `+`*(x: int): int {.magic: "UnaryPlusI", noSideEffect.} +proc `+`*(x: int8): int8 {.magic: "UnaryPlusI", noSideEffect.} +proc `+`*(x: int16): int16 {.magic: "UnaryPlusI", noSideEffect.} +proc `+`*(x: int32): int32 {.magic: "UnaryPlusI", noSideEffect.} +proc `+`*(x: int64): int64 {.magic: "UnaryPlusI", noSideEffect.} ## Unary `+` operator for an integer. Has no effect. -proc `-` *(x: int): int {.magic: "UnaryMinusI", noSideEffect.} -proc `-` *(x: int8): int8 {.magic: "UnaryMinusI", noSideEffect.} -proc `-` *(x: int16): int16 {.magic: "UnaryMinusI", noSideEffect.} -proc `-` *(x: int32): int32 {.magic: "UnaryMinusI", noSideEffect.} -proc `-` *(x: int64): int64 {.magic: "UnaryMinusI64", noSideEffect.} +proc `-`*(x: int): int {.magic: "UnaryMinusI", noSideEffect.} +proc `-`*(x: int8): int8 {.magic: "UnaryMinusI", noSideEffect.} +proc `-`*(x: int16): int16 {.magic: "UnaryMinusI", noSideEffect.} +proc `-`*(x: int32): int32 {.magic: "UnaryMinusI", noSideEffect.} +proc `-`*(x: int64): int64 {.magic: "UnaryMinusI64", noSideEffect.} ## Unary `-` operator for an integer. Negates `x`. -proc `not` *(x: int): int {.magic: "BitnotI", noSideEffect.} -proc `not` *(x: int8): int8 {.magic: "BitnotI", noSideEffect.} -proc `not` *(x: int16): int16 {.magic: "BitnotI", noSideEffect.} -proc `not` *(x: int32): int32 {.magic: "BitnotI", noSideEffect.} +proc `not`*(x: int): int {.magic: "BitnotI", noSideEffect.} +proc `not`*(x: int8): int8 {.magic: "BitnotI", noSideEffect.} +proc `not`*(x: int16): int16 {.magic: "BitnotI", noSideEffect.} +proc `not`*(x: int32): int32 {.magic: "BitnotI", noSideEffect.} ## computes the `bitwise complement` of the integer `x`. when defined(nimnomagic64): - proc `not` *(x: int64): int64 {.magic: "BitnotI", noSideEffect.} + proc `not`*(x: int64): int64 {.magic: "BitnotI", noSideEffect.} else: - proc `not` *(x: int64): int64 {.magic: "BitnotI64", noSideEffect.} + proc `not`*(x: int64): int64 {.magic: "BitnotI64", noSideEffect.} -proc `+` *(x, y: int): int {.magic: "AddI", noSideEffect.} -proc `+` *(x, y: int8): int8 {.magic: "AddI", noSideEffect.} -proc `+` *(x, y: int16): int16 {.magic: "AddI", noSideEffect.} -proc `+` *(x, y: int32): int32 {.magic: "AddI", noSideEffect.} +proc `+`*(x, y: int): int {.magic: "AddI", noSideEffect.} +proc `+`*(x, y: int8): int8 {.magic: "AddI", noSideEffect.} +proc `+`*(x, y: int16): int16 {.magic: "AddI", noSideEffect.} +proc `+`*(x, y: int32): int32 {.magic: "AddI", noSideEffect.} ## Binary `+` operator for an integer. when defined(nimnomagic64): - proc `+` *(x, y: int64): int64 {.magic: "AddI", noSideEffect.} + proc `+`*(x, y: int64): int64 {.magic: "AddI", noSideEffect.} else: - proc `+` *(x, y: int64): int64 {.magic: "AddI64", noSideEffect.} + proc `+`*(x, y: int64): int64 {.magic: "AddI64", noSideEffect.} -proc `-` *(x, y: int): int {.magic: "SubI", noSideEffect.} -proc `-` *(x, y: int8): int8 {.magic: "SubI", noSideEffect.} -proc `-` *(x, y: int16): int16 {.magic: "SubI", noSideEffect.} -proc `-` *(x, y: int32): int32 {.magic: "SubI", noSideEffect.} +proc `-`*(x, y: int): int {.magic: "SubI", noSideEffect.} +proc `-`*(x, y: int8): int8 {.magic: "SubI", noSideEffect.} +proc `-`*(x, y: int16): int16 {.magic: "SubI", noSideEffect.} +proc `-`*(x, y: int32): int32 {.magic: "SubI", noSideEffect.} ## Binary `-` operator for an integer. when defined(nimnomagic64): - proc `-` *(x, y: int64): int64 {.magic: "SubI", noSideEffect.} + proc `-`*(x, y: int64): int64 {.magic: "SubI", noSideEffect.} else: - proc `-` *(x, y: int64): int64 {.magic: "SubI64", noSideEffect.} + proc `-`*(x, y: int64): int64 {.magic: "SubI64", noSideEffect.} -proc `*` *(x, y: int): int {.magic: "MulI", noSideEffect.} -proc `*` *(x, y: int8): int8 {.magic: "MulI", noSideEffect.} -proc `*` *(x, y: int16): int16 {.magic: "MulI", noSideEffect.} -proc `*` *(x, y: int32): int32 {.magic: "MulI", noSideEffect.} +proc `*`*(x, y: int): int {.magic: "MulI", noSideEffect.} +proc `*`*(x, y: int8): int8 {.magic: "MulI", noSideEffect.} +proc `*`*(x, y: int16): int16 {.magic: "MulI", noSideEffect.} +proc `*`*(x, y: int32): int32 {.magic: "MulI", noSideEffect.} ## Binary `*` operator for an integer. when defined(nimnomagic64): - proc `*` *(x, y: int64): int64 {.magic: "MulI", noSideEffect.} + proc `*`*(x, y: int64): int64 {.magic: "MulI", noSideEffect.} else: - proc `*` *(x, y: int64): int64 {.magic: "MulI64", noSideEffect.} + proc `*`*(x, y: int64): int64 {.magic: "MulI64", noSideEffect.} -proc `div` *(x, y: int): int {.magic: "DivI", noSideEffect.} -proc `div` *(x, y: int8): int8 {.magic: "DivI", noSideEffect.} -proc `div` *(x, y: int16): int16 {.magic: "DivI", noSideEffect.} -proc `div` *(x, y: int32): int32 {.magic: "DivI", noSideEffect.} +proc `div`*(x, y: int): int {.magic: "DivI", noSideEffect.} +proc `div`*(x, y: int8): int8 {.magic: "DivI", noSideEffect.} +proc `div`*(x, y: int16): int16 {.magic: "DivI", noSideEffect.} +proc `div`*(x, y: int32): int32 {.magic: "DivI", noSideEffect.} ## computes the integer division. This is roughly the same as ## ``trunc(x/y)``. ## @@ -930,14 +895,14 @@ proc `div` *(x, y: int32): int32 {.magic: "DivI", noSideEffect.} ## 7 div 5 == 1 when defined(nimnomagic64): - proc `div` *(x, y: int64): int64 {.magic: "DivI", noSideEffect.} + proc `div`*(x, y: int64): int64 {.magic: "DivI", noSideEffect.} else: - proc `div` *(x, y: int64): int64 {.magic: "DivI64", noSideEffect.} + proc `div`*(x, y: int64): int64 {.magic: "DivI64", noSideEffect.} -proc `mod` *(x, y: int): int {.magic: "ModI", noSideEffect.} -proc `mod` *(x, y: int8): int8 {.magic: "ModI", noSideEffect.} -proc `mod` *(x, y: int16): int16 {.magic: "ModI", noSideEffect.} -proc `mod` *(x, y: int32): int32 {.magic: "ModI", noSideEffect.} +proc `mod`*(x, y: int): int {.magic: "ModI", noSideEffect.} +proc `mod`*(x, y: int8): int8 {.magic: "ModI", noSideEffect.} +proc `mod`*(x, y: int16): int16 {.magic: "ModI", noSideEffect.} +proc `mod`*(x, y: int32): int32 {.magic: "ModI", noSideEffect.} ## computes the integer modulo operation (remainder). ## This is the same as ## ``x - (x div y) * y``. @@ -946,16 +911,16 @@ proc `mod` *(x, y: int32): int32 {.magic: "ModI", noSideEffect.} ## (7 mod 5) == 2 when defined(nimnomagic64): - proc `mod` *(x, y: int64): int64 {.magic: "ModI", noSideEffect.} + proc `mod`*(x, y: int64): int64 {.magic: "ModI", noSideEffect.} else: - proc `mod` *(x, y: int64): int64 {.magic: "ModI64", noSideEffect.} + proc `mod`*(x, y: int64): int64 {.magic: "ModI64", noSideEffect.} when defined(nimNewShiftOps): - proc `shr` *(x: int, y: SomeInteger): int {.magic: "ShrI", noSideEffect.} - proc `shr` *(x: int8, y: SomeInteger): int8 {.magic: "ShrI", noSideEffect.} - proc `shr` *(x: int16, y: SomeInteger): int16 {.magic: "ShrI", noSideEffect.} - proc `shr` *(x: int32, y: SomeInteger): int32 {.magic: "ShrI", noSideEffect.} - proc `shr` *(x: int64, y: SomeInteger): int64 {.magic: "ShrI", noSideEffect.} + proc `shr`*(x: int, y: SomeInteger): int {.magic: "ShrI", noSideEffect.} + proc `shr`*(x: int8, y: SomeInteger): int8 {.magic: "ShrI", noSideEffect.} + proc `shr`*(x: int16, y: SomeInteger): int16 {.magic: "ShrI", noSideEffect.} + proc `shr`*(x: int32, y: SomeInteger): int32 {.magic: "ShrI", noSideEffect.} + proc `shr`*(x: int64, y: SomeInteger): int64 {.magic: "ShrI", noSideEffect.} ## computes the `shift right` operation of `x` and `y`, filling ## vacant bit positions with zeros. ## @@ -965,121 +930,138 @@ when defined(nimNewShiftOps): ## 0b0000_0001'i8 shr 1 == 0b0000_0000'i8 - proc `shl` *(x: int, y: SomeInteger): int {.magic: "ShlI", noSideEffect.} - proc `shl` *(x: int8, y: SomeInteger): int8 {.magic: "ShlI", noSideEffect.} - proc `shl` *(x: int16, y: SomeInteger): int16 {.magic: "ShlI", noSideEffect.} - proc `shl` *(x: int32, y: SomeInteger): int32 {.magic: "ShlI", noSideEffect.} - proc `shl` *(x: int64, y: SomeInteger): int64 {.magic: "ShlI", noSideEffect.} + proc `shl`*(x: int, y: SomeInteger): int {.magic: "ShlI", noSideEffect.} + proc `shl`*(x: int8, y: SomeInteger): int8 {.magic: "ShlI", noSideEffect.} + proc `shl`*(x: int16, y: SomeInteger): int16 {.magic: "ShlI", noSideEffect.} + proc `shl`*(x: int32, y: SomeInteger): int32 {.magic: "ShlI", noSideEffect.} + proc `shl`*(x: int64, y: SomeInteger): int64 {.magic: "ShlI", noSideEffect.} ## computes the `shift left` operation of `x` and `y`. ## ## .. code-block:: Nim ## 1'i32 shl 4 == 0x0000_0010 ## 1'i64 shl 4 == 0x0000_0000_0000_0010 else: - proc `shr` *(x, y: int): int {.magic: "ShrI", noSideEffect.} - proc `shr` *(x, y: int8): int8 {.magic: "ShrI", noSideEffect.} - proc `shr` *(x, y: int16): int16 {.magic: "ShrI", noSideEffect.} - proc `shr` *(x, y: int32): int32 {.magic: "ShrI", noSideEffect.} - proc `shr` *(x, y: int64): int64 {.magic: "ShrI", noSideEffect.} - - proc `shl` *(x, y: int): int {.magic: "ShlI", noSideEffect.} - proc `shl` *(x, y: int8): int8 {.magic: "ShlI", noSideEffect.} - proc `shl` *(x, y: int16): int16 {.magic: "ShlI", noSideEffect.} - proc `shl` *(x, y: int32): int32 {.magic: "ShlI", noSideEffect.} - proc `shl` *(x, y: int64): int64 {.magic: "ShlI", noSideEffect.} - -proc `and` *(x, y: int): int {.magic: "BitandI", noSideEffect.} -proc `and` *(x, y: int8): int8 {.magic: "BitandI", noSideEffect.} -proc `and` *(x, y: int16): int16 {.magic: "BitandI", noSideEffect.} -proc `and` *(x, y: int32): int32 {.magic: "BitandI", noSideEffect.} -proc `and` *(x, y: int64): int64 {.magic: "BitandI", noSideEffect.} + proc `shr`*(x, y: int): int {.magic: "ShrI", noSideEffect.} + proc `shr`*(x, y: int8): int8 {.magic: "ShrI", noSideEffect.} + proc `shr`*(x, y: int16): int16 {.magic: "ShrI", noSideEffect.} + proc `shr`*(x, y: int32): int32 {.magic: "ShrI", noSideEffect.} + proc `shr`*(x, y: int64): int64 {.magic: "ShrI", noSideEffect.} + + proc `shl`*(x, y: int): int {.magic: "ShlI", noSideEffect.} + proc `shl`*(x, y: int8): int8 {.magic: "ShlI", noSideEffect.} + proc `shl`*(x, y: int16): int16 {.magic: "ShlI", noSideEffect.} + proc `shl`*(x, y: int32): int32 {.magic: "ShlI", noSideEffect.} + proc `shl`*(x, y: int64): int64 {.magic: "ShlI", noSideEffect.} + +when defined(nimAshr): + proc ashr*(x: int, y: SomeInteger): int {.magic: "AshrI", noSideEffect.} + proc ashr*(x: int8, y: SomeInteger): int8 {.magic: "AshrI", noSideEffect.} + proc ashr*(x: int16, y: SomeInteger): int16 {.magic: "AshrI", noSideEffect.} + proc ashr*(x: int32, y: SomeInteger): int32 {.magic: "AshrI", noSideEffect.} + proc ashr*(x: int64, y: SomeInteger): int64 {.magic: "AshrI", noSideEffect.} + ## Shifts right by pushing copies of the leftmost bit in from the left, + ## and let the rightmost bits fall off. + ## + ## .. code-block:: Nim + ## 0b0001_0000'i8 shr 2 == 0b0000_0100'i8 + ## 0b1000_0000'i8 shr 8 == 0b1111_1111'i8 + ## 0b1000_0000'i8 shr 1 == 0b1100_0000'i8 +else: + # used for bootstrapping the compiler + proc ashr*[T](x: T, y: SomeInteger): T = discard + +proc `and`*(x, y: int): int {.magic: "BitandI", noSideEffect.} +proc `and`*(x, y: int8): int8 {.magic: "BitandI", noSideEffect.} +proc `and`*(x, y: int16): int16 {.magic: "BitandI", noSideEffect.} +proc `and`*(x, y: int32): int32 {.magic: "BitandI", noSideEffect.} +proc `and`*(x, y: int64): int64 {.magic: "BitandI", noSideEffect.} ## computes the `bitwise and` of numbers `x` and `y`. ## ## .. code-block:: Nim ## (0xffff'i16 and 0x0010'i16) == 0x0010 -proc `or` *(x, y: int): int {.magic: "BitorI", noSideEffect.} -proc `or` *(x, y: int8): int8 {.magic: "BitorI", noSideEffect.} -proc `or` *(x, y: int16): int16 {.magic: "BitorI", noSideEffect.} -proc `or` *(x, y: int32): int32 {.magic: "BitorI", noSideEffect.} -proc `or` *(x, y: int64): int64 {.magic: "BitorI", noSideEffect.} +proc `or`*(x, y: int): int {.magic: "BitorI", noSideEffect.} +proc `or`*(x, y: int8): int8 {.magic: "BitorI", noSideEffect.} +proc `or`*(x, y: int16): int16 {.magic: "BitorI", noSideEffect.} +proc `or`*(x, y: int32): int32 {.magic: "BitorI", noSideEffect.} +proc `or`*(x, y: int64): int64 {.magic: "BitorI", noSideEffect.} ## computes the `bitwise or` of numbers `x` and `y`. ## ## .. code-block:: Nim ## (0x0005'i16 or 0x0010'i16) == 0x0015 -proc `xor` *(x, y: int): int {.magic: "BitxorI", noSideEffect.} -proc `xor` *(x, y: int8): int8 {.magic: "BitxorI", noSideEffect.} -proc `xor` *(x, y: int16): int16 {.magic: "BitxorI", noSideEffect.} -proc `xor` *(x, y: int32): int32 {.magic: "BitxorI", noSideEffect.} -proc `xor` *(x, y: int64): int64 {.magic: "BitxorI", noSideEffect.} +proc `xor`*(x, y: int): int {.magic: "BitxorI", noSideEffect.} +proc `xor`*(x, y: int8): int8 {.magic: "BitxorI", noSideEffect.} +proc `xor`*(x, y: int16): int16 {.magic: "BitxorI", noSideEffect.} +proc `xor`*(x, y: int32): int32 {.magic: "BitxorI", noSideEffect.} +proc `xor`*(x, y: int64): int64 {.magic: "BitxorI", noSideEffect.} ## computes the `bitwise xor` of numbers `x` and `y`. ## ## .. code-block:: Nim ## (0x1011'i16 xor 0x0101'i16) == 0x1110 -proc `==` *(x, y: int): bool {.magic: "EqI", noSideEffect.} -proc `==` *(x, y: int8): bool {.magic: "EqI", noSideEffect.} -proc `==` *(x, y: int16): bool {.magic: "EqI", noSideEffect.} -proc `==` *(x, y: int32): bool {.magic: "EqI", noSideEffect.} -proc `==` *(x, y: int64): bool {.magic: "EqI", noSideEffect.} +proc `==`*(x, y: int): bool {.magic: "EqI", noSideEffect.} +proc `==`*(x, y: int8): bool {.magic: "EqI", noSideEffect.} +proc `==`*(x, y: int16): bool {.magic: "EqI", noSideEffect.} +proc `==`*(x, y: int32): bool {.magic: "EqI", noSideEffect.} +proc `==`*(x, y: int64): bool {.magic: "EqI", noSideEffect.} ## Compares two integers for equality. -proc `<=` *(x, y: int): bool {.magic: "LeI", noSideEffect.} -proc `<=` *(x, y: int8): bool {.magic: "LeI", noSideEffect.} -proc `<=` *(x, y: int16): bool {.magic: "LeI", noSideEffect.} -proc `<=` *(x, y: int32): bool {.magic: "LeI", noSideEffect.} -proc `<=` *(x, y: int64): bool {.magic: "LeI", noSideEffect.} +proc `<=`*(x, y: int): bool {.magic: "LeI", noSideEffect.} +proc `<=`*(x, y: int8): bool {.magic: "LeI", noSideEffect.} +proc `<=`*(x, y: int16): bool {.magic: "LeI", noSideEffect.} +proc `<=`*(x, y: int32): bool {.magic: "LeI", noSideEffect.} +proc `<=`*(x, y: int64): bool {.magic: "LeI", noSideEffect.} ## Returns true iff `x` is less than or equal to `y`. -proc `<` *(x, y: int): bool {.magic: "LtI", noSideEffect.} -proc `<` *(x, y: int8): bool {.magic: "LtI", noSideEffect.} -proc `<` *(x, y: int16): bool {.magic: "LtI", noSideEffect.} -proc `<` *(x, y: int32): bool {.magic: "LtI", noSideEffect.} -proc `<` *(x, y: int64): bool {.magic: "LtI", noSideEffect.} +proc `<`*(x, y: int): bool {.magic: "LtI", noSideEffect.} +proc `<`*(x, y: int8): bool {.magic: "LtI", noSideEffect.} +proc `<`*(x, y: int16): bool {.magic: "LtI", noSideEffect.} +proc `<`*(x, y: int32): bool {.magic: "LtI", noSideEffect.} +proc `<`*(x, y: int64): bool {.magic: "LtI", noSideEffect.} ## Returns true iff `x` is less than `y`. type IntMax32 = int|int8|int16|int32 -proc `+%` *(x, y: IntMax32): IntMax32 {.magic: "AddU", noSideEffect.} -proc `+%` *(x, y: int64): int64 {.magic: "AddU", noSideEffect.} +proc `+%`*(x, y: IntMax32): IntMax32 {.magic: "AddU", noSideEffect.} +proc `+%`*(x, y: int64): int64 {.magic: "AddU", noSideEffect.} ## treats `x` and `y` as unsigned and adds them. The result is truncated to ## fit into the result. This implements modulo arithmetic. No overflow ## errors are possible. -proc `-%` *(x, y: IntMax32): IntMax32 {.magic: "SubU", noSideEffect.} -proc `-%` *(x, y: int64): int64 {.magic: "SubU", noSideEffect.} +proc `-%`*(x, y: IntMax32): IntMax32 {.magic: "SubU", noSideEffect.} +proc `-%`*(x, y: int64): int64 {.magic: "SubU", noSideEffect.} ## treats `x` and `y` as unsigned and subtracts them. The result is ## truncated to fit into the result. This implements modulo arithmetic. ## No overflow errors are possible. -proc `*%` *(x, y: IntMax32): IntMax32 {.magic: "MulU", noSideEffect.} -proc `*%` *(x, y: int64): int64 {.magic: "MulU", noSideEffect.} +proc `*%`*(x, y: IntMax32): IntMax32 {.magic: "MulU", noSideEffect.} +proc `*%`*(x, y: int64): int64 {.magic: "MulU", noSideEffect.} ## treats `x` and `y` as unsigned and multiplies them. The result is ## truncated to fit into the result. This implements modulo arithmetic. ## No overflow errors are possible. -proc `/%` *(x, y: IntMax32): IntMax32 {.magic: "DivU", noSideEffect.} -proc `/%` *(x, y: int64): int64 {.magic: "DivU", noSideEffect.} +proc `/%`*(x, y: IntMax32): IntMax32 {.magic: "DivU", noSideEffect.} +proc `/%`*(x, y: int64): int64 {.magic: "DivU", noSideEffect.} ## treats `x` and `y` as unsigned and divides them. The result is ## truncated to fit into the result. This implements modulo arithmetic. ## No overflow errors are possible. -proc `%%` *(x, y: IntMax32): IntMax32 {.magic: "ModU", noSideEffect.} -proc `%%` *(x, y: int64): int64 {.magic: "ModU", noSideEffect.} +proc `%%`*(x, y: IntMax32): IntMax32 {.magic: "ModU", noSideEffect.} +proc `%%`*(x, y: int64): int64 {.magic: "ModU", noSideEffect.} ## treats `x` and `y` as unsigned and compute the modulo of `x` and `y`. ## The result is truncated to fit into the result. ## This implements modulo arithmetic. ## No overflow errors are possible. -proc `<=%` *(x, y: IntMax32): bool {.magic: "LeU", noSideEffect.} -proc `<=%` *(x, y: int64): bool {.magic: "LeU64", noSideEffect.} +proc `<=%`*(x, y: IntMax32): bool {.magic: "LeU", noSideEffect.} +proc `<=%`*(x, y: int64): bool {.magic: "LeU64", noSideEffect.} ## treats `x` and `y` as unsigned and compares them. ## Returns true iff ``unsigned(x) <= unsigned(y)``. -proc `<%` *(x, y: IntMax32): bool {.magic: "LtU", noSideEffect.} -proc `<%` *(x, y: int64): bool {.magic: "LtU64", noSideEffect.} +proc `<%`*(x, y: IntMax32): bool {.magic: "LtU", noSideEffect.} +proc `<%`*(x, y: int64): bool {.magic: "LtU64", noSideEffect.} ## treats `x` and `y` as unsigned and compares them. ## Returns true iff ``unsigned(x) < unsigned(y)``. @@ -1143,35 +1125,35 @@ proc `<`*[T: SomeUnsignedInt](x, y: T): bool {.magic: "LtU", noSideEffect.} ## Returns true iff ``unsigned(x) < unsigned(y)``. # floating point operations: -proc `+` *(x: float32): float32 {.magic: "UnaryPlusF64", noSideEffect.} -proc `-` *(x: float32): float32 {.magic: "UnaryMinusF64", noSideEffect.} -proc `+` *(x, y: float32): float32 {.magic: "AddF64", noSideEffect.} -proc `-` *(x, y: float32): float32 {.magic: "SubF64", noSideEffect.} -proc `*` *(x, y: float32): float32 {.magic: "MulF64", noSideEffect.} -proc `/` *(x, y: float32): float32 {.magic: "DivF64", noSideEffect.} - -proc `+` *(x: float): float {.magic: "UnaryPlusF64", noSideEffect.} -proc `-` *(x: float): float {.magic: "UnaryMinusF64", noSideEffect.} -proc `+` *(x, y: float): float {.magic: "AddF64", noSideEffect.} -proc `-` *(x, y: float): float {.magic: "SubF64", noSideEffect.} -proc `*` *(x, y: float): float {.magic: "MulF64", noSideEffect.} -proc `/` *(x, y: float): float {.magic: "DivF64", noSideEffect.} +proc `+`*(x: float32): float32 {.magic: "UnaryPlusF64", noSideEffect.} +proc `-`*(x: float32): float32 {.magic: "UnaryMinusF64", noSideEffect.} +proc `+`*(x, y: float32): float32 {.magic: "AddF64", noSideEffect.} +proc `-`*(x, y: float32): float32 {.magic: "SubF64", noSideEffect.} +proc `*`*(x, y: float32): float32 {.magic: "MulF64", noSideEffect.} +proc `/`*(x, y: float32): float32 {.magic: "DivF64", noSideEffect.} + +proc `+`*(x: float): float {.magic: "UnaryPlusF64", noSideEffect.} +proc `-`*(x: float): float {.magic: "UnaryMinusF64", noSideEffect.} +proc `+`*(x, y: float): float {.magic: "AddF64", noSideEffect.} +proc `-`*(x, y: float): float {.magic: "SubF64", noSideEffect.} +proc `*`*(x, y: float): float {.magic: "MulF64", noSideEffect.} +proc `/`*(x, y: float): float {.magic: "DivF64", noSideEffect.} ## computes the floating point division -proc `==` *(x, y: float32): bool {.magic: "EqF64", noSideEffect.} -proc `<=` *(x, y: float32): bool {.magic: "LeF64", noSideEffect.} +proc `==`*(x, y: float32): bool {.magic: "EqF64", noSideEffect.} +proc `<=`*(x, y: float32): bool {.magic: "LeF64", noSideEffect.} proc `<` *(x, y: float32): bool {.magic: "LtF64", noSideEffect.} -proc `==` *(x, y: float): bool {.magic: "EqF64", noSideEffect.} -proc `<=` *(x, y: float): bool {.magic: "LeF64", noSideEffect.} -proc `<` *(x, y: float): bool {.magic: "LtF64", noSideEffect.} +proc `==`*(x, y: float): bool {.magic: "EqF64", noSideEffect.} +proc `<=`*(x, y: float): bool {.magic: "LeF64", noSideEffect.} +proc `<`*(x, y: float): bool {.magic: "LtF64", noSideEffect.} # set operators -proc `*` *[T](x, y: set[T]): set[T] {.magic: "MulSet", noSideEffect.} +proc `*`*[T](x, y: set[T]): set[T] {.magic: "MulSet", noSideEffect.} ## This operator computes the intersection of two sets. -proc `+` *[T](x, y: set[T]): set[T] {.magic: "PlusSet", noSideEffect.} +proc `+`*[T](x, y: set[T]): set[T] {.magic: "PlusSet", noSideEffect.} ## This operator computes the union of two sets. -proc `-` *[T](x, y: set[T]): set[T] {.magic: "MinusSet", noSideEffect.} +proc `-`*[T](x, y: set[T]): set[T] {.magic: "MinusSet", noSideEffect.} ## This operator computes the difference of two sets. proc contains*[T](x: set[T], y: T): bool {.magic: "InSet", noSideEffect.} @@ -1203,20 +1185,20 @@ proc contains*[U, V, W](s: HSlice[U, V], value: W): bool {.noSideEffect, inline. ## assert((1..3).contains(4) == false) result = s.a <= value and value <= s.b -template `in` * (x, y: untyped): untyped {.dirty.} = contains(y, x) +template `in`*(x, y: untyped): untyped {.dirty.} = contains(y, x) ## Sugar for contains ## ## .. code-block:: Nim ## assert(1 in (1..3) == true) ## assert(5 in (1..3) == false) -template `notin` * (x, y: untyped): untyped {.dirty.} = not contains(y, x) +template `notin`*(x, y: untyped): untyped {.dirty.} = not contains(y, x) ## Sugar for not containing ## ## .. code-block:: Nim ## assert(1 notin (1..3) == false) ## assert(5 notin (1..3) == true) -proc `is` *[T, S](x: T, y: S): bool {.magic: "Is", noSideEffect.} +proc `is`*[T, S](x: T, y: S): bool {.magic: "Is", noSideEffect.} ## Checks if T is of the same type as S ## ## .. code-block:: Nim @@ -1228,12 +1210,12 @@ proc `is` *[T, S](x: T, y: S): bool {.magic: "Is", noSideEffect.} ## ## assert(test[int](3) == 3) ## assert(test[string]("xyz") == 0) -template `isnot` *(x, y: untyped): untyped = not (x is y) +template `isnot`*(x, y: untyped): untyped = not (x is y) ## Negated version of `is`. Equivalent to ``not(x is y)``. -proc `of` *[T, S](x: typeDesc[T], y: typeDesc[S]): bool {.magic: "Of", noSideEffect.} -proc `of` *[T, S](x: T, y: typeDesc[S]): bool {.magic: "Of", noSideEffect.} -proc `of` *[T, S](x: T, y: S): bool {.magic: "Of", noSideEffect.} +proc `of`*[T, S](x: typeDesc[T], y: typeDesc[S]): bool {.magic: "Of", noSideEffect.} +proc `of`*[T, S](x: T, y: typeDesc[S]): bool {.magic: "Of", noSideEffect.} +proc `of`*[T, S](x: T, y: S): bool {.magic: "Of", noSideEffect.} ## Checks if `x` has a type of `y` ## ## .. code-block:: Nim @@ -1256,7 +1238,7 @@ proc cmp*[T](x, y: T): int {.procvar.} = proc cmp*(x, y: string): int {.noSideEffect, procvar.} ## Compare proc for strings. More efficient than the generic version. -proc `@` * [IDX, T](a: array[IDX, T]): seq[T] {. +proc `@`* [IDX, T](a: array[IDX, T]): seq[T] {. magic: "ArrToSeq", nosideeffect.} ## turns an array into a sequence. This most often useful for constructing ## sequences with the array constructor: ``@[1, 2, 3]`` has the type @@ -1267,15 +1249,13 @@ proc setLen*[T](s: var seq[T], newlen: Natural) {. ## sets the length of `s` to `newlen`. ## ``T`` may be any sequence type. ## If the current length is greater than the new length, - ## ``s`` will be truncated. `s` cannot be nil! To initialize a sequence with - ## a size, use ``newSeq`` instead. + ## ``s`` will be truncated. proc setLen*(s: var string, newlen: Natural) {. magic: "SetLengthStr", noSideEffect.} ## sets the length of `s` to `newlen`. ## If the current length is greater than the new length, - ## ``s`` will be truncated. `s` cannot be nil! To initialize a string with - ## a size, use ``newString`` instead. + ## ``s`` will be truncated. ## ## .. code-block:: Nim ## var myS = "Nim is great!!" @@ -1296,25 +1276,25 @@ proc newStringOfCap*(cap: Natural): string {. ## procedure exists only for optimization purposes; the same effect can ## be achieved with the ``&`` operator or with ``add``. -proc `&` * (x: string, y: char): string {. +proc `&`*(x: string, y: char): string {. magic: "ConStrStr", noSideEffect, merge.} ## Concatenates `x` with `y` ## ## .. code-block:: Nim ## assert("ab" & 'c' == "abc") -proc `&` * (x, y: char): string {. +proc `&`*(x, y: char): string {. magic: "ConStrStr", noSideEffect, merge.} ## Concatenates `x` and `y` into a string ## ## .. code-block:: Nim ## assert('a' & 'b' == "ab") -proc `&` * (x, y: string): string {. +proc `&`*(x, y: string): string {. magic: "ConStrStr", noSideEffect, merge.} ## Concatenates `x` and `y` ## ## .. code-block:: Nim ## assert("ab" & "cd" == "abcd") -proc `&` * (x: char, y: string): string {. +proc `&`*(x: char, y: string): string {. magic: "ConStrStr", noSideEffect, merge.} ## Concatenates `x` with `y` ## @@ -1367,12 +1347,13 @@ const hostOS* {.magic: "HostOS".}: string = "" ## a string that describes the host operating system. Possible values: ## "windows", "macosx", "linux", "netbsd", "freebsd", "openbsd", "solaris", - ## "aix", "standalone". + ## "aix", "haiku", "standalone". hostCPU* {.magic: "HostCPU".}: string = "" ## a string that describes the host CPU. Possible values: ## "i386", "alpha", "powerpc", "powerpc64", "powerpc64el", "sparc", - ## "amd64", "mips", "mipsel", "arm", "arm64", "mips64", "mips64el". + ## "amd64", "mips", "mipsel", "arm", "arm64", "mips64", "mips64el", + ## "riscv64". seqShallowFlag = low(int) strlitFlag = 1 shl (sizeof(int)*8 - 2) # later versions of the codegen \ @@ -1461,9 +1442,9 @@ when defined(nodejs): programResult = 0 else: var programResult* {.exportc: "nim_program_result".}: int - ## modify this variable to specify the exit code of the program - ## under normal circumstances. When the program is terminated - ## prematurely using ``quit``, this value is ignored. + ## modify this variable to specify the exit code of the program + ## under normal circumstances. When the program is terminated + ## prematurely using ``quit``, this value is ignored. when defined(nimdoc): proc quit*(errorcode: int = QuitSuccess) {.magic: "Exit", noreturn.} @@ -1483,14 +1464,24 @@ when defined(nimdoc): ## ## Note that this is a *runtime* call and using ``quit`` inside a macro won't ## have any compile time effect. If you need to stop the compiler inside a - ## macro, use the `error <manual.html#error-pragma>`_ or `fatal - ## <manual.html#fatal-pragma>`_ pragmas. - + ## macro, use the `error <manual.html#pragmas-error-pragma>`_ or `fatal + ## <manual.html#pragmas-fatal-pragma>`_ pragmas. elif defined(genode): - proc quit*(errorcode: int = QuitSuccess) {.magic: "Exit", noreturn, - importcpp: "genodeEnv->parent().exit(@); Genode::sleep_forever()", - header: "<base/sleep.h>".} + include genode/env + + var systemEnv {.exportc: runtimeEnvSym.}: GenodeEnvPtr + + type GenodeEnv* = GenodeEnvPtr + ## Opaque type representing Genode environment. + + proc quit*(env: GenodeEnv; errorcode: int) {.magic: "Exit", noreturn, + importcpp: "#->parent().exit(@); Genode::sleep_forever()", header: "<base/sleep.h>".} + + proc quit*(errorcode: int = QuitSuccess) = + systemEnv.quit(errorCode) + + elif defined(nodejs): proc quit*(errorcode: int = QuitSuccess) {.magic: "Exit", @@ -1510,12 +1501,17 @@ const hasAlloc = (hostOS != "standalone" or not defined(nogc)) and not defined(n when not defined(JS) and not defined(nimscript) and hostOS != "standalone": include "system/cgprocs" -when not defined(JS) and not defined(nimscript) and hasAlloc: - proc setStackBottom(theStackBottom: pointer) {.compilerRtl, noinline, benign.} +when not defined(JS) and not defined(nimscript) and hasAlloc and not defined(gcDestructors): proc addChar(s: NimString, c: char): NimString {.compilerProc, benign.} -proc add *[T](x: var seq[T], y: T) {.magic: "AppendSeqElem", noSideEffect.} -proc add *[T](x: var seq[T], y: openArray[T]) {.noSideEffect.} = +when defined(gcDestructors): + proc add*[T](x: var seq[T], y: sink T) {.magic: "AppendSeqElem", noSideEffect.} = + let xl = x.len + setLen(x, xl + 1) + x[xl] = y +else: + proc add*[T](x: var seq[T], y: T) {.magic: "AppendSeqElem", noSideEffect.} +proc add*[T](x: var seq[T], y: openArray[T]) {.noSideEffect.} = ## Generic proc for adding a data item `y` to a container `x`. ## For containers that have an order, `add` means *append*. New generic ## containers should also call their adding proc `add` for consistency. @@ -1556,7 +1552,7 @@ proc delete*[T](x: var seq[T], i: Natural) {.noSideEffect.} = defaultImpl() else: when defined(js): - {.emit: "`x`[`x`_Idx].splice(`i`, 1);".} + {.emit: "`x`.splice(`i`, 1);".} else: defaultImpl() @@ -1578,7 +1574,7 @@ proc insert*[T](x: var seq[T], item: T, i = 0.Natural) {.noSideEffect.} = else: when defined(js): var it : T - {.emit: "`x`[`x`_Idx].splice(`i`, 0, `it`);".} + {.emit: "`x`.splice(`i`, 0, `it`);".} else: defaultImpl() x[i] = item @@ -1620,8 +1616,6 @@ else: ## supports. Currently this is ``uint32`` for JS and ``uint64`` for other ## targets. -{.deprecated: [TAddress: ByteAddress].} - when defined(windows): type clong* {.importc: "long", nodecl.} = int32 @@ -1713,28 +1707,6 @@ proc addQuitProc*(QuitProc: proc() {.noconv.}) {. # In case of an unhandled exeption the exit handlers should # not be called explicitly! The user may decide to do this manually though. -proc copy*(s: string, first = 0): string {. - magic: "CopyStr", importc: "copyStr", noSideEffect, deprecated.} -proc copy*(s: string, first, last: int): string {. - magic: "CopyStrLast", importc: "copyStrLast", noSideEffect, - deprecated.} - ## copies a slice of `s` into a new string and returns this new - ## string. The bounds `first` and `last` denote the indices of - ## the first and last characters that shall be copied. If ``last`` - ## is omitted, it is treated as ``high(s)``. - ## **Deprecated since version 0.8.12**: Use ``substr`` instead. - -proc substr*(s: string, first = 0): string {. - magic: "CopyStr", importc: "copyStr", noSideEffect.} -proc substr*(s: string, first, last: int): string {. - magic: "CopyStrLast", importc: "copyStrLast", noSideEffect.} - ## copies a slice of `s` into a new string and returns this new - ## string. The bounds `first` and `last` denote the indices of - ## the first and last characters that shall be copied. If ``last`` - ## is omitted, it is treated as ``high(s)``. If ``last >= s.len``, ``s.len`` - ## is used instead: This means ``substr`` can also be used to `cut`:idx: - ## or `limit`:idx: a string's length. - when not defined(nimscript) and not defined(JS): proc zeroMem*(p: pointer, size: Natural) {.inline, benign.} ## overwrites the contents of the memory at ``p`` with the value 0. @@ -1774,7 +1746,7 @@ when not defined(nimscript): proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline, benign.} = ## allocates a new memory block with at least ``T.sizeof * size`` ## bytes. The block has to be freed with ``resize(block, 0)`` or - ## ``free(block)``. The block is not initialized, so reading + ## ``dealloc(block)``. The block is not initialized, so reading ## from it before writing to it is undefined behaviour! ## The allocated memory belongs to its allocating thread! ## Use `createSharedU` to allocate from a shared heap. @@ -1789,7 +1761,7 @@ when not defined(nimscript): proc create*(T: typedesc, size = 1.Positive): ptr T {.inline, benign.} = ## allocates a new memory block with at least ``T.sizeof * size`` ## bytes. The block has to be freed with ``resize(block, 0)`` or - ## ``free(block)``. The block is initialized with all bytes + ## ``dealloc(block)``. The block is initialized with all bytes ## containing zero, so it is somewhat safer than ``createU``. ## The allocated memory belongs to its allocating thread! ## Use `createShared` to allocate from a shared heap. @@ -1807,7 +1779,7 @@ when not defined(nimscript): ## grows or shrinks a given memory block. If p is **nil** then a new ## memory block is returned. In either way the block has at least ## ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is not - ## **nil** ``resize`` calls ``free(p)``. In other cases the block + ## **nil** ``resize`` calls ``dealloc(p)``. In other cases the block ## has to be freed with ``free``. The allocated memory belongs to ## its allocating thread! ## Use `resizeShared` to reallocate from a shared heap. @@ -1887,11 +1859,11 @@ when not defined(js) and not defined(booting) and defined(nimTrMacros): # unnecessary slow down in this case. swap(cast[ptr pointer](addr arr[a])[], cast[ptr pointer](addr arr[b])[]) -template `>=%` *(x, y: untyped): untyped = y <=% x +template `>=%`*(x, y: untyped): untyped = y <=% x ## treats `x` and `y` as unsigned and compares them. ## Returns true iff ``unsigned(x) >= unsigned(y)``. -template `>%` *(x, y: untyped): untyped = y <% x +template `>%`*(x, y: untyped): untyped = y <% x ## treats `x` and `y` as unsigned and compares them. ## Returns true iff ``unsigned(x) > unsigned(y)``. @@ -1906,32 +1878,32 @@ proc `$`*(x: int64): string {.magic: "Int64ToStr", noSideEffect.} when not defined(nimscript): when not defined(JS) and hasAlloc: - proc `$` *(x: uint64): string {.noSideEffect.} + proc `$`*(x: uint64): string {.noSideEffect.} ## The stringify operator for an unsigned integer argument. Returns `x` ## converted to a decimal string. -proc `$` *(x: float): string {.magic: "FloatToStr", noSideEffect.} +proc `$`*(x: float): string {.magic: "FloatToStr", noSideEffect.} ## The stringify operator for a float argument. Returns `x` ## converted to a decimal string. -proc `$` *(x: bool): string {.magic: "BoolToStr", noSideEffect.} +proc `$`*(x: bool): string {.magic: "BoolToStr", noSideEffect.} ## The stringify operator for a boolean argument. Returns `x` ## converted to the string "false" or "true". -proc `$` *(x: char): string {.magic: "CharToStr", noSideEffect.} +proc `$`*(x: char): string {.magic: "CharToStr", noSideEffect.} ## The stringify operator for a character argument. Returns `x` ## converted to a string. -proc `$` *(x: cstring): string {.magic: "CStrToStr", noSideEffect.} +proc `$`*(x: cstring): string {.magic: "CStrToStr", noSideEffect.} ## The stringify operator for a CString argument. Returns `x` ## converted to a string. -proc `$` *(x: string): string {.magic: "StrToStr", noSideEffect.} +proc `$`*(x: string): string {.magic: "StrToStr", noSideEffect.} ## The stringify operator for a string argument. Returns `x` ## as it is. This operator is useful for generic code, so ## that ``$expr`` also works if ``expr`` is already a string. -proc `$` *[Enum: enum](x: Enum): string {.magic: "EnumToStr", noSideEffect.} +proc `$`*[Enum: enum](x: Enum): string {.magic: "EnumToStr", noSideEffect.} ## The stringify operator for an enumeration argument. This works for ## any enumeration type thanks to compiler magic. If ## a ``$`` operator for a concrete enumeration is provided, this is @@ -1958,10 +1930,10 @@ const NimMajor* {.intdefine.}: int = 0 ## is the major number of Nim's version. - NimMinor* {.intdefine.}: int = 17 + NimMinor* {.intdefine.}: int = 18 ## is the minor number of Nim's version. - NimPatch* {.intdefine.}: int = 3 + NimPatch* {.intdefine.}: int = 1 ## is the patch number of Nim's version. NimVersion*: string = $NimMajor & "." & $NimMinor & "." & $NimPatch @@ -2000,7 +1972,7 @@ when sizeof(int) <= 2: else: type IntLikeForCount = int|int8|int16|int32|char|bool|uint8|uint16|enum -iterator countdown*[T](a, b: T, step = 1): T {.inline.} = +iterator countdown*[T](a, b: T, step: Positive = 1): T {.inline.} = ## Counts from ordinal value `a` down to `b` (inclusive) with the given ## step count. `T` may be any ordinal type, `step` may only ## be positive. **Note**: This fails to count to ``low(int)`` if T = int for @@ -2023,7 +1995,7 @@ iterator countdown*[T](a, b: T, step = 1): T {.inline.} = dec(res, step) when defined(nimNewRoof): - iterator countup*[T](a, b: T, step = 1): T {.inline.} = + iterator countup*[T](a, b: T, step: Positive = 1): T {.inline.} = ## Counts from ordinal value `a` up to `b` (inclusive) with the given ## step count. `S`, `T` may be any ordinal type, `step` may only ## be positive. **Note**: This fails to count to ``high(int)`` if T = int for @@ -2040,7 +2012,7 @@ when defined(nimNewRoof): inc(res, step) iterator `..`*[T](a, b: T): T {.inline.} = - ## An alias for `countup`. + ## An alias for `countup(a, b, 1)`. when T is IntLikeForCount: var res = int(a) while res <= int(b): @@ -2159,8 +2131,8 @@ proc max*[T](x, y: T): T = if y <= x: x else: y {.pop.} -proc high*(T: typedesc[SomeReal]): T = Inf -proc low*(T: typedesc[SomeReal]): T = NegInf +proc high*(T: typedesc[SomeFloat]): T = Inf +proc low*(T: typedesc[SomeFloat]): T = NegInf proc clamp*[T](x, a, b: T): T = ## limits the value ``x`` within the interval [a, b] @@ -2223,10 +2195,17 @@ iterator items*[T](a: set[T]): T {.inline.} = iterator items*(a: cstring): char {.inline.} = ## iterates over each item of `a`. - var i = 0 - while a[i] != '\0': - yield a[i] - inc(i) + when defined(js): + var i = 0 + var L = len(a) + while i < L: + yield a[i] + inc(i) + else: + var i = 0 + while a[i] != '\0': + yield a[i] + inc(i) iterator mitems*(a: var cstring): var char {.inline.} = ## iterates over each item of `a` so that you can modify the yielded value. @@ -2326,9 +2305,18 @@ iterator mpairs*(a: var cstring): tuple[key: int, val: var char] {.inline.} = inc(i) -proc isNil*[T](x: seq[T]): bool {.noSideEffect, magic: "IsNil".} +when defined(nimNoNilSeqs2): + when not compileOption("nilseqs"): + {.pragma: nilError, error.} + else: + {.pragma: nilError.} +else: + {.pragma: nilError.} + +proc isNil*[T](x: seq[T]): bool {.noSideEffect, magic: "IsNil", nilError.} proc isNil*[T](x: ref T): bool {.noSideEffect, magic: "IsNil".} -proc isNil*(x: string): bool {.noSideEffect, magic: "IsNil".} +proc isNil*(x: string): bool {.noSideEffect, magic: "IsNil", nilError.} + proc isNil*[T](x: ptr T): bool {.noSideEffect, magic: "IsNil".} proc isNil*(x: pointer): bool {.noSideEffect, magic: "IsNil".} proc isNil*(x: cstring): bool {.noSideEffect, magic: "IsNil".} @@ -2336,7 +2324,7 @@ proc isNil*[T: proc](x: T): bool {.noSideEffect, magic: "IsNil".} ## Fast check whether `x` is nil. This is sometimes more efficient than ## ``== nil``. -proc `==` *[I, T](x, y: array[I, T]): bool = +proc `==`*[I, T](x, y: array[I, T]): bool = for f in low(x)..high(x): if x[f] != y[f]: return @@ -2349,7 +2337,7 @@ proc `@`*[T](a: openArray[T]): seq[T] = newSeq(result, a.len) for i in 0..a.len-1: result[i] = a[i] -proc `&` *[T](x, y: seq[T]): seq[T] {.noSideEffect.} = +proc `&`*[T](x, y: seq[T]): seq[T] {.noSideEffect.} = ## Concatenates two sequences. ## Requires copying of the sequences. ## @@ -2361,7 +2349,7 @@ proc `&` *[T](x, y: seq[T]): seq[T] {.noSideEffect.} = for i in 0..y.len-1: result[i+x.len] = y[i] -proc `&` *[T](x: seq[T], y: T): seq[T] {.noSideEffect.} = +proc `&`*[T](x: seq[T], y: T): seq[T] {.noSideEffect.} = ## Appends element y to the end of the sequence. ## Requires copying of the sequence ## @@ -2372,7 +2360,7 @@ proc `&` *[T](x: seq[T], y: T): seq[T] {.noSideEffect.} = result[i] = x[i] result[x.len] = y -proc `&` *[T](x: T, y: seq[T]): seq[T] {.noSideEffect.} = +proc `&`*[T](x: T, y: seq[T]): seq[T] {.noSideEffect.} = ## Prepends the element x to the beginning of the sequence. ## Requires copying of the sequence ## @@ -2383,14 +2371,18 @@ proc `&` *[T](x: T, y: seq[T]): seq[T] {.noSideEffect.} = for i in 0..y.len-1: result[i+1] = y[i] -proc `==` *[T](x, y: seq[T]): bool {.noSideEffect.} = +proc `==`*[T](x, y: seq[T]): bool {.noSideEffect.} = ## Generic equals operator for sequences: relies on a equals operator for ## the element type `T`. when nimvm: - if x.isNil and y.isNil: - return true + when not defined(nimNoNil): + if x.isNil and y.isNil: + return true + else: + if x.len == 0 and y.len == 0: + return true else: - when not defined(JS) or defined(nimphp): + when not defined(JS): proc seqToPtr[T](x: seq[T]): pointer {.inline, nosideeffect.} = result = cast[pointer](x) else: @@ -2400,8 +2392,9 @@ proc `==` *[T](x, y: seq[T]): bool {.noSideEffect.} = if seqToPtr(x) == seqToPtr(y): return true - if x.isNil or y.isNil: - return false + when not defined(nimNoNil): + if x.isNil or y.isNil: + return false if x.len != y.len: return false @@ -2450,7 +2443,7 @@ iterator fieldPairs*[T: tuple|object](x: T): RootObj {. ## When you iterate over objects with different field types you have to use ## the compile time ``when`` instead of a runtime ``if`` to select the code ## you want to run for each type. To perform the comparison use the `is - ## operator <manual.html#is-operator>`_. Example: + ## operator <manual.html#generics-is-operator>`_. Example: ## ## .. code-block:: Nim ## @@ -2524,7 +2517,7 @@ proc `$`*[T: tuple|object](x: T): string = result.add(name) result.add(": ") when compiles($value): - when compiles(value.isNil): + when value isnot string and value isnot seq and compiles(value.isNil): if value.isNil: result.add "nil" else: result.addQuoted(value) else: @@ -2543,7 +2536,7 @@ proc collectionToString[T](x: T, prefix, separator, suffix: string): string = else: result.add(separator) - when compiles(value.isNil): + when value isnot string and value isnot seq and compiles(value.isNil): # this branch should not be necessary if value.isNil: result.add "nil" @@ -2568,10 +2561,7 @@ proc `$`*[T](x: seq[T]): string = ## ## .. code-block:: nim ## $(@[23, 45]) == "@[23, 45]" - if x.isNil: - "nil" - else: - collectionToString(x, "@[", ", ", "]") + collectionToString(x, "@[", ", ", "]") # ----------------- GC interface --------------------------------------------- @@ -2583,8 +2573,6 @@ when not defined(nimscript) and hasAlloc: gcOptimizeTime, ## optimize for speed gcOptimizeSpace ## optimize for memory footprint - {.deprecated: [TGC_Strategy: GC_Strategy].} - when not defined(JS): proc GC_disable*() {.rtl, inl, benign.} ## disables the GC. If called n-times, n calls to `GC_enable` are needed to @@ -2598,10 +2586,6 @@ when not defined(nimscript) and hasAlloc: ## forces a full garbage collection pass. ## Ordinary code does not need to call this (and should not). - proc GC_setStrategy*(strategy: GC_Strategy) {.rtl, deprecated, benign.} - ## tells the GC the desired strategy for the application. - ## **Deprecated** since version 0.8.14. This has always been a nop. - proc GC_enableMarkAndSweep*() {.rtl, benign.} proc GC_disableMarkAndSweep*() {.rtl, benign.} ## the current implementation uses a reference counting garbage collector @@ -2626,6 +2610,11 @@ when not defined(nimscript) and hasAlloc: proc GC_unref*(x: string) {.magic: "GCunref", benign.} ## see the documentation of `GC_ref`. + when not defined(JS) and not defined(nimscript) and hasAlloc: + proc nimGC_setStackBottom*(theStackBottom: pointer) {.compilerRtl, noinline, benign.} + ## Expands operating GC stack range to `theStackBottom`. Does nothing + ## if current stack bottom is already lower than `theStackBottom`. + else: template GC_disable* = {.warning: "GC_disable is a no-op in JavaScript".} @@ -2664,7 +2653,7 @@ when not defined(nimscript) and hasAlloc: {.warning: "GC_unref is a no-op in JavaScript".} template GC_getStatistics*(): string = - {.warning: "GC_disableMarkAndSweep is a no-op in JavaScript".} + {.warning: "GC_getStatistics is a no-op in JavaScript".} "" template accumulateResult*(iter: untyped) = @@ -2748,21 +2737,16 @@ type filename*: cstring ## filename of the proc that is currently executing len*: int16 ## length of the inspectable slots calldepth*: int16 ## used for max call depth checking -#{.deprecated: [TFrame: Frame].} when defined(JS): proc add*(x: var string, y: cstring) {.asmNoStackFrame.} = - when defined(nimphp): - asm """`x` .= `y`;""" - else: - asm """ - var len = `x`[0].length-1; - for (var i = 0; i < `y`.length; ++i) { - `x`[0][len] = `y`.charCodeAt(i); - ++len; - } - `x`[0][len] = 0 - """ + asm """ + var len = `x`.length; + for (var i = 0; i < `y`.length; ++i) { + `x`[len] = `y`.charCodeAt(i); + ++len; + } + """ proc add*(x: var cstring, y: cstring) {.magic: "AppendStrStr".} elif hasAlloc: @@ -2866,6 +2850,58 @@ else: if x < 0: -x else: x {.pop.} +when not defined(JS): + proc likely_proc(val: bool): bool {.importc: "likely", nodecl, nosideeffect.} + proc unlikely_proc(val: bool): bool {.importc: "unlikely", nodecl, nosideeffect.} + +template likely*(val: bool): bool = + ## Hints the optimizer that `val` is likely going to be true. + ## + ## You can use this template to decorate a branch condition. On certain + ## platforms this can help the processor predict better which branch is + ## going to be run. Example: + ## + ## .. code-block:: nim + ## for value in inputValues: + ## if likely(value <= 100): + ## process(value) + ## else: + ## echo "Value too big!" + ## + ## On backends without branch prediction (JS and the nimscript VM), this + ## template will not affect code execution. + when nimvm: + val + else: + when defined(JS): + val + else: + likely_proc(val) + +template unlikely*(val: bool): bool = + ## Hints the optimizer that `val` is likely going to be false. + ## + ## You can use this proc to decorate a branch condition. On certain + ## platforms this can help the processor predict better which branch is + ## going to be run. Example: + ## + ## .. code-block:: nim + ## for value in inputValues: + ## if unlikely(value > 100): + ## echo "Value too big!" + ## else: + ## process(value) + ## + ## On backends without branch prediction (JS and the nimscript VM), this + ## template will not affect code execution. + when nimvm: + val + else: + when defined(JS): + val + else: + unlikely_proc(val) + type FileSeekPos* = enum ## Position relative to which seek should happen # The values are ordered so that they match with stdio @@ -2888,21 +2924,22 @@ when not defined(JS): #and not defined(nimscript): # WARNING: This is very fragile! An array size of 8 does not work on my # Linux 64bit system. -- That's because the stack direction is the other # way round. - when declared(setStackBottom): + when declared(nimGC_setStackBottom): var locals {.volatile.}: pointer locals = addr(locals) - setStackBottom(locals) + nimGC_setStackBottom(locals) proc initStackBottomWith(locals: pointer) {.inline, compilerproc.} = # We need to keep initStackBottom around for now to avoid # bootstrapping problems. - when declared(setStackBottom): - setStackBottom(locals) + when declared(nimGC_setStackBottom): + nimGC_setStackBottom(locals) - {.push profiler: off.} - var - strDesc = TNimType(size: sizeof(string), kind: tyString, flags: {ntfAcyclic}) - {.pop.} + when not defined(gcDestructors): + {.push profiler: off.} + var + strDesc = TNimType(size: sizeof(string), kind: tyString, flags: {ntfAcyclic}) + {.pop.} # ----------------- IO Part ------------------------------------------------ @@ -2928,9 +2965,23 @@ when not defined(JS): #and not defined(nimscript): FileHandle* = cint ## type that represents an OS file handle; this is ## useful for low-level file access - {.deprecated: [TFile: File, TFileHandle: FileHandle, TFileMode: FileMode].} - include "system/ansi_c" + include "system/memory" + + proc zeroMem(p: pointer, size: Natural) = + nimZeroMem(p, size) + when declared(memTrackerOp): + memTrackerOp("zeroMem", p, size) + proc copyMem(dest, source: pointer, size: Natural) = + nimCopyMem(dest, source, size) + when declared(memTrackerOp): + memTrackerOp("copyMem", dest, size) + proc moveMem(dest, source: pointer, size: Natural) = + c_memmove(dest, source, size) + when declared(memTrackerOp): + memTrackerOp("moveMem", dest, size) + proc equalMem(a, b: pointer, size: Natural): bool = + nimCmpMem(a, b, size) == 0 proc cmp(x, y: string): int = when nimvm: @@ -2939,7 +2990,7 @@ when not defined(JS): #and not defined(nimscript): else: result = 0 else: let minlen = min(x.len, y.len) - result = int(c_memcmp(x.cstring, y.cstring, minlen.csize)) + result = int(nimCmpMem(x.cstring, y.cstring, minlen.csize)) if result == 0: result = x.len - y.len @@ -3082,10 +3133,6 @@ when not defined(JS): #and not defined(nimscript): ## if the end of the file has been reached, ``true`` otherwise. If ## ``false`` is returned `line` contains no new data. - proc writeLn*[Ty](f: File, x: varargs[Ty, `$`]) {.inline, - tags: [WriteIOEffect], benign, deprecated.} - ## **Deprecated since version 0.11.4:** Use **writeLine** instead. - proc writeLine*[Ty](f: File, x: varargs[Ty, `$`]) {.inline, tags: [WriteIOEffect], benign.} ## writes the values `x` to `f` and then writes "\\n". @@ -3200,7 +3247,6 @@ when not defined(JS): #and not defined(nimscript): hasRaiseAction: bool raiseAction: proc (e: ref Exception): bool {.closure.} SafePoint = TSafePoint - # {.deprecated: [TSafePoint: SafePoint].} when declared(initAllocator): initAllocator() @@ -3213,7 +3259,7 @@ when not defined(JS): #and not defined(nimscript): when declared(initGC): initGC() when not defined(nimscript): - proc setControlCHook*(hook: proc () {.noconv.} not nil) + proc setControlCHook*(hook: proc () {.noconv.}) ## allows you to override the behaviour of your application when CTRL+C ## is pressed. Only one such hook is supported. @@ -3233,22 +3279,6 @@ when not defined(JS): #and not defined(nimscript): when defined(memtracker): include "system/memtracker" - when not defined(nimscript): - proc zeroMem(p: pointer, size: Natural) = - c_memset(p, 0, size) - when declared(memTrackerOp): - memTrackerOp("zeroMem", p, size) - proc copyMem(dest, source: pointer, size: Natural) = - c_memcpy(dest, source, size) - when declared(memTrackerOp): - memTrackerOp("copyMem", dest, size) - proc moveMem(dest, source: pointer, size: Natural) = - c_memmove(dest, source, size) - when declared(memTrackerOp): - memTrackerOp("moveMem", dest, size) - proc equalMem(a, b: pointer, size: Natural): bool = - c_memcmp(a, b, size) == 0 - when hostOS == "standalone": include "system/embedded" else: @@ -3295,8 +3325,14 @@ when not defined(JS): #and not defined(nimscript): when hasAlloc: include "system/mmdisp" {.pop.} {.push stack_trace: off, profiler:off.} - when hasAlloc: include "system/sysstr" + when hasAlloc: + when defined(gcDestructors): + include "core/strs" + include "core/seqs" + else: + include "system/sysstr" {.pop.} + when hasAlloc: include "system/strmantle" when hostOS != "standalone": include "system/sysio" when hasThreadSupport: @@ -3341,8 +3377,9 @@ when not defined(JS): #and not defined(nimscript): while f.readLine(res): yield res when not defined(nimscript) and hasAlloc: - include "system/assign" - include "system/repr" + when not defined(gcDestructors): + include "system/assign" + include "system/repr" when hostOS != "standalone" and not defined(nimscript): proc getCurrentException*(): ref Exception {.compilerRtl, inl, benign.} = @@ -3355,12 +3392,15 @@ when not defined(JS): #and not defined(nimscript): var e = getCurrentException() return if e == nil: "" else: e.msg - proc onRaise*(action: proc(e: ref Exception): bool{.closure.}) = + proc onRaise*(action: proc(e: ref Exception): bool{.closure.}) {.deprecated.} = ## can be used in a ``try`` statement to setup a Lisp-like ## `condition system`:idx:\: This prevents the 'raise' statement to ## raise an exception but instead calls ``action``. ## If ``action`` returns false, the exception has been handled and ## does not propagate further through the call stack. + ## + ## *Deprecated since version 0.18.1*: No good usages of this + ## feature are known. if not isNil(excHandler): excHandler.hasRaiseAction = true excHandler.raiseAction = action @@ -3428,9 +3468,17 @@ elif defined(JS): when defined(nimffi): include "system/sysio" +when not defined(nimNoArrayToString): + proc `$`*[T, IDX](x: array[IDX, T]): string = + ## generic ``$`` operator for arrays that is lifted from the components + collectionToString(x, "[", ", ", "]") -proc `$`*[T, IDX](x: array[IDX, T]): string = - ## generic ``$`` operator for arrays that is lifted from the components +proc `$`*[T](x: openarray[T]): string = + ## generic ``$`` operator for openarrays that is lifted from the components + ## of `x`. Example: + ## + ## .. code-block:: nim + ## $(@[23, 45].toOpenArray(0, 1)) == "[23, 45]" collectionToString(x, "[", ", ", "]") proc quit*(errormsg: string, errorcode = QuitFailure) {.noReturn.} = @@ -3441,58 +3489,6 @@ proc quit*(errormsg: string, errorcode = QuitFailure) {.noReturn.} = {.pop.} # checks {.pop.} # hints -when not defined(JS): - proc likely_proc(val: bool): bool {.importc: "likely", nodecl, nosideeffect.} - proc unlikely_proc(val: bool): bool {.importc: "unlikely", nodecl, nosideeffect.} - -template likely*(val: bool): bool = - ## Hints the optimizer that `val` is likely going to be true. - ## - ## You can use this template to decorate a branch condition. On certain - ## platforms this can help the processor predict better which branch is - ## going to be run. Example: - ## - ## .. code-block:: nim - ## for value in inputValues: - ## if likely(value <= 100): - ## process(value) - ## else: - ## echo "Value too big!" - ## - ## On backends without branch prediction (JS and the nimscript VM), this - ## template will not affect code execution. - when nimvm: - val - else: - when defined(JS): - val - else: - likely_proc(val) - -template unlikely*(val: bool): bool = - ## Hints the optimizer that `val` is likely going to be false. - ## - ## You can use this proc to decorate a branch condition. On certain - ## platforms this can help the processor predict better which branch is - ## going to be run. Example: - ## - ## .. code-block:: nim - ## for value in inputValues: - ## if unlikely(value > 100): - ## echo "Value too big!" - ## else: - ## process(value) - ## - ## On backends without branch prediction (JS and the nimscript VM), this - ## template will not affect code execution. - when nimvm: - val - else: - when defined(JS): - val - else: - unlikely_proc(val) - proc `/`*(x, y: int): float {.inline, noSideEffect.} = ## integer division that results in a float. result = toFloat(x) / toFloat(y) @@ -3545,6 +3541,9 @@ template spliceImpl(s, a, L, b: untyped): untyped = template `^^`(s, i: untyped): untyped = (when i is BackwardsIndex: s.len - int(i) else: int(i)) +template `[]`*(s: string; i: int): char = arrGet(s, i) +template `[]=`*(s: string; i: int; val: char) = arrPut(s, i, val) + when hasAlloc or defined(nimscript): proc `[]`*[T, U](s: string, x: HSlice[T, U]): string {.inline.} = ## slice operation for strings. @@ -3717,7 +3716,7 @@ proc `/=`*[T: float|float32](x: var T, y: T) {.inline, noSideEffect.} = ## Divides in place a floating point number x = x / y -proc `&=`* (x: var string, y: string) {.magic: "AppendStrStr", noSideEffect.} +proc `&=`*(x: var string, y: string) {.magic: "AppendStrStr", noSideEffect.} template `&=`*(x, y: typed) = ## generic 'sink' operator for Nim. For files an alias for ``write``. ## If not specialized further an alias for ``add``. @@ -3730,7 +3729,7 @@ proc astToStr*[T](x: T): string {.magic: "AstToStr", noSideEffect.} ## for debugging. proc instantiationInfo*(index = -1, fullPaths = false): tuple[ - filename: string, line: int] {. magic: "InstantiationInfo", noSideEffect.} + filename: string, line: int, column: int] {.magic: "InstantiationInfo", noSideEffect.} ## provides access to the compiler's instantiation stack line information ## of a template. ## @@ -3777,6 +3776,16 @@ proc failedAssertImpl*(msg: string) {.raises: [], tags: [].} = tags: [].} Hide(raiseAssert)(msg) +include "system/helpers" # for `lineInfoToString` + +template assertImpl(cond: bool, msg = "", enabled: static[bool]) = + const loc = $instantiationInfo(-1, true) + bind instantiationInfo + mixin failedAssertImpl + when enabled: + if not cond: + failedAssertImpl(loc & " `" & astToStr(cond) & "` " & msg) + template assert*(cond: bool, msg = "") = ## Raises ``AssertionError`` with `msg` if `cond` is false. Note ## that ``AssertionError`` is hidden from the effect system, so it doesn't @@ -3786,19 +3795,11 @@ template assert*(cond: bool, msg = "") = ## The compiler may not generate any code at all for ``assert`` if it is ## advised to do so through the ``-d:release`` or ``--assertions:off`` ## `command line switches <nimc.html#command-line-switches>`_. - bind instantiationInfo - mixin failedAssertImpl - when compileOption("assertions"): - {.line.}: - if not cond: failedAssertImpl(astToStr(cond) & ' ' & msg) + assertImpl(cond, msg, compileOption("assertions")) template doAssert*(cond: bool, msg = "") = - ## same as `assert` but is always turned on and not affected by the - ## ``--assertions`` command line switch. - bind instantiationInfo - {.line: instantiationInfo().}: - if not cond: - raiseAssert(astToStr(cond) & ' ' & msg) + ## same as ``assert`` but is always turned on regardless of ``--assertions`` + assertImpl(cond, msg, true) iterator items*[T](a: seq[T]): T {.inline.} = ## iterates over each item of `a`. @@ -3868,7 +3869,7 @@ proc shallow*(s: var string) {.noSideEffect, inline.} = ## marks a string `s` as `shallow`:idx:. Subsequent assignments will not ## perform deep copies of `s`. This is only useful for optimization ## purposes. - when not defined(JS) and not defined(nimscript): + when not defined(JS) and not defined(nimscript) and not defined(gcDestructors): var s = cast[PGenericSeq](s) # string literals cannot become 'shallow': if (s.reserved and strlitFlag) == 0: @@ -3879,6 +3880,7 @@ type NimNode* {.magic: "PNimrodNode".} = ref NimNodeObj ## represents a Nim AST node. Macros operate on this type. + {.deprecated: [PNimrodNode: NimNode].} when false: @@ -3922,29 +3924,48 @@ proc addEscapedChar*(s: var string, c: char) {.noSideEffect, inline.} = ## * replaces any ``\`` by ``\\`` ## * replaces any ``'`` by ``\'`` ## * replaces any ``"`` by ``\"`` - ## * replaces any other character in the set ``{'\0'..'\31', '\127'..'\255'}`` + ## * replaces any ``\a`` by ``\\a`` + ## * replaces any ``\b`` by ``\\b`` + ## * replaces any ``\t`` by ``\\t`` + ## * replaces any ``\n`` by ``\\n`` + ## * replaces any ``\v`` by ``\\v`` + ## * replaces any ``\f`` by ``\\f`` + ## * replaces any ``\c`` by ``\\c`` + ## * replaces any ``\e`` by ``\\e`` + ## * replaces any other character not in the set ``{'\21..'\126'} ## by ``\xHH`` where ``HH`` is its hexadecimal value. ## ## The procedure has been designed so that its output is usable for many ## different common syntaxes. ## **Note**: This is not correct for producing Ansi C code! case c - of '\0'..'\31', '\127'..'\255': - add(s, "\\x") + of '\a': s.add "\\a" # \x07 + of '\b': s.add "\\b" # \x08 + of '\t': s.add "\\t" # \x09 + of '\L': s.add "\\n" # \x0A + of '\v': s.add "\\v" # \x0B + of '\f': s.add "\\f" # \x0C + of '\c': s.add "\\c" # \x0D + of '\e': s.add "\\e" # \x1B + of '\\': s.add("\\\\") + of '\'': s.add("\\'") + of '\"': s.add("\\\"") + of {'\32'..'\126'} - {'\\', '\'', '\"'}: s.add(c) + else: + s.add("\\x") const HexChars = "0123456789ABCDEF" let n = ord(c) s.add(HexChars[int((n and 0xF0) shr 4)]) s.add(HexChars[int(n and 0xF)]) - of '\\': add(s, "\\\\") - of '\'': add(s, "\\'") - of '\"': add(s, "\\\"") - else: add(s, c) proc addQuoted*[T](s: var string, x: T) = ## Appends `x` to string `s` in place, applying quoting and escaping ## if `x` is a string or char. See ## `addEscapedChar <system.html#addEscapedChar>`_ - ## for the escaping scheme. + ## for the escaping scheme. When `x` is a string, characters in the + ## range ``{\128..\255}`` are never escaped so that multibyte UTF-8 + ## characters are untouched (note that this behavior is different from + ## ``addEscapedChar``). ## ## The Nim standard library uses this function on the elements of ## collections when producing a string representation of a collection. @@ -3960,10 +3981,15 @@ proc addQuoted*[T](s: var string, x: T) = ## tmp.add(", ") ## tmp.addQuoted('c') ## assert(tmp == """1, "string", 'c'""") - when T is string: + when T is string or T is cstring: s.add("\"") for c in x: - s.addEscapedChar(c) + # Only ASCII chars are escaped to avoid butchering + # multibyte UTF-8 characters. + if c <= 127.char: + s.addEscapedChar(c) + else: + s.add c s.add("\"") elif T is char: s.add("'") @@ -3977,22 +4003,31 @@ proc addQuoted*[T](s: var string, x: T) = when hasAlloc: # XXX: make these the default (or implement the NilObject optimization) - proc safeAdd*[T](x: var seq[T], y: T) {.noSideEffect.} = + proc safeAdd*[T](x: var seq[T], y: T) {.noSideEffect, deprecated.} = ## Adds ``y`` to ``x`` unless ``x`` is not yet initialized; in that case, ## ``x`` becomes ``@[y]`` - if x == nil: x = @[y] - else: x.add(y) + when defined(nimNoNilSeqs): + x.add(y) + else: + if x == nil: x = @[y] + else: x.add(y) - proc safeAdd*(x: var string, y: char) = + proc safeAdd*(x: var string, y: char) {.noSideEffect, deprecated.} = ## Adds ``y`` to ``x``. If ``x`` is ``nil`` it is initialized to ``""`` - if x == nil: x = "" - x.add(y) + when defined(nimNoNilSeqs): + x.add(y) + else: + if x == nil: x = "" + x.add(y) - proc safeAdd*(x: var string, y: string) = + proc safeAdd*(x: var string, y: string) {.noSideEffect, deprecated.} = ## Adds ``y`` to ``x`` unless ``x`` is not yet initalized; in that ## case, ``x`` becomes ``y`` - if x == nil: x = y - else: x.add(y) + when defined(nimNoNilSeqs): + x.add(y) + else: + if x == nil: x = y + else: x.add(y) proc locals*(): RootObj {.magic: "Plugin", noSideEffect.} = ## generates a tuple constructor expression listing all the local variables @@ -4018,7 +4053,9 @@ proc locals*(): RootObj {.magic: "Plugin", noSideEffect.} = ## # -> B is 1 discard -when hasAlloc and not defined(nimscript) and not defined(JS): +when hasAlloc and not defined(nimscript) and not defined(JS) and + not defined(gcDestructors): + # XXX how to implement 'deepCopy' is an open problem. proc deepCopy*[T](x: var T, y: T) {.noSideEffect, magic: "DeepCopy".} = ## performs a deep copy of `y` and copies it into `x`. ## This is also used by the code generator @@ -4036,14 +4073,19 @@ proc procCall*(x: untyped) {.magic: "ProcCall", compileTime.} = ## procCall someMethod(a, b) discard -proc xlen*(x: string): int {.magic: "XLenStr", noSideEffect.} = discard -proc xlen*[T](x: seq[T]): int {.magic: "XLenSeq", noSideEffect.} = +proc xlen*(x: string): int {.magic: "XLenStr", noSideEffect, + deprecated: "use len() instead".} = + ## **Deprecated since version 0.18.1**. Use len() instead. + discard +proc xlen*[T](x: seq[T]): int {.magic: "XLenSeq", noSideEffect, + deprecated: "use len() instead".} = ## returns the length of a sequence or a string without testing for 'nil'. ## This is an optimization that rarely makes sense. + ## **Deprecated since version 0.18.1**. Use len() instead. discard -proc `==` *(x, y: cstring): bool {.magic: "EqCString", noSideEffect, +proc `==`*(x, y: cstring): bool {.magic: "EqCString", noSideEffect, inline.} = ## Checks for equality between two `cstring` variables. proc strcmp(a, b: cstring): cint {.noSideEffect, @@ -4052,6 +4094,11 @@ proc `==` *(x, y: cstring): bool {.magic: "EqCString", noSideEffect, elif x.isNil or y.isNil: result = false else: result = strcmp(x, y) == 0 +when defined(nimNoNilSeqs2): + when not compileOption("nilseqs"): + proc `==`*(x: string; y: type(nil)): bool {.error.} = discard + proc `==`*(x: type(nil); y: string): bool {.error.} = discard + template closureScope*(body: untyped): untyped = ## Useful when creating a closure in a loop to capture local loop variables by ## their current iteration values. Example: @@ -4073,8 +4120,43 @@ template closureScope*(body: untyped): untyped = ## myClosure() # outputs 3 (proc() = body)() +template once*(body: untyped): untyped = + ## Executes a block of code only once (the first time the block is reached). + ## When hot code reloading is enabled, protects top-level code from being + ## re-executed on each module reload. + ## + ## .. code-block:: nim + ## proc draw(t: Triangle) = + ## once: + ## graphicsInit() + ## + ## line(t.p1, t.p2) + ## line(t.p2, t.p3) + ## line(t.p3, t.p1) + ## + var alreadyExecuted {.global.} = false + if not alreadyExecuted: + alreadyExecuted = true + body + {.pop.} #{.push warning[GcMem]: off, warning[Uninit]: off.} +proc substr*(s: string, first, last: int): string = + let first = max(first, 0) + let L = max(min(last, high(s)) - first + 1, 0) + result = newString(L) + for i in 0 .. L-1: + result[i] = s[i+first] + +proc substr*(s: string, first = 0): string = + ## copies a slice of `s` into a new string and returns this new + ## string. The bounds `first` and `last` denote the indices of + ## the first and last characters that shall be copied. If ``last`` + ## is omitted, it is treated as ``high(s)``. If ``last >= s.len``, ``s.len`` + ## is used instead: This means ``substr`` can also be used to `cut`:idx: + ## or `limit`:idx: a string's length. + result = substr(s, first, high(s)) + when defined(nimconfig): include "system/nimscript" @@ -4102,24 +4184,28 @@ else: template doAssertRaises*(exception, code: untyped): typed = ## Raises ``AssertionError`` if specified ``code`` does not raise the - ## specified exception. - runnableExamples: - doAssertRaises(ValueError): - raise newException(ValueError, "Hello World") - + ## specified exception. Example: + ## + ## .. code-block:: nim + ## doAssertRaises(ValueError): + ## raise newException(ValueError, "Hello World") + var wrong = false try: - block: + if true: code - raiseAssert(astToStr(exception) & " wasn't raised by:\n" & astToStr(code)) + wrong = true except exception: discard except Exception as exc: raiseAssert(astToStr(exception) & " wasn't raised, another error was raised instead by:\n"& astToStr(code)) + if wrong: + raiseAssert(astToStr(exception) & " wasn't raised by:\n" & astToStr(code)) -when defined(cpp) and appType != "lib" and not defined(js) and - not defined(nimscript) and hostOS != "standalone": +when defined(cpp) and appType != "lib" and + not defined(js) and not defined(nimscript) and + hostOS != "standalone" and not defined(noCppExceptions): proc setTerminate(handler: proc() {.noconv.}) {.importc: "std::set_terminate", header: "<exception>".} setTerminate proc() {.noconv.} = @@ -4128,9 +4214,46 @@ when defined(cpp) and appType != "lib" and not defined(js) and let ex = getCurrentException() let trace = ex.getStackTrace() - stderr.write trace & "Error: unhandled exception: " & ex.msg & - " [" & $ex.name & "]\n" + when defined(genode): + # stderr not available by default, use the LOG session + echo trace & "Error: unhandled exception: " & ex.msg & + " [" & $ex.name & "]\n" + else: + stderr.write trace & "Error: unhandled exception: " & ex.msg & + " [" & $ex.name & "]\n" quit 1 +when not defined(js): + proc toOpenArray*[T](x: seq[T]; first, last: int): openarray[T] {. + magic: "Slice".} + proc toOpenArray*[T](x: openarray[T]; first, last: int): openarray[T] {. + magic: "Slice".} + proc toOpenArray*[I, T](x: array[I, T]; first, last: I): openarray[T] {. + magic: "Slice".} + proc toOpenArray*(x: string; first, last: int): openarray[char] {. + magic: "Slice".} + proc toOpenArrayByte*(x: string; first, last: int): openarray[byte] {. + magic: "Slice".} + type - ForLoopExpr*{.compilerProc.}[T] = object ## the type of a 'for' loop expression + ForLoopStmt* {.compilerProc.} = object ## special type that marks a macro + ## as a `for-loop macro`:idx: + +when defined(genode): + var componentConstructHook*: proc (env: GenodeEnv) {.nimcall.} + ## Hook into the Genode component bootstrap process. + ## This hook is called after all globals are initialized. + ## When this hook is set the component will not automatically exit, + ## call ``quit`` explicitly to do so. This is the only available method + ## of accessing the initial Genode environment. + + proc nim_component_construct(env: GenodeEnv) {.exportc.} = + ## Procedure called during ``Component::construct`` by the loader. + if componentConstructHook.isNil: + env.quit(programResult) + # No native Genode application initialization, + # exit as would POSIX. + else: + componentConstructHook(env) + # Perform application initialization + # and return to thread entrypoint. diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index 4291013a2..b090117a9 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -29,7 +29,9 @@ const FliOffset = 6 RealFli = MaxFli - FliOffset - HugeChunkSize = int high(int32) - 1 # 2 GB, depends on TLSF's impl + # size of chunks in last matrix bin + MaxBigChunkSize = 1 shl MaxFli - 1 shl (MaxFli-MaxLog2Sli-1) + HugeChunkSize = MaxBigChunkSize + 1 type PTrunk = ptr Trunk @@ -114,6 +116,8 @@ type nextChunkSize: int bottomData: AvlNode heapLinks: HeapLinks + when defined(nimTypeNames): + allocCounter, deallocCounter: int const fsLookupTable: array[byte, int8] = [ @@ -154,10 +158,11 @@ proc mappingSearch(r, fl, sl: var int) {.inline.} = # PageSize alignment: let t = roundup((1 shl (msbit(uint32 r) - MaxLog2Sli)), PageSize) - 1 r = r + t + r = r and not t + r = min(r, MaxBigChunkSize) fl = msbit(uint32 r) sl = (r shr (fl - MaxLog2Sli)) - MaxSli dec fl, FliOffset - r = r and not t sysAssert((r and PageMask) == 0, "mappingSearch: still not aligned") # See http://www.gii.upv.es/tlsf/files/papers/tlsf_desc.pdf for details of @@ -431,8 +436,9 @@ proc requestOsChunks(a: var MemRegion, size: int): PBigChunk = a.nextChunkSize = PageSize*4 else: a.nextChunkSize = min(roundup(usedMem shr 2, PageSize), a.nextChunkSize * 2) - var size = size + a.nextChunkSize = min(a.nextChunkSize, MaxBigChunkSize) + var size = size if size > a.nextChunkSize: result = cast[PBigChunk](osAllocPages(size)) else: @@ -448,10 +454,10 @@ proc requestOsChunks(a: var MemRegion, size: int): PBigChunk = a.addHeapLink(result, size) when defined(debugHeapLinks): cprintf("owner: %p; result: %p; next pointer %p; size: %ld\n", addr(a), - result, result.heapLink, result.origSize) + result, result.heapLink, result.size) when defined(memtracker): - trackLocation(addr result.origSize, sizeof(int)) + trackLocation(addr result.size, sizeof(int)) sysAssert((cast[ByteAddress](result) and PageMask) == 0, "requestOsChunks 1") #zeroMem(result, size) @@ -518,55 +524,61 @@ proc updatePrevSize(a: var MemRegion, c: PBigChunk, if isAccessible(a, ri): ri.prevSize = prevSize or (ri.prevSize and 1) +proc splitChunk2(a: var MemRegion, c: PBigChunk, size: int): PBigChunk = + result = cast[PBigChunk](cast[ByteAddress](c) +% size) + result.size = c.size - size + track("result.size", addr result.size, sizeof(int)) + # XXX check if these two nil assignments are dead code given + # addChunkToMatrix's implementation: + result.next = nil + result.prev = nil + # size and not used: + result.prevSize = size + sysAssert((size and 1) == 0, "splitChunk 2") + sysAssert((size and PageMask) == 0, + "splitChunk: size is not a multiple of the PageSize") + updatePrevSize(a, c, result.size) + c.size = size + incl(a, a.chunkStarts, pageIndex(result)) + +proc splitChunk(a: var MemRegion, c: PBigChunk, size: int) = + let rest = splitChunk2(a, c, size) + addChunkToMatrix(a, rest) + proc freeBigChunk(a: var MemRegion, c: PBigChunk) = var c = c sysAssert(c.size >= PageSize, "freeBigChunk") inc(a.freeMem, c.size) - when coalescRight: - var ri = cast[PChunk](cast[ByteAddress](c) +% c.size) - sysAssert((cast[ByteAddress](ri) and PageMask) == 0, "freeBigChunk 2") - if isAccessible(a, ri) and chunkUnused(ri): - sysAssert(not isSmallChunk(ri), "freeBigChunk 3") - if not isSmallChunk(ri): - removeChunkFromMatrix(a, cast[PBigChunk](ri)) - inc(c.size, ri.size) - excl(a.chunkStarts, pageIndex(ri)) + c.prevSize = c.prevSize and not 1 # set 'used' to false when coalescLeft: - let prevSize = c.prevSize and not 1 + let prevSize = c.prevSize if prevSize != 0: var le = cast[PChunk](cast[ByteAddress](c) -% prevSize) sysAssert((cast[ByteAddress](le) and PageMask) == 0, "freeBigChunk 4") if isAccessible(a, le) and chunkUnused(le): sysAssert(not isSmallChunk(le), "freeBigChunk 5") - if not isSmallChunk(le): + if not isSmallChunk(le) and le.size < MaxBigChunkSize: removeChunkFromMatrix(a, cast[PBigChunk](le)) inc(le.size, c.size) excl(a.chunkStarts, pageIndex(c)) c = cast[PBigChunk](le) - - incl(a, a.chunkStarts, pageIndex(c)) - updatePrevSize(a, c, c.size) + if c.size > MaxBigChunkSize: + let rest = splitChunk2(a, c, MaxBigChunkSize) + addChunkToMatrix(a, c) + c = rest + when coalescRight: + var ri = cast[PChunk](cast[ByteAddress](c) +% c.size) + sysAssert((cast[ByteAddress](ri) and PageMask) == 0, "freeBigChunk 2") + if isAccessible(a, ri) and chunkUnused(ri): + sysAssert(not isSmallChunk(ri), "freeBigChunk 3") + if not isSmallChunk(ri) and c.size < MaxBigChunkSize: + removeChunkFromMatrix(a, cast[PBigChunk](ri)) + inc(c.size, ri.size) + excl(a.chunkStarts, pageIndex(ri)) + if c.size > MaxBigChunkSize: + let rest = splitChunk2(a, c, MaxBigChunkSize) + addChunkToMatrix(a, rest) addChunkToMatrix(a, c) - # set 'used' to false: - c.prevSize = c.prevSize and not 1 - -proc splitChunk(a: var MemRegion, c: PBigChunk, size: int) = - var rest = cast[PBigChunk](cast[ByteAddress](c) +% size) - rest.size = c.size - size - track("rest.origSize", addr rest.origSize, sizeof(int)) - # XXX check if these two nil assignments are dead code given - # addChunkToMatrix's implementation: - rest.next = nil - rest.prev = nil - # size and not used: - rest.prevSize = size - sysAssert((size and 1) == 0, "splitChunk 2") - sysAssert((size and PageMask) == 0, - "splitChunk: size is not a multiple of the PageSize") - updatePrevSize(a, c, rest.size) - c.size = size - incl(a, a.chunkStarts, pageIndex(rest)) - addChunkToMatrix(a, rest) proc getBigChunk(a: var MemRegion, size: int): PBigChunk = sysAssert(size > 0, "getBigChunk 2") @@ -590,7 +602,7 @@ proc getBigChunk(a: var MemRegion, size: int): PBigChunk = splitChunk(a, result, size) # set 'used' to to true: result.prevSize = 1 - track("setUsedToFalse", addr result.origSize, sizeof(int)) + track("setUsedToFalse", addr result.size, sizeof(int)) incl(a, a.chunkStarts, pageIndex(result)) dec(a.freeMem, size) @@ -728,6 +740,8 @@ when false: result = nil proc rawAlloc(a: var MemRegion, requestedSize: int): pointer = + when defined(nimTypeNames): + inc(a.allocCounter) sysAssert(allocInv(a), "rawAlloc: begin") sysAssert(roundup(65, 8) == 72, "rawAlloc: roundup broken") sysAssert(requestedSize >= sizeof(FreeCell), "rawAlloc: requested size too small") @@ -801,6 +815,8 @@ proc rawAlloc0(a: var MemRegion, requestedSize: int): pointer = zeroMem(result, requestedSize) proc rawDealloc(a: var MemRegion, p: pointer) = + when defined(nimTypeNames): + inc(a.deallocCounter) #sysAssert(isAllocatedPtr(a, p), "rawDealloc: no allocated pointer") sysAssert(allocInv(a), "rawDealloc: begin") var c = pageAddr(p) @@ -821,7 +837,7 @@ proc rawDealloc(a: var MemRegion, p: pointer) = c.freeList = f when overwriteFree: # set to 0xff to check for usage after free bugs: - c_memset(cast[pointer](cast[int](p) +% sizeof(FreeCell)), -1'i32, + nimSetMem(cast[pointer](cast[int](p) +% sizeof(FreeCell)), -1'i32, s -% sizeof(FreeCell)) # check if it is not in the freeSmallChunks[s] list: if c.free < s: @@ -838,7 +854,7 @@ proc rawDealloc(a: var MemRegion, p: pointer) = s == 0, "rawDealloc 2") else: # set to 0xff to check for usage after free bugs: - when overwriteFree: c_memset(p, -1'i32, c.size -% bigChunkOverhead()) + when overwriteFree: nimSetMem(p, -1'i32, c.size -% bigChunkOverhead()) # free big chunk var c = cast[PBigChunk](c) dec a.occ, c.size @@ -952,7 +968,7 @@ proc deallocOsPages(a: var MemRegion) = let (p, size) = it.chunks[i] when defined(debugHeapLinks): cprintf("owner %p; dealloc A: %p size: %ld; next: %p\n", addr(a), - it, it.origSize, next) + it, it.size, next) sysAssert size >= PageSize, "origSize too small" osDeallocPages(p, size) it = next @@ -966,6 +982,10 @@ proc getOccupiedMem(a: MemRegion): int {.inline.} = result = a.occ # a.currMem - a.freeMem +when defined(nimTypeNames): + proc getMemCounters(a: MemRegion): (int, int) {.inline.} = + (a.allocCounter, a.deallocCounter) + # ---------------------- thread memory region ------------------------------- template instantiateForRegion(allocator: untyped) = @@ -1009,6 +1029,9 @@ template instantiateForRegion(allocator: untyped) = proc getOccupiedMem(): int = return allocator.occ #getTotalMem() - getFreeMem() proc getMaxMem*(): int = return getMaxMem(allocator) + when defined(nimTypeNames): + proc getMemCounters*(): (int, int) = getMemCounters(allocator) + # -------------------- shared heap region ---------------------------------- when hasThreadSupport: var sharedHeap: MemRegion diff --git a/lib/system/ansi_c.nim b/lib/system/ansi_c.nim index 0bac979e7..67e42c0af 100644 --- a/lib/system/ansi_c.nim +++ b/lib/system/ansi_c.nim @@ -25,6 +25,11 @@ proc c_memset(p: pointer, value: cint, size: csize): pointer {. importc: "memset", header: "<string.h>", discardable.} proc c_strcmp(a, b: cstring): cint {. importc: "strcmp", header: "<string.h>", noSideEffect.} +proc c_strlen(a: cstring): csize {. + importc: "strlen", header: "<string.h>", noSideEffect.} +proc c_abort() {. + importc: "abort", header: "<stdlib.h>", noSideEffect.} + when defined(linux) and defined(amd64): type @@ -45,7 +50,7 @@ when defined(windows): SIGTERM = cint(15) elif defined(macosx) or defined(linux) or defined(freebsd) or defined(openbsd) or defined(netbsd) or defined(solaris) or - defined(dragonfly): + defined(dragonfly) or defined(nintendoswitch) or defined(genode): const SIGABRT = cint(6) SIGFPE = cint(8) @@ -54,6 +59,15 @@ elif defined(macosx) or defined(linux) or defined(freebsd) or SIGSEGV = cint(11) SIGTERM = cint(15) SIGPIPE = cint(13) +elif defined(haiku): + const + SIGABRT = cint(6) + SIGFPE = cint(8) + SIGILL = cint(4) + SIGINT = cint(2) + SIGSEGV = cint(11) + SIGTERM = cint(15) + SIGPIPE = cint(7) else: when NoFakeVars: {.error: "SIGABRT not ported to your platform".} @@ -69,6 +83,8 @@ else: when defined(macosx): const SIGBUS = cint(10) +elif defined(haiku): + const SIGBUS = cint(30) else: template SIGBUS: untyped = SIGSEGV diff --git a/lib/system/assign.nim b/lib/system/assign.nim index f061c89cf..2b74e6682 100644 --- a/lib/system/assign.nim +++ b/lib/system/assign.nim @@ -74,13 +74,17 @@ proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) = var dst = cast[ByteAddress](cast[PPointer](dest)[]) for i in 0..seq.len-1: genericAssignAux( - cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize), + cast[pointer](dst +% i *% mt.base.size +% GenericSeqSize), cast[pointer](cast[ByteAddress](s2) +% i *% mt.base.size +% GenericSeqSize), mt.base, shallow) of tyObject: - if mt.base != nil: - genericAssignAux(dest, src, mt.base, shallow) + var it = mt.base + # don't use recursion here on the PNimType because the subtype + # check should only be done at the very end: + while it != nil: + genericAssignAux(dest, src, it.node, shallow) + it = it.base genericAssignAux(dest, src, mt.node, shallow) # we need to copy m_type field for tyObject, as it could be empty for # sequence reallocations: @@ -89,13 +93,15 @@ proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) = # if p of TB: # var tbObj = TB(p) # tbObj of TC # needs to be false! + #c_fprintf(stdout, "%s %s\n", pint[].name, mt.name) + chckObjAsgn(cast[ptr PNimType](src)[], mt) pint[] = mt # cast[ptr PNimType](src)[] of tyTuple: genericAssignAux(dest, src, mt.node, shallow) of tyArray, tyArrayConstr: for i in 0..(mt.size div mt.base.size)-1: - genericAssignAux(cast[pointer](d +% i*% mt.base.size), - cast[pointer](s +% i*% mt.base.size), mt.base, shallow) + genericAssignAux(cast[pointer](d +% i *% mt.base.size), + cast[pointer](s +% i *% mt.base.size), mt.base, shallow) of tyRef: unsureAsgnRef(cast[PPointer](dest), cast[PPointer](s)[]) of tyOptAsRef: @@ -160,8 +166,8 @@ proc genericAssignOpenArray(dest, src: pointer, len: int, d = cast[ByteAddress](dest) s = cast[ByteAddress](src) for i in 0..len-1: - genericAssign(cast[pointer](d +% i*% mt.base.size), - cast[pointer](s +% i*% mt.base.size), mt.base) + genericAssign(cast[pointer](d +% i *% mt.base.size), + cast[pointer](s +% i *% mt.base.size), mt.base) proc objectInit(dest: pointer, typ: PNimType) {.compilerProc, benign.} proc objectInitAux(dest: pointer, n: ptr TNimNode) {.benign.} = @@ -196,11 +202,6 @@ proc objectInit(dest: pointer, typ: PNimType) = # ---------------------- assign zero ----------------------------------------- -proc nimDestroyRange[T](r: T) {.compilerProc.} = - # internal proc used for destroying sequences and arrays - mixin `=destroy` - for i in countup(0, r.len - 1): `=destroy`(r[i]) - proc genericReset(dest: pointer, mt: PNimType) {.compilerProc, benign.} proc genericResetAux(dest: pointer, n: ptr TNimNode) = var d = cast[ByteAddress](dest) @@ -229,7 +230,7 @@ proc genericReset(dest: pointer, mt: PNimType) = pint[] = nil of tyArray, tyArrayConstr: for i in 0..(mt.size div mt.base.size)-1: - genericReset(cast[pointer](d +% i*% mt.base.size), mt.base) + genericReset(cast[pointer](d +% i *% mt.base.size), mt.base) else: zeroMem(dest, mt.size) # set raw bits to zero diff --git a/lib/system/atomics.nim b/lib/system/atomics.nim index afc435638..56ebde823 100644 --- a/lib/system/atomics.nim +++ b/lib/system/atomics.nim @@ -241,7 +241,7 @@ when defined(vcc): else: {.error: "invalid CAS instruction".} -elif defined(tcc) and not defined(windows): +elif defined(tcc): when defined(amd64): {.emit:""" static int __tcc_cas(int *ptr, int oldVal, int newVal) @@ -262,7 +262,7 @@ static int __tcc_cas(int *ptr, int oldVal, int newVal) } """.} else: - assert sizeof(int) == 4 + #assert sizeof(int) == 4 {.emit:""" static int __tcc_cas(int *ptr, int oldVal, int newVal) { @@ -295,7 +295,7 @@ else: when (defined(x86) or defined(amd64)) and defined(vcc): proc cpuRelax* {.importc: "YieldProcessor", header: "<windows.h>".} -elif (defined(x86) or defined(amd64)) and someGcc: +elif (defined(x86) or defined(amd64)) and (someGcc or defined(bcc)): proc cpuRelax* {.inline.} = {.emit: """asm volatile("pause" ::: "memory");""".} elif someGcc or defined(tcc): diff --git a/lib/system/cgprocs.nim b/lib/system/cgprocs.nim index 660c68116..72219c2b7 100644 --- a/lib/system/cgprocs.nim +++ b/lib/system/cgprocs.nim @@ -12,7 +12,6 @@ type LibHandle = pointer # private type ProcAddr = pointer # library loading and loading of procs: -{.deprecated: [TLibHandle: LibHandle, TProcAddr: ProcAddr].} proc nimLoadLibrary(path: string): LibHandle {.compilerproc.} proc nimUnloadLibrary(lib: LibHandle) {.compilerproc.} diff --git a/lib/system/channels.nim b/lib/system/channels.nim index df6c6d41e..14d3a3005 100644 --- a/lib/system/channels.nim +++ b/lib/system/channels.nim @@ -32,8 +32,6 @@ type PRawChannel = ptr RawChannel LoadStoreMode = enum mStore, mLoad Channel* {.gcsafe.}[TMsg] = RawChannel ## a channel for thread communication -{.deprecated: [TRawChannel: RawChannel, TLoadStoreMode: LoadStoreMode, - TChannel: Channel].} const ChannelDeadMask = -2 @@ -118,7 +116,7 @@ proc storeAux(dest, src: pointer, mt: PNimType, t: PRawChannel, if mode == mStore: x[] = alloc0(t.region, seq.len *% mt.base.size +% GenericSeqSize) else: - unsureAsgnRef(x, newObj(mt, seq.len * mt.base.size + GenericSeqSize)) + unsureAsgnRef(x, newSeq(mt, seq.len)) var dst = cast[ByteAddress](cast[PPointer](dest)[]) var dstseq = cast[PGenericSeq](dst) dstseq.len = seq.len @@ -235,7 +233,7 @@ proc send*[TMsg](c: var Channel[TMsg], msg: TMsg) {.inline.} = proc trySend*[TMsg](c: var Channel[TMsg], msg: TMsg): bool {.inline.} = ## Tries to send a message to a thread. `msg` is deeply copied. Doesn't block. ## Returns `false` if the message was not sent because number of pending items - ## in the cannel exceeded `maxItems`. + ## in the channel exceeded `maxItems`. sendImpl(cast[PRawChannel](addr c), cast[PNimType](getTypeInfo(msg)), unsafeAddr(msg), true) proc llRecv(q: PRawChannel, res: pointer, typ: PNimType) = diff --git a/lib/system/chcks.nim b/lib/system/chcks.nim index 69b680dbd..d3651f659 100644 --- a/lib/system/chcks.nim +++ b/lib/system/chcks.nim @@ -52,6 +52,11 @@ proc chckNil(p: pointer) = if p == nil: sysFatal(NilAccessError, "attempt to write to a nil address") +when defined(nimNewRuntime): + proc chckMove(b: bool) {.compilerproc.} = + if not b: + sysFatal(MoveError, "attempt to access an object that was moved") + proc chckNilDisp(p: pointer) {.compilerproc.} = if p == nil: sysFatal(NilAccessError, "cannot dispatch; dispatcher is nil") diff --git a/lib/system/deepcopy.nim b/lib/system/deepcopy.nim index 51e138e5e..750da00cf 100644 --- a/lib/system/deepcopy.nim +++ b/lib/system/deepcopy.nim @@ -105,7 +105,7 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; tab: var PtrTable) = var dst = cast[ByteAddress](cast[PPointer](dest)[]) for i in 0..seq.len-1: genericDeepCopyAux( - cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize), + cast[pointer](dst +% i *% mt.base.size +% GenericSeqSize), cast[pointer](cast[ByteAddress](s2) +% i *% mt.base.size +% GenericSeqSize), mt.base, tab) @@ -122,8 +122,8 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; tab: var PtrTable) = genericDeepCopyAux(dest, src, mt.node, tab) of tyArray, tyArrayConstr: for i in 0..(mt.size div mt.base.size)-1: - genericDeepCopyAux(cast[pointer](d +% i*% mt.base.size), - cast[pointer](s +% i*% mt.base.size), mt.base, tab) + genericDeepCopyAux(cast[pointer](d +% i *% mt.base.size), + cast[pointer](s +% i *% mt.base.size), mt.base, tab) of tyRef, tyOptAsRef: let s2 = cast[PPointer](src)[] if s2 == nil: @@ -183,5 +183,5 @@ proc genericDeepCopyOpenArray(dest, src: pointer, len: int, d = cast[ByteAddress](dest) s = cast[ByteAddress](src) for i in 0..len-1: - genericDeepCopy(cast[pointer](d +% i*% mt.base.size), - cast[pointer](s +% i*% mt.base.size), mt.base) + genericDeepCopy(cast[pointer](d +% i *% mt.base.size), + cast[pointer](s +% i *% mt.base.size), mt.base) diff --git a/lib/system/dyncalls.nim b/lib/system/dyncalls.nim index c8e251d1e..8fb694829 100644 --- a/lib/system/dyncalls.nim +++ b/lib/system/dyncalls.nim @@ -31,6 +31,13 @@ proc nimLoadLibraryError(path: string) = stderr.rawWrite("\n") when not defined(nimDebugDlOpen) and not defined(windows): stderr.rawWrite("compile with -d:nimDebugDlOpen for more information\n") + when defined(windows) and defined(guiapp): + # Because console output is not shown in GUI apps, display error as message box: + const prefix = "could not load: " + var msg: array[1000, char] + copyMem(msg[0].addr, prefix.cstring, prefix.len) + copyMem(msg[prefix.len].addr, path.cstring, min(path.len + 1, 1000 - prefix.len)) + discard MessageBoxA(0, msg[0].addr, nil, 0) quit(1) proc procAddrError(name: cstring) {.noinline.} = @@ -160,6 +167,24 @@ elif defined(genode): proc nimGetProcAddr(lib: LibHandle, name: cstring): ProcAddr {. error: "nimGetProcAddr not implemented".} +elif defined(nintendoswitch): + proc nimUnloadLibrary(lib: LibHandle) = + stderr.rawWrite("nimUnLoadLibrary not implemented") + stderr.rawWrite("\n") + quit(1) + + proc nimLoadLibrary(path: string): LibHandle = + stderr.rawWrite("nimLoadLibrary not implemented") + stderr.rawWrite("\n") + quit(1) + + + proc nimGetProcAddr(lib: LibHandle, name: cstring): ProcAddr = + stderr.rawWrite("nimGetProAddr not implemented") + stderr.write(name) + stderr.rawWrite("\n") + quit(1) + else: {.error: "no implementation for dyncalls".} diff --git a/lib/system/embedded.nim b/lib/system/embedded.nim index a14f43e7e..4d453fcca 100644 --- a/lib/system/embedded.nim +++ b/lib/system/embedded.nim @@ -40,4 +40,7 @@ proc reraiseException() {.compilerRtl.} = proc writeStackTrace() = discard -proc setControlCHook(hook: proc () {.noconv.} not nil) = discard +proc setControlCHook(hook: proc () {.noconv.}) = discard + +proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} = + sysFatal(ReraiseError, "exception handling is not available") diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index 476582af2..7d5f5af7f 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -31,7 +31,17 @@ proc showErrorMessage(data: cstring) {.gcsafe.} = if errorMessageWriter != nil: errorMessageWriter($data) else: - writeToStdErr(data) + when defined(genode): + # stderr not available by default, use the LOG session + echo data + else: + writeToStdErr(data) + +proc quitOrDebug() {.inline.} = + when not defined(endb): + quit(1) + else: + endbStep() # call the debugger proc chckIndx(i, a, b: int): int {.inline, compilerproc, benign.} proc chckRange(i, a, b: int): int {.inline, compilerproc, benign.} @@ -50,6 +60,8 @@ var # list of exception handlers # a global variable for the root of all try blocks currException {.threadvar.}: ref Exception + raise_counter {.threadvar.}: uint + gcFramePtr {.threadvar.}: GcFrame type @@ -108,6 +120,25 @@ proc pushCurrentException(e: ref Exception) {.compilerRtl, inl.} = proc popCurrentException {.compilerRtl, inl.} = currException = currException.up +proc popCurrentExceptionEx(id: uint) {.compilerRtl.} = + # in cpp backend exceptions can pop-up in the different order they were raised, example #5628 + if currException.raise_id == id: + currException = currException.up + else: + var cur = currException.up + var prev = currException + while cur != nil and cur.raise_id != id: + prev = cur + cur = cur.up + if cur == nil: + showErrorMessage("popCurrentExceptionEx() exception was not found in the exception stack. Aborting...") + quitOrDebug() + prev.up = cur.up + +proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} = + if not e.isNil: + currException = e + # some platforms have native support for stack traces: const nativeStackTraceSupported* = (defined(macosx) or defined(linux)) and @@ -182,7 +213,7 @@ proc auxWriteStackTrace(f: PFrame; s: var seq[StackTraceEntry]) = inc(i) it = it.prev var last = i-1 - if s.isNil: + if s.len == 0: s = newSeq[StackTraceEntry](i) else: last = s.len + i - 1 @@ -276,7 +307,7 @@ when hasSomeStackTrace: when NimStackTrace: auxWriteStackTrace(framePtr, s) else: - s = nil + s = @[] proc stackTraceAvailable(): bool = when NimStackTrace: @@ -291,12 +322,6 @@ when hasSomeStackTrace: else: proc stackTraceAvailable*(): bool = result = false -proc quitOrDebug() {.inline.} = - when not defined(endb): - quit(1) - else: - endbStep() # call the debugger - var onUnhandledException*: (proc (errorMsg: string) {. nimcall.}) ## set this error \ ## handler to override the existing behaviour on an unhandled exception. @@ -320,6 +345,10 @@ proc raiseExceptionAux(e: ref Exception) = quitOrDebug() else: pushCurrentException(e) + raise_counter.inc + if raise_counter == 0: + raise_counter.inc # skip zero at overflow + e.raise_id = raise_counter {.emit: "`e`->raise();".} else: if excHandler != nil: @@ -332,10 +361,10 @@ proc raiseExceptionAux(e: ref Exception) = else: when hasSomeStackTrace: var buf = newStringOfCap(2000) - if isNil(e.trace): rawWriteStackTrace(buf) + if e.trace.len == 0: rawWriteStackTrace(buf) else: add(buf, $e.trace) add(buf, "Error: unhandled exception: ") - if not isNil(e.msg): add(buf, e.msg) + add(buf, e.msg) add(buf, " [") add(buf, $e.name) add(buf, "]\n") @@ -353,7 +382,7 @@ proc raiseExceptionAux(e: ref Exception) = var buf: array[0..2000, char] var L = 0 add(buf, "Error: unhandled exception: ") - if not isNil(e.msg): add(buf, e.msg) + add(buf, e.msg) add(buf, " [") xadd(buf, e.name, e.name.len) add(buf, "]\n") @@ -368,7 +397,7 @@ proc raiseExceptionAux(e: ref Exception) = proc raiseException(e: ref Exception, ename: cstring) {.compilerRtl.} = if e.name.isNil: e.name = ename when hasSomeStackTrace: - if e.trace.isNil: + if e.trace.len == 0: rawWriteStackTrace(e.trace) elif framePtr != nil: e.trace.add reraisedFrom(reraisedFromBegin) @@ -398,15 +427,16 @@ proc getStackTrace(): string = result = "No stack traceback available\n" proc getStackTrace(e: ref Exception): string = - if not isNil(e) and not isNil(e.trace): + if not isNil(e): result = $e.trace else: result = "" -proc getStackTraceEntries*(e: ref Exception): seq[StackTraceEntry] = - ## Returns the attached stack trace to the exception ``e`` as - ## a ``seq``. This is not yet available for the JS backend. - shallowCopy(result, e.trace) +when not defined(gcDestructors): + proc getStackTraceEntries*(e: ref Exception): seq[StackTraceEntry] = + ## Returns the attached stack trace to the exception ``e`` as + ## a ``seq``. This is not yet available for the JS backend. + shallowCopy(result, e.trace) when defined(nimRequiresNimFrame): proc stackOverflow() {.noinline.} = @@ -482,7 +512,7 @@ when not defined(noSignalHandler) and not defined(useNimRtl): registerSignalHandler() # call it in initialization section -proc setControlCHook(hook: proc () {.noconv.} not nil) = +proc setControlCHook(hook: proc () {.noconv.}) = # ugly cast, but should work on all architectures: type SignalHandler = proc (sign: cint) {.noconv, benign.} c_signal(SIGINT, cast[SignalHandler](hook)) diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 66d49ce1b..74ac68eea 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -535,7 +535,7 @@ proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer = var oldsize = cast[PGenericSeq](old).len*elemSize + GenericSeqSize copyMem(res, ol, oldsize + sizeof(Cell)) - zeroMem(cast[pointer](cast[ByteAddress](res)+% oldsize +% sizeof(Cell)), + zeroMem(cast[pointer](cast[ByteAddress](res) +% oldsize +% sizeof(Cell)), newsize-oldsize) sysAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "growObj: 3") # This can be wrong for intermediate temps that are nevertheless on the @@ -548,7 +548,10 @@ proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer = gcTrace(res, csAllocated) track("growObj old", ol, 0) track("growObj new", res, newsize) - when reallyDealloc: + when defined(nimIncrSeqV3): + # since we steal the old seq's contents, we set the old length to 0. + cast[PGenericSeq](old).len = 0 + elif reallyDealloc: sysAssert(allocInv(gch.region), "growObj before dealloc") if ol.refcount shr rcShift <=% 1: # free immediately to save space: diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim index 246e55f14..88e150cd1 100644 --- a/lib/system/gc_common.nim +++ b/lib/system/gc_common.nim @@ -37,26 +37,35 @@ when defined(nimTypeNames): a[j] = v if h == 1: break - proc dumpNumberOfInstances* = - # also add the allocated strings to the list of known types: + iterator dumpHeapInstances*(): tuple[name: cstring; count: int; sizes: int] = + ## Iterate over summaries of types on heaps. + ## This data may be inaccurate if allocations + ## are made by the iterator body. if strDesc.nextType == nil: strDesc.nextType = nimTypeRoot strDesc.name = "string" nimTypeRoot = addr strDesc + var it = nimTypeRoot + while it != nil: + if (it.instances > 0 or it.sizes != 0): + yield (it.name, it.instances, it.sizes) + it = it.nextType + + proc dumpNumberOfInstances* = var a: InstancesInfo var n = 0 - var it = nimTypeRoot var totalAllocated = 0 - while it != nil: - if (it.instances > 0 or it.sizes != 0) and n < a.len: - a[n] = (it.name, it.instances, it.sizes) - inc n + for it in dumpHeapInstances(): + a[n] = it + inc n inc totalAllocated, it.sizes - it = it.nextType sortInstances(a, n) for i in 0 .. n-1: c_fprintf(stdout, "[Heap] %s: #%ld; bytes: %ld\n", a[i][0], a[i][1], a[i][2]) c_fprintf(stdout, "[Heap] total number of bytes: %ld\n", totalAllocated) + when defined(nimTypeNames): + let (allocs, deallocs) = getMemCounters() + c_fprintf(stdout, "[Heap] allocs/deallocs: %ld/%ld\n", allocs, deallocs) when defined(nimGcRefLeak): proc oomhandler() = @@ -200,7 +209,7 @@ when declared(threadType): if threadType == ThreadType.None: initAllocator() var stackTop {.volatile.}: pointer - setStackBottom(addr(stackTop)) + nimGC_setStackBottom(addr(stackTop)) initGC() threadType = ThreadType.ForeignThread @@ -257,7 +266,7 @@ when nimCoroutines: gch.activeStack.setPosition(addr(sp)) when not defined(useNimRtl): - proc setStackBottom(theStackBottom: pointer) = + proc nimGC_setStackBottom(theStackBottom: pointer) = # Initializes main stack of the thread. when nimCoroutines: if gch.stack.next == nil: @@ -431,9 +440,9 @@ type GlobalMarkerProc = proc () {.nimcall, benign.} var globalMarkersLen: int - globalMarkers: array[0.. 3499, GlobalMarkerProc] + globalMarkers: array[0..3499, GlobalMarkerProc] threadLocalMarkersLen: int - threadLocalMarkers: array[0.. 3499, GlobalMarkerProc] + threadLocalMarkers: array[0..3499, GlobalMarkerProc] gHeapidGenerator: int proc nimRegisterGlobalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} = diff --git a/lib/system/gc_ms.nim b/lib/system/gc_ms.nim index 75f9c6749..96221b175 100644 --- a/lib/system/gc_ms.nim +++ b/lib/system/gc_ms.nim @@ -264,12 +264,13 @@ proc forAllChildren(cell: PCell, op: WalkOp) = of tyRef, tyOptAsRef: # common case forAllChildrenAux(cellToUsr(cell), cell.typ.base, op) of tySequence: - var d = cast[ByteAddress](cellToUsr(cell)) - var s = cast[PGenericSeq](d) - if s != nil: - for i in 0..s.len-1: - forAllChildrenAux(cast[pointer](d +% i *% cell.typ.base.size +% - GenericSeqSize), cell.typ.base, op) + when not defined(gcDestructors): + var d = cast[ByteAddress](cellToUsr(cell)) + var s = cast[PGenericSeq](d) + if s != nil: + for i in 0..s.len-1: + forAllChildrenAux(cast[pointer](d +% i *% cell.typ.base.size +% + GenericSeqSize), cell.typ.base, op) else: discard proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer = @@ -310,53 +311,54 @@ proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl.} = result = rawNewObj(typ, size, gch) when defined(memProfiler): nimProfile(size) -proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} = - # `newObj` already uses locks, so no need for them here. - let size = addInt(mulInt(len, typ.base.size), GenericSeqSize) - result = newObj(typ, size) - cast[PGenericSeq](result).len = len - cast[PGenericSeq](result).reserved = len - when defined(memProfiler): nimProfile(size) - proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} = result = rawNewObj(typ, size, gch) zeroMem(result, size) when defined(memProfiler): nimProfile(size) -proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} = - let size = addInt(mulInt(len, typ.base.size), GenericSeqSize) - result = newObj(typ, size) - cast[PGenericSeq](result).len = len - cast[PGenericSeq](result).reserved = len - when defined(memProfiler): nimProfile(size) - -proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer = - acquire(gch) - collectCT(gch, newsize + sizeof(Cell)) - var ol = usrToCell(old) - sysAssert(ol.typ != nil, "growObj: 1") - gcAssert(ol.typ.kind in {tyString, tySequence}, "growObj: 2") - - var res = cast[PCell](rawAlloc(gch.region, newsize + sizeof(Cell))) - var elemSize = 1 - if ol.typ.kind != tyString: elemSize = ol.typ.base.size - incTypeSize ol.typ, newsize - - var oldsize = cast[PGenericSeq](old).len*elemSize + GenericSeqSize - copyMem(res, ol, oldsize + sizeof(Cell)) - zeroMem(cast[pointer](cast[ByteAddress](res)+% oldsize +% sizeof(Cell)), - newsize-oldsize) - sysAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "growObj: 3") - when withBitvectors: incl(gch.allocated, res) - when useCellIds: - inc gch.idGenerator - res.id = gch.idGenerator - release(gch) - result = cellToUsr(res) - when defined(memProfiler): nimProfile(newsize-oldsize) +when not defined(gcDestructors): + proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} = + # `newObj` already uses locks, so no need for them here. + let size = addInt(mulInt(len, typ.base.size), GenericSeqSize) + result = newObj(typ, size) + cast[PGenericSeq](result).len = len + cast[PGenericSeq](result).reserved = len + when defined(memProfiler): nimProfile(size) + + proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} = + let size = addInt(mulInt(len, typ.base.size), GenericSeqSize) + result = newObj(typ, size) + cast[PGenericSeq](result).len = len + cast[PGenericSeq](result).reserved = len + when defined(memProfiler): nimProfile(size) + + proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer = + acquire(gch) + collectCT(gch, newsize + sizeof(Cell)) + var ol = usrToCell(old) + sysAssert(ol.typ != nil, "growObj: 1") + gcAssert(ol.typ.kind in {tyString, tySequence}, "growObj: 2") + + var res = cast[PCell](rawAlloc(gch.region, newsize + sizeof(Cell))) + var elemSize = 1 + if ol.typ.kind != tyString: elemSize = ol.typ.base.size + incTypeSize ol.typ, newsize + + var oldsize = cast[PGenericSeq](old).len*elemSize + GenericSeqSize + copyMem(res, ol, oldsize + sizeof(Cell)) + zeroMem(cast[pointer](cast[ByteAddress](res)+% oldsize +% sizeof(Cell)), + newsize-oldsize) + sysAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "growObj: 3") + when withBitvectors: incl(gch.allocated, res) + when useCellIds: + inc gch.idGenerator + res.id = gch.idGenerator + release(gch) + result = cellToUsr(res) + when defined(memProfiler): nimProfile(newsize-oldsize) -proc growObj(old: pointer, newsize: int): pointer {.rtl.} = - result = growObj(old, newsize, gch) + proc growObj(old: pointer, newsize: int): pointer {.rtl.} = + result = growObj(old, newsize, gch) {.push profiler:off.} diff --git a/lib/system/gc_regions.nim b/lib/system/gc_regions.nim index e9efbdfb0..06fded86b 100644 --- a/lib/system/gc_regions.nim +++ b/lib/system/gc_regions.nim @@ -70,8 +70,9 @@ type bump: pointer head, tail: Chunk nextChunkSize, totalSize: int - freeLists: array[MaxSmallObject div MemAlign, FreeEntry] - holes: SizedFreeEntry + when false: + freeLists: array[MaxSmallObject div MemAlign, FreeEntry] + holes: SizedFreeEntry when hasThreadSupport: lock: SysLock @@ -144,22 +145,24 @@ proc allocSlowPath(r: var MemRegion; size: int) = r.tail = fresh r.remaining = s - sizeof(BaseChunk) -proc alloc(r: var MemRegion; size: int): pointer = - if size <= MaxSmallObject: - var it = r.freeLists[size div MemAlign] - if it != nil: - r.freeLists[size div MemAlign] = it.next - return pointer(it) - else: - var it = r.holes - var prev: SizedFreeEntry = nil - while it != nil: - if it.size >= size: - if prev != nil: prev.next = it.next - else: r.holes = it.next +proc allocFast(r: var MemRegion; size: int): pointer = + when false: + if size <= MaxSmallObject: + var it = r.freeLists[size div MemAlign] + if it != nil: + r.freeLists[size div MemAlign] = it.next return pointer(it) - prev = it - it = it.next + else: + var it = r.holes + var prev: SizedFreeEntry = nil + while it != nil: + if it.size >= size: + if prev != nil: prev.next = it.next + else: r.holes = it.next + return pointer(it) + prev = it + it = it.next + let size = roundup(size, MemAlign) if size > r.remaining: allocSlowPath(r, size) sysAssert(size <= r.remaining, "size <= r.remaining") @@ -184,15 +187,16 @@ proc dealloc(r: var MemRegion; p: pointer; size: int) = # it is benefitial to not use the free lists here: if r.bump -! size == p: dec r.bump, size - elif size <= MaxSmallObject: - let it = cast[FreeEntry](p) - it.next = r.freeLists[size div MemAlign] - r.freeLists[size div MemAlign] = it - else: - let it = cast[SizedFreeEntry](p) - it.size = size - it.next = r.holes - r.holes = it + when false: + if size <= MaxSmallObject: + let it = cast[FreeEntry](p) + it.next = r.freeLists[size div MemAlign] + r.freeLists[size div MemAlign] = it + else: + let it = cast[SizedFreeEntry](p) + it.size = size + it.next = r.holes + r.holes = it proc deallocAll(r: var MemRegion; head: Chunk) = var it = head @@ -220,9 +224,10 @@ proc setObstackPtr*(r: var MemRegion; sp: StackPtr) = if sp.current.next != nil: deallocAll(r, sp.current.next) sp.current.next = nil - # better leak this memory than be sorry: - for i in 0..high(r.freeLists): r.freeLists[i] = nil - r.holes = nil + when false: + # better leak this memory than be sorry: + for i in 0..high(r.freeLists): r.freeLists[i] = nil + r.holes = nil #else: # deallocAll(r, r.head) # r.head = nil @@ -270,7 +275,7 @@ proc isOnHeap*(r: MemRegion; p: pointer): bool = it = it.next proc rawNewObj(r: var MemRegion, typ: PNimType, size: int): pointer = - var res = cast[ptr ObjHeader](alloc(r, size + sizeof(ObjHeader))) + var res = cast[ptr ObjHeader](allocFast(r, size + sizeof(ObjHeader))) res.typ = typ if typ.finalizer != nil: res.nextFinal = r.head.head @@ -278,17 +283,19 @@ proc rawNewObj(r: var MemRegion, typ: PNimType, size: int): pointer = result = res +! sizeof(ObjHeader) proc rawNewSeq(r: var MemRegion, typ: PNimType, size: int): pointer = - var res = cast[ptr SeqHeader](alloc(r, size + sizeof(SeqHeader))) + var res = cast[ptr SeqHeader](allocFast(r, size + sizeof(SeqHeader))) res.typ = typ res.region = addr(r) result = res +! sizeof(SeqHeader) proc newObj(typ: PNimType, size: int): pointer {.compilerRtl.} = + sysAssert typ.kind notin {tySequence, tyString}, "newObj cannot be used to construct seqs" result = rawNewObj(tlRegion, typ, size) zeroMem(result, size) when defined(memProfiler): nimProfile(size) proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl.} = + sysAssert typ.kind notin {tySequence, tyString}, "newObj cannot be used to construct seqs" result = rawNewObj(tlRegion, typ, size) when defined(memProfiler): nimProfile(size) @@ -351,6 +358,11 @@ proc alloc0(r: var MemRegion; size: Natural): pointer = # but incorrect in general. XXX result = alloc0(size) +proc alloc(r: var MemRegion; size: Natural): pointer = + # ignore the region. That is correct for the channels module + # but incorrect in general. XXX + result = alloc(size) + proc dealloc(r: var MemRegion; p: pointer) = dealloc(p) proc allocShared(size: Natural): pointer = @@ -389,4 +401,7 @@ proc getFreeMem*(r: MemRegion): int = r.remaining proc getTotalMem*(r: MemRegion): int = result = r.totalSize -proc setStackBottom(theStackBottom: pointer) = discard +proc nimGC_setStackBottom(theStackBottom: pointer) = discard + +proc nimGCref(x: pointer) {.compilerProc.} = discard +proc nimGCunref(x: pointer) {.compilerProc.} = discard diff --git a/lib/system/helpers.nim b/lib/system/helpers.nim new file mode 100644 index 000000000..fb1218684 --- /dev/null +++ b/lib/system/helpers.nim @@ -0,0 +1,11 @@ +## helpers used system.nim and other modules, avoids code duplication while +## also minimizing symbols exposed in system.nim +# +# TODO: move other things here that should not be exposed in system.nim + +proc lineInfoToString(file: string, line, column: int): string = + file & "(" & $line & ", " & $column & ")" + +proc `$`(info: type(instantiationInfo(0))): string = + # The +1 is needed here + lineInfoToString(info.fileName, info.line, info.column+1) diff --git a/lib/system/hti.nim b/lib/system/hti.nim index 45b1d1cd3..bb3769ac4 100644 --- a/lib/system/hti.nim +++ b/lib/system/hti.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -when declared(NimString): +when declared(ThisIsSystem): # we are in system module: {.pragma: codegenType, compilerproc.} else: diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 8065f2255..836ac198d 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -31,8 +31,6 @@ type JSRef = ref RootObj # Fake type. -{.deprecated: [TSafePoint: SafePoint, TCallFrame: CallFrame].} - var framePtr {.importc, nodecl, volatile.}: PCallFrame excHandler {.importc, nodecl, volatile.}: int = 0 @@ -48,10 +46,7 @@ proc nimCharToStr(x: char): string {.compilerproc.} = result[0] = x proc isNimException(): bool {.asmNoStackFrame.} = - when defined(nimphp): - asm "return isset(`lastJSError`['m_type']);" - else: - asm "return `lastJSError`.m_type;" + asm "return `lastJSError`.m_type;" proc getCurrentException*(): ref Exception {.compilerRtl, benign.} = if isNimException(): result = cast[ref Exception](lastJSError) @@ -61,15 +56,14 @@ proc getCurrentExceptionMsg*(): string = if isNimException(): return cast[Exception](lastJSError).msg else: - when not defined(nimphp): - var msg: cstring - {.emit: """ - if (`lastJSError`.message !== undefined) { - `msg` = `lastJSError`.message; - } - """.} - if not msg.isNil: - return $msg + var msg: cstring + {.emit: """ + if (`lastJSError`.message !== undefined) { + `msg` = `lastJSError`.message; + } + """.} + if not msg.isNil: + return $msg return "" proc auxWriteStackTrace(f: PCallFrame): string = @@ -114,7 +108,7 @@ proc getStackTrace*(e: ref Exception): string = e.trace proc unhandledException(e: ref Exception) {. compilerproc, asmNoStackFrame.} = var buf = "" - if e.msg != nil and e.msg[0] != '\0': + if e.msg.len != 0: add(buf, "Error: unhandled exception: ") add(buf, e.msg) else: @@ -140,12 +134,9 @@ proc raiseException(e: ref Exception, ename: cstring) {. e.name = ename if excHandler == 0: unhandledException(e) - when defined(nimphp): - asm """throw new Exception($`e`["message"]);""" - else: - when NimStackTrace: - e.trace = rawWriteStackTrace() - asm "throw `e`;" + when NimStackTrace: + e.trace = rawWriteStackTrace() + asm "throw `e`;" proc reraiseException() {.compilerproc, asmNoStackFrame.} = if lastJSError == nil: @@ -173,57 +164,33 @@ proc raiseFieldError(f: string) {.compilerproc, noreturn.} = raise newException(FieldError, f & " is not accessible") proc setConstr() {.varargs, asmNoStackFrame, compilerproc.} = - when defined(nimphp): - asm """ - $args = func_get_args(); - $result = array(); - foreach ($args as $x) { - if (is_array($x)) { - for ($j = $x[0]; $j <= $x[1]; $j++) { - $result[$j] = true; - } - } else { - $result[$x] = true; - } - } - return $result; - """ - else: - asm """ - var result = {}; - for (var i = 0; i < arguments.length; ++i) { - var x = arguments[i]; - if (typeof(x) == "object") { - for (var j = x[0]; j <= x[1]; ++j) { - result[j] = true; - } - } else { - result[x] = true; + asm """ + var result = {}; + for (var i = 0; i < arguments.length; ++i) { + var x = arguments[i]; + if (typeof(x) == "object") { + for (var j = x[0]; j <= x[1]; ++j) { + result[j] = true; } + } else { + result[x] = true; } - return result; - """ - -proc makeNimstrLit(c: cstring): string {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - {.emit: """return `c`;""".} - else: - {.emit: """ - var ln = `c`.length; - var result = new Array(ln + 1); - var i = 0; - for (; i < ln; ++i) { - result[i] = `c`.charCodeAt(i); } - result[i] = 0; // terminating zero return result; - """.} + """ + +proc makeNimstrLit(c: cstring): string {.asmNoStackFrame, compilerproc.} = + {.emit: """ + var ln = `c`.length; + var result = new Array(ln); + for (var i = 0; i < ln; ++i) { + result[i] = `c`.charCodeAt(i); + } + return result; + """.} proc cstrToNimstr(c: cstring): string {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - {.emit: """return `c`;""".} - else: - {.emit: """ + {.emit: """ var ln = `c`.length; var result = new Array(ln); var r = 0; @@ -256,183 +223,109 @@ proc cstrToNimstr(c: cstring): string {.asmNoStackFrame, compilerproc.} = } ++r; } - result[r] = 0; // terminating zero return result; """.} proc toJSStr(s: string): cstring {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - {.emit: """return `s`;""".} - else: - asm """ - var len = `s`.length-1; - var asciiPart = new Array(len); - var fcc = String.fromCharCode; - var nonAsciiPart = null; - var nonAsciiOffset = 0; - for (var i = 0; i < len; ++i) { - if (nonAsciiPart !== null) { - var offset = (i - nonAsciiOffset) * 2; - var code = `s`[i].toString(16); - if (code.length == 1) { - code = "0"+code; - } - nonAsciiPart[offset] = "%"; - nonAsciiPart[offset + 1] = code; - } - else if (`s`[i] < 128) - asciiPart[i] = fcc(`s`[i]); - else { - asciiPart.length = i; - nonAsciiOffset = i; - nonAsciiPart = new Array((len - i) * 2); - --i; + asm """ + var len = `s`.length; + var asciiPart = new Array(len); + var fcc = String.fromCharCode; + var nonAsciiPart = null; + var nonAsciiOffset = 0; + for (var i = 0; i < len; ++i) { + if (nonAsciiPart !== null) { + var offset = (i - nonAsciiOffset) * 2; + var code = `s`[i].toString(16); + if (code.length == 1) { + code = "0"+code; } + nonAsciiPart[offset] = "%"; + nonAsciiPart[offset + 1] = code; + } + else if (`s`[i] < 128) + asciiPart[i] = fcc(`s`[i]); + else { + asciiPart.length = i; + nonAsciiOffset = i; + nonAsciiPart = new Array((len - i) * 2); + --i; } - asciiPart = asciiPart.join(""); - return (nonAsciiPart === null) ? - asciiPart : asciiPart + decodeURIComponent(nonAsciiPart.join("")); + } + asciiPart = asciiPart.join(""); + return (nonAsciiPart === null) ? + asciiPart : asciiPart + decodeURIComponent(nonAsciiPart.join("")); """ proc mnewString(len: int): string {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - asm """ - return str_repeat(chr(0),`len`); - """ - else: - asm """ - var result = new Array(`len`+1); - result[0] = 0; - result[`len`] = 0; - return result; - """ - -when defined(nimphp): - proc nimAt(x: string; i: int): string {.asmNoStackFrame, compilerproc.} = - asm """ - return `x`[`i`]; - """ - -when defined(nimphp): - proc nimSubstr(s: string; a, b: int): string {. - asmNoStackFrame, compilerproc.} = - asm """return substr(`s`,`a`,`b`-`a`+1);""" + asm """ + return new Array(`len`); + """ proc SetCard(a: int): int {.compilerproc, asmNoStackFrame.} = # argument type is a fake - when defined(nimphp): - asm """ - return count(`a`); - """ - else: - asm """ - var result = 0; - for (var elem in `a`) { ++result; } - return result; - """ + asm """ + var result = 0; + for (var elem in `a`) { ++result; } + return result; + """ proc SetEq(a, b: int): bool {.compilerproc, asmNoStackFrame.} = - when defined(nimphp): - asm """ - foreach (`a` as $elem=>$_) { if (!isset(`b`[$elem])) return false; } - foreach (`b` as $elem=>$_) { if (!isset(`a`[$elem])) return false; } - return true; - """ - else: - asm """ - for (var elem in `a`) { if (!`b`[elem]) return false; } - for (var elem in `b`) { if (!`a`[elem]) return false; } - return true; - """ + asm """ + for (var elem in `a`) { if (!`b`[elem]) return false; } + for (var elem in `b`) { if (!`a`[elem]) return false; } + return true; + """ proc SetLe(a, b: int): bool {.compilerproc, asmNoStackFrame.} = - when defined(nimphp): - asm """ - foreach (`a` as $elem=>$_) { if (!isset(`b`[$elem])) return false; } - return true; - """ - else: - asm """ - for (var elem in `a`) { if (!`b`[elem]) return false; } - return true; - """ + asm """ + for (var elem in `a`) { if (!`b`[elem]) return false; } + return true; + """ proc SetLt(a, b: int): bool {.compilerproc.} = result = SetLe(a, b) and not SetEq(a, b) proc SetMul(a, b: int): int {.compilerproc, asmNoStackFrame.} = - when defined(nimphp): - asm """ - var $result = array(); - foreach (`a` as $elem=>$_) { - if (isset(`b`[$elem])) { $result[$elem] = true; } - } - return $result; - """ - else: - asm """ - var result = {}; - for (var elem in `a`) { - if (`b`[elem]) { result[elem] = true; } - } - return result; - """ + asm """ + var result = {}; + for (var elem in `a`) { + if (`b`[elem]) { result[elem] = true; } + } + return result; + """ proc SetPlus(a, b: int): int {.compilerproc, asmNoStackFrame.} = - when defined(nimphp): - asm """ - var $result = array(); - foreach (`a` as $elem=>$_) { $result[$elem] = true; } - foreach (`b` as $elem=>$_) { $result[$elem] = true; } - return $result; - """ - else: - asm """ - var result = {}; - for (var elem in `a`) { result[elem] = true; } - for (var elem in `b`) { result[elem] = true; } - return result; - """ + asm """ + var result = {}; + for (var elem in `a`) { result[elem] = true; } + for (var elem in `b`) { result[elem] = true; } + return result; + """ proc SetMinus(a, b: int): int {.compilerproc, asmNoStackFrame.} = - when defined(nimphp): - asm """ - $result = array(); - foreach (`a` as $elem=>$_) { - if (!isset(`b`[$elem])) { $result[$elem] = true; } - } - return $result; - """ - else: - asm """ - var result = {}; - for (var elem in `a`) { - if (!`b`[elem]) { result[elem] = true; } - } - return result; - """ + asm """ + var result = {}; + for (var elem in `a`) { + if (!`b`[elem]) { result[elem] = true; } + } + return result; + """ proc cmpStrings(a, b: string): int {.asmNoStackFrame, compilerProc.} = asm """ if (`a` == `b`) return 0; if (!`a`) return -1; if (!`b`) return 1; - for (var i = 0; i < `a`.length-1; ++i) { + for (var i = 0; i < `a`.length && i < `b`.length; i++) { var result = `a`[i] - `b`[i]; if (result != 0) return result; } - return 0; + return `a`.length - `b`.length; """ proc cmp(x, y: string): int = - when defined(nimphp): - asm """ - if(`x` < `y`) `result` = -1; - elseif (`x` > `y`) `result` = 1; - else `result` = 0; - """ - else: - return cmpStrings(x, y) + return cmpStrings(x, y) proc eqStrings(a, b: string): bool {.asmNoStackFrame, compilerProc.} = asm """ @@ -467,7 +360,7 @@ elif not defined(nimOldEcho): console.log(buf); """ -elif not defined(nimphp): +else: proc ewriteln(x: cstring) = var node : JSRef {.emit: "`node` = document.getElementsByTagName('body')[0];".} @@ -493,127 +386,77 @@ elif not defined(nimphp): # Arithmetic: proc addInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - asm """ - return `a` + `b`; - """ - else: - asm """ - var result = `a` + `b`; - if (result > 2147483647 || result < -2147483648) `raiseOverflow`(); - return result; - """ + asm """ + var result = `a` + `b`; + if (result > 2147483647 || result < -2147483648) `raiseOverflow`(); + return result; + """ proc subInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - asm """ - return `a` - `b`; - """ - else: - asm """ - var result = `a` - `b`; - if (result > 2147483647 || result < -2147483648) `raiseOverflow`(); - return result; - """ + asm """ + var result = `a` - `b`; + if (result > 2147483647 || result < -2147483648) `raiseOverflow`(); + return result; + """ proc mulInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - asm """ - return `a` * `b`; - """ - else: - asm """ - var result = `a` * `b`; - if (result > 2147483647 || result < -2147483648) `raiseOverflow`(); - return result; - """ + asm """ + var result = `a` * `b`; + if (result > 2147483647 || result < -2147483648) `raiseOverflow`(); + return result; + """ proc divInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - asm """ - return trunc(`a` / `b`); - """ - else: - asm """ - if (`b` == 0) `raiseDivByZero`(); - if (`b` == -1 && `a` == 2147483647) `raiseOverflow`(); - return Math.trunc(`a` / `b`); - """ + asm """ + if (`b` == 0) `raiseDivByZero`(); + if (`b` == -1 && `a` == 2147483647) `raiseOverflow`(); + return Math.trunc(`a` / `b`); + """ proc modInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - asm """ - return `a` % `b`; - """ - else: - asm """ - if (`b` == 0) `raiseDivByZero`(); - if (`b` == -1 && `a` == 2147483647) `raiseOverflow`(); - return Math.trunc(`a` % `b`); - """ + asm """ + if (`b` == 0) `raiseDivByZero`(); + if (`b` == -1 && `a` == 2147483647) `raiseOverflow`(); + return Math.trunc(`a` % `b`); + """ proc addInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - asm """ - return `a` + `b`; - """ - else: - asm """ - var result = `a` + `b`; - if (result > 9223372036854775807 - || result < -9223372036854775808) `raiseOverflow`(); - return result; - """ + asm """ + var result = `a` + `b`; + if (result > 9223372036854775807 + || result < -9223372036854775808) `raiseOverflow`(); + return result; + """ proc subInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - asm """ - return `a` - `b`; - """ - else: - asm """ - var result = `a` - `b`; - if (result > 9223372036854775807 - || result < -9223372036854775808) `raiseOverflow`(); - return result; - """ + asm """ + var result = `a` - `b`; + if (result > 9223372036854775807 + || result < -9223372036854775808) `raiseOverflow`(); + return result; + """ proc mulInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - asm """ - return `a` * `b`; - """ - else: - asm """ - var result = `a` * `b`; - if (result > 9223372036854775807 - || result < -9223372036854775808) `raiseOverflow`(); - return result; - """ + asm """ + var result = `a` * `b`; + if (result > 9223372036854775807 + || result < -9223372036854775808) `raiseOverflow`(); + return result; + """ proc divInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - asm """ - return trunc(`a` / `b`); - """ - else: - asm """ - if (`b` == 0) `raiseDivByZero`(); - if (`b` == -1 && `a` == 9223372036854775807) `raiseOverflow`(); - return Math.trunc(`a` / `b`); - """ + asm """ + if (`b` == 0) `raiseDivByZero`(); + if (`b` == -1 && `a` == 9223372036854775807) `raiseOverflow`(); + return Math.trunc(`a` / `b`); + """ proc modInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = - when defined(nimphp): - asm """ - return `a` % `b`; - """ - else: - asm """ - if (`b` == 0) `raiseDivByZero`(); - if (`b` == -1 && `a` == 9223372036854775807) `raiseOverflow`(); - return Math.trunc(`a` % `b`); - """ + asm """ + if (`b` == 0) `raiseDivByZero`(); + if (`b` == -1 && `a` == 9223372036854775807) `raiseOverflow`(); + return Math.trunc(`a` % `b`); + """ proc negInt(a: int): int {.compilerproc.} = result = a*(-1) @@ -655,7 +498,6 @@ proc chckNilDisp(p: pointer) {.compilerproc.} = if p == nil: sysFatal(NilAccessError, "cannot dispatch; dispatcher is nil") -type NimString = string # hack for hti.nim include "system/hti" proc isFatPointer(ti: PNimType): bool = @@ -767,24 +609,14 @@ proc genericReset(x: JSRef, ti: PNimType): JSRef {.compilerproc.} = else: discard -when defined(nimphp): - proc arrayConstr(len: int, value: string, typ: string): JSRef {. - asmNoStackFrame, compilerproc.} = - # types are fake - asm """ - $result = array(); - for ($i = 0; $i < `len`; $i++) $result[] = `value`; - return $result; - """ -else: - proc arrayConstr(len: int, value: JSRef, typ: PNimType): JSRef {. - asmNoStackFrame, compilerproc.} = +proc arrayConstr(len: int, value: JSRef, typ: PNimType): JSRef {. + asmNoStackFrame, compilerproc.} = # types are fake - asm """ - var result = new Array(`len`); - for (var i = 0; i < `len`; ++i) result[i] = nimCopy(null, `value`, `typ`); - return result; - """ + asm """ + var result = new Array(`len`); + for (var i = 0; i < `len`; ++i) result[i] = nimCopy(null, `value`, `typ`); + return result; + """ proc chckIndx(i, a, b: int): int {.compilerproc.} = if i >= a and i <= b: return i @@ -813,9 +645,7 @@ proc isObj(obj, subclass: PNimType): bool {.compilerproc.} = return true proc addChar(x: string, c: char) {.compilerproc, asmNoStackFrame.} = - asm """ - `x`[`x`.length-1] = `c`; `x`.push(0); - """ + asm "`x`.push(`c`);" {.pop.} @@ -909,3 +739,16 @@ when defined(nodejs): else: # Deprecated. Use `alert` defined in dom.nim proc alert*(s: cstring) {.importc, nodecl, deprecated.} + +# Workaround for IE, IE up to version 11 lacks 'Math.trunc'. We produce +# 'Math.trunc' for Nim's ``div`` and ``mod`` operators: +when not defined(nodejs): + {.emit: """ + if (!Math.trunc) { + Math.trunc = function(v) { + v = +v; + if (!isFinite(v)) return v; + + return (v - v % 1) || (v < 0 ? -0 : v === 0 ? v : 0); + }; + }""".} diff --git a/lib/system/memory.nim b/lib/system/memory.nim new file mode 100644 index 000000000..f86fd4696 --- /dev/null +++ b/lib/system/memory.nim @@ -0,0 +1,47 @@ +const useLibC = not defined(nimNoLibc) + +proc nimCopyMem(dest, source: pointer, size: Natural) {.compilerproc, inline.} = + when useLibC: + c_memcpy(dest, source, size) + else: + let d = cast[ptr UncheckedArray[byte]](dest) + let s = cast[ptr UncheckedArray[byte]](source) + var i = 0 + while i < size: + d[i] = s[i] + inc i + +proc nimSetMem(a: pointer, v: cint, size: Natural) {.inline.} = + when useLibC: + c_memset(a, v, size) + else: + let a = cast[ptr UncheckedArray[byte]](a) + var i = 0 + let v = cast[byte](v) + while i < size: + a[i] = v + inc i + +proc nimZeroMem(p: pointer, size: Natural) {.compilerproc, inline.} = + nimSetMem(p, 0, size) + +proc nimCmpMem(a, b: pointer, size: Natural): cint {.compilerproc, inline.} = + when useLibC: + c_memcmp(a, b, size) + else: + let a = cast[ptr UncheckedArray[byte]](a) + let b = cast[ptr UncheckedArray[byte]](b) + var i = 0 + while i < size: + let d = a[i].cint - b[i].cint + if d != 0: return d + inc i + +proc nimCStrLen(a: cstring): csize {.compilerproc, inline.} = + when useLibC: + c_strlen(a) + else: + var a = cast[ptr byte](a) + while a[] != 0: + a = cast[ptr byte](cast[uint](a) + 1) + inc result diff --git a/lib/system/memtracker.nim b/lib/system/memtracker.nim index ae0297438..1b1f18039 100644 --- a/lib/system/memtracker.nim +++ b/lib/system/memtracker.nim @@ -73,12 +73,12 @@ proc addEntry(entry: LogEntry) = let x = cast[proc() {.nimcall, tags: [], gcsafe, locks: 0.}](writeStackTrace) x() quit 1 - if gLog.count > high(gLog.data): - gLogger(gLog) - gLog.count = 0 - gLog.data[gLog.count] = entry - inc gLog.count - gLog.disabled = false + #if gLog.count > high(gLog.data): + # gLogger(gLog) + # gLog.count = 0 + #gLog.data[gLog.count] = entry + #inc gLog.count + #gLog.disabled = false proc memTrackerWrite(address: pointer; size: int; file: cstring; line: int) {.compilerProc.} = addEntry LogEntry(op: "write", address: address, diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index 45e0c74c0..e7e14b948 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -146,7 +146,7 @@ when defined(boehmgc): proc getFreeMem(): int = return boehmGetFreeBytes() proc getTotalMem(): int = return boehmGetHeapSize() - proc setStackBottom(theStackBottom: pointer) = discard + proc nimGC_setStackBottom(theStackBottom: pointer) = discard proc initGC() = boehmGCinit() @@ -175,8 +175,7 @@ when defined(boehmgc): dest[] = src type - MemRegion = object {.final, pure.} - {.deprecated: [TMemRegion: MemRegion].} + MemRegion = object proc alloc(r: var MemRegion, size: int): pointer = result = boehmAlloc(size) @@ -215,60 +214,58 @@ elif defined(gogc): goNumSizeClasses = 67 type - cbool {.importc: "_Bool", nodecl.} = bool - goMStats_inner_struct = object - size: uint32 - nmalloc: uint64 - nfree: uint64 + size: uint32 + nmalloc: uint64 + nfree: uint64 goMStats = object - # General statistics. - alloc: uint64 # bytes allocated and still in use - total_alloc: uint64 # bytes allocated (even if freed) - sys: uint64 # bytes obtained from system (should be sum of xxx_sys below, no locking, approximate) - nlookup: uint64 # number of pointer lookups - nmalloc: uint64 # number of mallocs - nfree: uint64 # number of frees - # Statistics about malloc heap. - # protected by mheap.Lock - heap_alloc: uint64 # bytes allocated and still in use - heap_sys: uint64 # bytes obtained from system - heap_idle: uint64 # bytes in idle spans - heap_inuse: uint64 # bytes in non-idle spans - heap_released: uint64 # bytes released to the OS - heap_objects: uint64 # total number of allocated objects - # Statistics about allocation of low-level fixed-size structures. - # Protected by FixAlloc locks. - stacks_inuse: uint64 # bootstrap stacks - stacks_sys: uint64 - mspan_inuse: uint64 # MSpan structures - mspan_sys: uint64 - mcache_inuse: uint64 # MCache structures - mcache_sys: uint64 - buckhash_sys: uint64 # profiling bucket hash table - gc_sys: uint64 - other_sys: uint64 - # Statistics about garbage collector. - # Protected by mheap or stopping the world during GC. - next_gc: uint64 # next GC (in heap_alloc time) - last_gc: uint64 # last GC (in absolute time) - pause_total_ns: uint64 - pause_ns: array[256, uint64] # circular buffer of recent gc pause lengths - pause_end: array[256, uint64] # circular buffer of recent gc end times (nanoseconds since 1970) - numgc: uint32 - numforcedgc: uint32 # number of user-forced GCs - gc_cpu_fraction: float64 # fraction of CPU time used by GC - enablegc: cbool - debuggc: cbool - # Statistics about allocation size classes. - by_size: array[goNumSizeClasses, goMStats_inner_struct] - # Statistics below here are not exported to MemStats directly. - tinyallocs: uint64 # number of tiny allocations that didn't cause actual allocation; not exported to go directly - gc_trigger: uint64 - heap_live: uint64 - heap_scan: uint64 - heap_marked: uint64 + # General statistics. + alloc: uint64 # bytes allocated and still in use + total_alloc: uint64 # bytes allocated (even if freed) + sys: uint64 # bytes obtained from system (should be sum of xxx_sys below, no locking, approximate) + nlookup: uint64 # number of pointer lookups + nmalloc: uint64 # number of mallocs + nfree: uint64 # number of frees + # Statistics about malloc heap. + # protected by mheap.Lock + heap_alloc: uint64 # bytes allocated and still in use + heap_sys: uint64 # bytes obtained from system + heap_idle: uint64 # bytes in idle spans + heap_inuse: uint64 # bytes in non-idle spans + heap_released: uint64 # bytes released to the OS + heap_objects: uint64 # total number of allocated objects + # Statistics about allocation of low-level fixed-size structures. + # Protected by FixAlloc locks. + stacks_inuse: uint64 # bootstrap stacks + stacks_sys: uint64 + mspan_inuse: uint64 # MSpan structures + mspan_sys: uint64 + mcache_inuse: uint64 # MCache structures + mcache_sys: uint64 + buckhash_sys: uint64 # profiling bucket hash table + gc_sys: uint64 + other_sys: uint64 + # Statistics about garbage collector. + # Protected by mheap or stopping the world during GC. + next_gc: uint64 # next GC (in heap_alloc time) + last_gc: uint64 # last GC (in absolute time) + pause_total_ns: uint64 + pause_ns: array[256, uint64] # circular buffer of recent gc pause lengths + pause_end: array[256, uint64] # circular buffer of recent gc end times (nanoseconds since 1970) + numgc: uint32 + numforcedgc: uint32 # number of user-forced GCs + gc_cpu_fraction: float64 # fraction of CPU time used by GC + enablegc: bool + debuggc: bool + # Statistics about allocation size classes. + by_size: array[goNumSizeClasses, goMStats_inner_struct] + # Statistics below here are not exported to MemStats directly. + tinyallocs: uint64 # number of tiny allocations that didn't cause actual allocation; not exported to go directly + gc_trigger: uint64 + heap_live: uint64 + heap_scan: uint64 + heap_marked: uint64 proc goRuntime_ReadMemStats(a2: ptr goMStats) {.cdecl, importc: "runtime_ReadMemStats", @@ -305,7 +302,7 @@ elif defined(gogc): goRuntime_ReadMemStats(addr mstats) result = int(mstats.sys) - proc setStackBottom(theStackBottom: pointer) = discard + proc nimGC_setStackBottom(theStackBottom: pointer) = discard proc alloc(size: Natural): pointer = result = c_malloc(size) @@ -341,9 +338,12 @@ elif defined(gogc): proc getOccupiedSharedMem(): int = discard const goFlagNoZero: uint32 = 1 shl 3 - proc goRuntimeMallocGC(size: uint, typ: uint, flag: uint32): pointer {.importc: "runtime_mallocgc", dynlib: goLib.} + proc goRuntimeMallocGC(size: uint, typ: uint, flag: uint32): pointer {. + importc: "runtime_mallocgc", dynlib: goLib.} - proc goSetFinalizer(obj: pointer, f: pointer) {.importc: "set_finalizer", codegenDecl:"$1 $2$3 __asm__ (\"main.Set_finalizer\");\n$1 $2$3", dynlib: goLib.} + proc goSetFinalizer(obj: pointer, f: pointer) {. + importc: "set_finalizer", codegenDecl: "$1 $2$3 __asm__ (\"main.Set_finalizer\");\n$1 $2$3", + dynlib: goLib.} proc newObj(typ: PNimType, size: int): pointer {.compilerproc.} = result = goRuntimeMallocGC(roundup(size, sizeof(pointer)).uint, 0.uint, 0.uint32) @@ -369,8 +369,7 @@ elif defined(gogc): proc growObj(old: pointer, newsize: int): pointer = # the Go GC doesn't have a realloc - var - oldsize = cast[PGenericSeq](old).len * cast[PGenericSeq](old).elemSize + GenericSeqSize + let oldsize = cast[PGenericSeq](old).len * cast[PGenericSeq](old).elemSize + GenericSeqSize result = goRuntimeMallocGC(roundup(newsize, sizeof(pointer)).uint, 0.uint, goFlagNoZero) copyMem(result, old, oldsize) zeroMem(cast[pointer](cast[ByteAddress](result) +% oldsize), newsize - oldsize) @@ -386,8 +385,7 @@ elif defined(gogc): dest[] = src type - MemRegion = object {.final, pure.} - {.deprecated: [TMemRegion: MemRegion].} + MemRegion = object proc alloc(r: var MemRegion, size: int): pointer = result = alloc(size) @@ -449,7 +447,7 @@ elif defined(nogc) and defined(useMalloc): proc getFreeMem(): int = discard proc getTotalMem(): int = discard - proc setStackBottom(theStackBottom: pointer) = discard + proc nimGC_setStackBottom(theStackBottom: pointer) = discard proc initGC() = discard @@ -477,8 +475,7 @@ elif defined(nogc) and defined(useMalloc): dest[] = src type - MemRegion = object {.final, pure.} - {.deprecated: [TMemRegion: MemRegion].} + MemRegion = object proc alloc(r: var MemRegion, size: int): pointer = result = alloc(size) @@ -523,7 +520,7 @@ elif defined(nogc): proc growObj(old: pointer, newsize: int): pointer = result = realloc(old, newsize) - proc setStackBottom(theStackBottom: pointer) = discard + proc nimGC_setStackBottom(theStackBottom: pointer) = discard proc nimGCref(p: pointer) {.compilerproc, inline.} = discard proc nimGCunref(p: pointer) {.compilerproc, inline.} = discard @@ -551,23 +548,25 @@ else: elif defined(gcRegions): # XXX due to bootstrapping reasons, we cannot use compileOption("gc", "stack") here include "system/gc_regions" - elif defined(gcMarkAndSweep): + elif defined(gcMarkAndSweep) or defined(gcDestructors): # XXX use 'compileOption' here include "system/gc_ms" - elif defined(gcGenerational): - include "system/gc" else: include "system/gc" -when not declared(nimNewSeqOfCap): +when not declared(nimNewSeqOfCap) and not defined(gcDestructors): proc nimNewSeqOfCap(typ: PNimType, cap: int): pointer {.compilerproc.} = - let s = addInt(mulInt(cap, typ.base.size), GenericSeqSize) - when declared(newObjNoInit): - result = if ntfNoRefs in typ.base.flags: newObjNoInit(typ, s) else: newObj(typ, s) + when defined(gcRegions): + let s = mulInt(cap, typ.base.size) # newStr already adds GenericSeqSize + result = newStr(typ, s, ntfNoRefs notin typ.base.flags) else: - result = newObj(typ, s) - cast[PGenericSeq](result).len = 0 - cast[PGenericSeq](result).reserved = cap + let s = addInt(mulInt(cap, typ.base.size), GenericSeqSize) + when declared(newObjNoInit): + result = if ntfNoRefs in typ.base.flags: newObjNoInit(typ, s) else: newObj(typ, s) + else: + result = newObj(typ, s) + cast[PGenericSeq](result).len = 0 + cast[PGenericSeq](result).reserved = cap {.pop.} diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index f91dae41e..64d6255da 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -38,13 +38,19 @@ proc removeFile(dir: string) {. tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin proc moveFile(src, dest: string) {. tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin +proc moveDir(src, dest: string) {. + tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin proc copyFile(src, dest: string) {. tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin +proc copyDir(src, dest: string) {. + tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin proc createDir(dir: string) {.tags: [WriteIOEffect], raises: [OSError].} = builtin proc getOsError: string = builtin proc setCurrentDir(dir: string) = builtin -proc getCurrentDir(): string = builtin +proc getCurrentDir*(): string = + ## Retrieves the current working directory. + builtin proc rawExec(cmd: string): int {.tags: [ExecIOEffect], raises: [OSError].} = builtin @@ -67,12 +73,12 @@ proc switch*(key: string, val="") = proc warning*(name: string; val: bool) = ## Disables or enables a specific warning. let v = if val: "on" else: "off" - warningImpl(name & "]:" & v, "warning[" & name & "]:" & v) + warningImpl(name & ":" & v, "warning:" & name & ":" & v) proc hint*(name: string; val: bool) = ## Disables or enables a specific hint. let v = if val: "on" else: "off" - hintImpl(name & "]:" & v, "hint[" & name & "]:" & v) + hintImpl(name & ":" & v, "hint:" & name & ":" & v) proc patchFile*(package, filename, replacement: string) = ## Overrides the location of a given file belonging to the @@ -114,6 +120,10 @@ proc existsEnv*(key: string): bool {.tags: [ReadIOEffect].} = ## Checks for the existence of an environment variable named `key`. builtin +proc putEnv*(key, val: string) {.tags: [WriteIOEffect].} = + ## Sets the value of the environment variable named key to val. + builtin + proc fileExists*(filename: string): bool {.tags: [ReadIOEffect].} = ## Checks if the file exists. builtin @@ -172,7 +182,7 @@ template checkOsError = if err.len > 0: raise newException(OSError, err) template log(msg: string, body: untyped) = - if mode == ScriptMode.Verbose or mode == ScriptMode.Whatif: + if mode in {ScriptMode.Verbose, ScriptMode.Whatif}: echo "[NimScript] ", msg if mode != ScriptMode.WhatIf: body @@ -202,12 +212,24 @@ proc mvFile*(`from`, to: string) {.raises: [OSError].} = moveFile `from`, to checkOsError() +proc mvDir*(`from`, to: string) {.raises: [OSError].} = + ## Moves the dir `from` to `to`. + log "mvDir: " & `from` & ", " & to: + moveDir `from`, to + checkOsError() + proc cpFile*(`from`, to: string) {.raises: [OSError].} = ## Copies the file `from` to `to`. log "cpFile: " & `from` & ", " & to: copyFile `from`, to checkOsError() +proc cpDir*(`from`, to: string) {.raises: [OSError].} = + ## Copies the dir `from` to `to`. + log "cpDir: " & `from` & ", " & to: + copyDir `from`, to + checkOsError() + proc exec*(command: string) = ## Executes an external process. log "exec: " & command: @@ -261,6 +283,12 @@ proc cd*(dir: string) {.raises: [OSError].} = setCurrentDir(dir) checkOsError() +proc findExe*(bin: string): string = + ## Searches for bin in the current working directory and then in directories + ## listed in the PATH environment variable. Returns "" if the exe cannot be + ## found. + builtin + template withDir*(dir: string; body: untyped): untyped = ## Changes the current directory temporarily. ## @@ -277,7 +305,6 @@ template withDir*(dir: string; body: untyped): untyped = finally: cd(curDir) -template `==?`(a, b: string): bool = cmpIgnoreStyle(a, b) == 0 proc writeTask(name, desc: string) = if desc.len > 0: @@ -285,29 +312,30 @@ proc writeTask(name, desc: string) = for i in 0 ..< 20 - name.len: spaces.add ' ' echo name, spaces, desc -template task*(name: untyped; description: string; body: untyped): untyped = - ## Defines a task. Hidden tasks are supported via an empty description. - ## Example: - ## - ## .. code-block:: nim - ## task build, "default build is via the C backend": - ## setCommand "c" - proc `name Task`*() = body - - let cmd = getCommand() - if cmd.len == 0 or cmd ==? "help": - setCommand "help" - writeTask(astToStr(name), description) - elif cmd ==? astToStr(name): - setCommand "nop" - `name Task`() - proc cppDefine*(define: string) = ## tell Nim that ``define`` is a C preprocessor ``#define`` and so always ## needs to be mangled. builtin when not defined(nimble): + template `==?`(a, b: string): bool = cmpIgnoreStyle(a, b) == 0 + template task*(name: untyped; description: string; body: untyped): untyped = + ## Defines a task. Hidden tasks are supported via an empty description. + ## Example: + ## + ## .. code-block:: nim + ## task build, "default build is via the C backend": + ## setCommand "c" + proc `name Task`*() = body + + let cmd = getCommand() + if cmd.len == 0 or cmd ==? "help": + setCommand "help" + writeTask(astToStr(name), description) + elif cmd ==? astToStr(name): + setCommand "nop" + `name Task`() + # nimble has its own implementation for these things. var packageName* = "" ## Nimble support: Set this to the package name. It diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim index 1ad4cf695..06e89f130 100644 --- a/lib/system/osalloc.nim +++ b/lib/system/osalloc.nim @@ -58,7 +58,7 @@ when defined(emscripten): # Convert pointer to PageSize correct one. var new_pos = cast[ByteAddress](pos) +% (PageSize - (pos %% PageSize)) - if (new_pos-pos)< sizeof(EmscriptenMMapBlock): + if (new_pos-pos) < sizeof(EmscriptenMMapBlock): new_pos = new_pos +% PageSize result = cast[pointer](new_pos) @@ -78,7 +78,118 @@ when defined(emscripten): munmap(mmapDescr.realPointer, mmapDescr.realSize) elif defined(genode): - include genodealloc # osAllocPages, osTryAllocPages, osDeallocPages + include genode/alloc # osAllocPages, osTryAllocPages, osDeallocPages + +elif defined(nintendoswitch): + + import nintendoswitch/switch_memory + + type + PSwitchBlock = ptr NSwitchBlock + ## This will hold the heap pointer data in a separate + ## block of memory that is PageSize bytes above + ## the requested memory. It's the only good way + ## to pass around data with heap allocations + NSwitchBlock {.pure, inheritable.} = object + realSize: int + heap: pointer # pointer to main heap alloc + heapMirror: pointer # pointer to virtmem mapped heap + + proc alignSize(size: int): int {.inline.} = + ## Align a size integer to be in multiples of PageSize + ## The nintendo switch will not allocate memory that is not + ## aligned to 0x1000 bytes and will just crash. + (size + (PageSize - 1)) and not (PageSize - 1) + + proc deallocate(heapMirror: pointer, heap: pointer, size: int) = + # Unmap the allocated memory + discard svcUnmapMemory(heapMirror, heap, size.uint64) + # These should be called (theoretically), but referencing them crashes the switch. + # The above call seems to free all heap memory, so these are not needed. + # virtmemFreeMap(nswitchBlock.heapMirror, nswitchBlock.realSize.csize) + # free(nswitchBlock.heap) + + proc freeMem(p: pointer) = + # Retrieve the switch block data from the pointer we set before + # The data is located just sizeof(NSwitchBlock) bytes below + # the top of the pointer to the heap + let + nswitchDescrPos = cast[ByteAddress](p) -% sizeof(NSwitchBlock) + nswitchBlock = cast[PSwitchBlock](nswitchDescrPos) + + deallocate( + nswitchBlock.heapMirror, nswitchBlock.heap, nswitchBlock.realSize + ) + + proc storeHeapData(address, heapMirror, heap: pointer, size: int) {.inline.} = + ## Store data in the heap for deallocation purposes later + + # the position of our heap pointer data. Since we allocated PageSize extra + # bytes, we should have a buffer on top of the requested size of at least + # PageSize bytes, which is much larger than sizeof(NSwitchBlock). So we + # decrement the address by sizeof(NSwitchBlock) and use that address + # to store our pointer data + let nswitchBlockPos = cast[ByteAddress](address) -% sizeof(NSwitchBlock) + + # We need to store this in a pointer obj (PSwitchBlock) so that the data sticks + # at the address we've chosen. If NSwitchBlock is used here, the data will + # be all 0 when we try to retrieve it later. + var nswitchBlock = cast[PSwitchBlock](nswitchBlockPos) + nswitchBlock.realSize = size + nswitchBlock.heap = heap + nswitchBlock.heapMirror = heapMirror + + proc getOriginalHeapPosition(address: pointer, difference: int): pointer {.inline.} = + ## This function sets the heap back to the originally requested + ## size + let + pos = cast[int](address) + newPos = cast[ByteAddress](pos) +% difference + + return cast[pointer](newPos) + + template allocPages(size: int, outOfMemoryStmt: untyped): untyped = + # This is to ensure we get a block of memory the requested + # size, as well as space to store our structure + let realSize = alignSize(size + sizeof(NSwitchBlock)) + + let heap = memalign(PageSize, realSize) + + if heap.isNil: + outOfMemoryStmt + + let heapMirror = virtmemReserveMap(realSize.csize) + result = heapMirror + + let rc = svcMapMemory(heapMirror, heap, realSize.uint64) + # Any return code not equal 0 means an error in libnx + if rc.uint32 != 0: + deallocate(heapMirror, heap, realSize) + outOfMemoryStmt + + # set result to be the original size requirement + result = getOriginalHeapPosition(result, realSize - size) + + storeHeapData(result, heapMirror, heap, realSize) + + proc osAllocPages(size: int): pointer {.inline.} = + allocPages(size): + raiseOutOfMem() + + proc osTryAllocPages(size: int): pointer = + allocPages(size): + return nil + + proc osDeallocPages(p: pointer, size: int) = + # Note that in order for the Switch not to crash, a call to + # deallocHeap(runFinalizers = true, allowGcAfterwards = false) + # must be run before gfxExit(). The Switch requires all memory + # to be deallocated before the graphics application has exited. + # + # gfxExit() can be found in <switch/gfx/gfx.h> in the github + # repo https://github.com/switchbrew/libnx + when reallyOsDealloc: + freeMem(p) elif defined(posix): const @@ -96,6 +207,9 @@ elif defined(posix): # some arches like mips and alpha use different values const MAP_ANONYMOUS = 0x20 const MAP_PRIVATE = 0x02 # Changes are private + elif defined(haiku): + const MAP_ANONYMOUS = 0x08 + const MAP_PRIVATE = 0x02 else: var MAP_ANONYMOUS {.importc: "MAP_ANONYMOUS", header: "<sys/mman.h>".}: cint diff --git a/lib/system/platforms.nim b/lib/system/platforms.nim index 8939615cd..31d2cb01e 100644 --- a/lib/system/platforms.nim +++ b/lib/system/platforms.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -## Platform detection for Nim. This module is included by the system module! +## Platform detection for NimScript. This module is included by the system module! ## Do not import it directly! type @@ -29,14 +29,15 @@ type arm, ## ARM based processor arm64, ## ARM64 based processor vm, ## Some Virtual machine: Nim's VM or JavaScript - avr ## AVR based processor - msp430 ## TI MSP430 microcontroller + avr, ## AVR based processor + msp430, ## TI MSP430 microcontroller + riscv64 ## RISC-V 64-bit processor OsPlatform* {.pure.} = enum ## the OS this program will run on. none, dos, windows, os2, linux, morphos, skyos, solaris, irix, netbsd, freebsd, openbsd, aix, palmos, qnx, amiga, atari, netware, macos, macosx, haiku, android, js, nimVM, - standalone + standalone, nintendoswitch const targetOS* = when defined(windows): OsPlatform.windows @@ -61,8 +62,9 @@ const elif defined(haiku): OsPlatform.haiku elif defined(android): OsPlatform.android elif defined(js): OsPlatform.js - elif defined(nimrodVM): OsPlatform.nimVM + elif defined(nimVM): OsPlatform.nimVM elif defined(standalone): OsPlatform.standalone + elif defined(nintendoswitch): OsPlatform.nintendoswitch else: OsPlatform.none ## the OS this program will run on. @@ -84,5 +86,6 @@ const elif defined(vm): CpuPlatform.vm elif defined(avr): CpuPlatform.avr elif defined(msp430): CpuPlatform.msp430 + elif defined(riscv64): CpuPlatform.riscv64 else: CpuPlatform.none ## the CPU this program will run on. diff --git a/lib/system/repr.nim b/lib/system/repr.nim index 982b07467..85701c28f 100644 --- a/lib/system/repr.nim +++ b/lib/system/repr.nim @@ -25,29 +25,13 @@ proc reprPointer(x: pointer): string {.compilerproc.} = discard c_sprintf(buf, "%p", x) return $buf -proc `$`(x: uint64): string = - if x == 0: - result = "0" - else: - result = newString(60) - var i = 0 - var n = x - while n != 0: - let nn = n div 10'u64 - result[i] = char(n - 10'u64 * nn + ord('0')) - inc i - n = nn - result.setLen i - - let half = i div 2 - # Reverse - for t in 0 .. half-1: swap(result[t], result[i-t-1]) - proc reprStrAux(result: var string, s: cstring; len: int) = if cast[pointer](s) == nil: add result, "nil" return - add result, reprPointer(cast[pointer](s)) & "\"" + if len > 0: + add result, reprPointer(cast[pointer](s)) + add result, "\"" for i in 0 .. pred(len): let c = s[i] case c @@ -180,9 +164,10 @@ when not defined(useNimRtl): proc reprSequence(result: var string, p: pointer, typ: PNimType, cl: var ReprClosure) = if p == nil: - add result, "nil" + add result, "[]" return - result.add(reprPointer(p) & "[") + result.add(reprPointer(p)) + result.add '[' var bs = typ.base.size for i in 0..cast[PGenericSeq](p).len-1: if i > 0: add result, ", " @@ -284,7 +269,7 @@ when not defined(useNimRtl): of tyChar: add result, reprChar(cast[ptr char](p)[]) of tyString: let sp = cast[ptr string](p) - reprStrAux(result, if sp[].isNil: nil else: sp[].cstring, sp[].len) + reprStrAux(result, sp[].cstring, sp[].len) of tyCString: let cs = cast[ptr cstring](p)[] if cs.isNil: add result, "nil" diff --git a/lib/system/reprjs.nim b/lib/system/reprjs.nim index 304ce4310..7cb25a252 100644 --- a/lib/system/reprjs.nim +++ b/lib/system/reprjs.nim @@ -55,11 +55,11 @@ proc reprStrAux(result: var string, s: cstring, len: int) = case c of '"': add(result, "\\\"") of '\\': add(result, "\\\\") - of '\10': add(result, "\\10\"\n\"") - of '\127'..'\255', '\0'..'\9', '\11'..'\31': - add( result, "\\" & reprInt(ord(c)) ) + #of '\10': add(result, "\\10\"\n\"") + of '\127'..'\255', '\0'..'\31': + add(result, "\\" & reprInt(ord(c))) else: - add( result, reprInt(ord(c)) ) # Not sure about this. + add(result, c) add(result, "\"") proc reprStr(s: string): string {.compilerRtl.} = @@ -232,10 +232,7 @@ proc reprAux(result: var string, p: pointer, typ: PNimType, of tyString: var fp: int {. emit: "`fp` = `p`;\n" .} - if cast[string](fp).isNil: - add(result, "nil") - else: - add( result, reprStr(cast[string](p)) ) + add( result, reprStr(cast[string](p)) ) of tyCString: var fp: cstring {. emit: "`fp` = `p`;\n" .} diff --git a/lib/system/strmantle.nim b/lib/system/strmantle.nim new file mode 100644 index 000000000..ceaecb4f9 --- /dev/null +++ b/lib/system/strmantle.nim @@ -0,0 +1,298 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Compilerprocs for strings that do not depend on the string implementation. + +proc cmpStrings(a, b: string): int {.inline, compilerProc.} = + let alen = a.len + let blen = b.len + let minlen = min(alen, blen) + if minlen > 0: + result = c_memcmp(unsafeAddr a[0], unsafeAddr b[0], minlen.csize) + if result == 0: + result = alen - blen + else: + result = alen - blen + +proc eqStrings(a, b: string): bool {.inline, compilerProc.} = + let alen = a.len + let blen = b.len + if alen == blen: + if alen == 0: return true + return equalMem(unsafeAddr(a[0]), unsafeAddr(b[0]), alen) + +proc hashString(s: string): int {.compilerproc.} = + # the compiler needs exactly the same hash function! + # this used to be used for efficient generation of string case statements + var h = 0 + for i in 0..len(s)-1: + h = h +% ord(s[i]) + h = h +% h shl 10 + h = h xor (h shr 6) + h = h +% h shl 3 + h = h xor (h shr 11) + h = h +% h shl 15 + result = h + +proc add*(result: var string; x: int64) = + let base = result.len + setLen(result, base + sizeof(x)*4) + var i = 0 + var y = x + while true: + var d = y div 10 + result[base+i] = chr(abs(int(y - d*10)) + ord('0')) + inc(i) + y = d + if y == 0: break + if x < 0: + result[base+i] = '-' + inc(i) + setLen(result, base+i) + # mirror the string: + for j in 0..i div 2 - 1: + swap(result[base+j], result[base+i-j-1]) + +proc nimIntToStr(x: int): string {.compilerRtl.} = + result = newStringOfCap(sizeof(x)*4) + result.add x + +proc add*(result: var string; x: float) = + when nimvm: + result.add $x + else: + var buf: array[0..64, char] + when defined(nimNoArrayToCstringConversion): + var n: int = c_sprintf(addr buf, "%.16g", x) + else: + var n: int = c_sprintf(buf, "%.16g", x) + var hasDot = false + for i in 0..n-1: + if buf[i] == ',': + buf[i] = '.' + hasDot = true + elif buf[i] in {'a'..'z', 'A'..'Z', '.'}: + hasDot = true + if not hasDot: + buf[n] = '.' + buf[n+1] = '0' + buf[n+2] = '\0' + # On Windows nice numbers like '1.#INF', '-1.#INF' or '1.#NAN' + # of '-1.#IND' are produced. + # We want to get rid of these here: + if buf[n-1] in {'n', 'N', 'D', 'd'}: + result.add "nan" + elif buf[n-1] == 'F': + if buf[0] == '-': + result.add "-inf" + else: + result.add "inf" + else: + var i = 0 + while buf[i] != '\0': + result.add buf[i] + inc i + +proc nimFloatToStr(f: float): string {.compilerproc.} = + result = newStringOfCap(8) + result.add f + +proc c_strtod(buf: cstring, endptr: ptr cstring): float64 {. + importc: "strtod", header: "<stdlib.h>", noSideEffect.} + +const + IdentChars = {'a'..'z', 'A'..'Z', '0'..'9', '_'} + powtens = [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22] + +proc nimParseBiggestFloat(s: string, number: var BiggestFloat, + start = 0): int {.compilerProc.} = + # This routine attempt to parse float that can parsed quickly. + # ie whose integer part can fit inside a 53bits integer. + # their real exponent must also be <= 22. If the float doesn't follow + # these restrictions, transform the float into this form: + # INTEGER * 10 ^ exponent and leave the work to standard `strtod()`. + # This avoid the problems of decimal character portability. + # see: http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ + var + i = start + sign = 1.0 + kdigits, fdigits = 0 + exponent: int + integer: uint64 + frac_exponent = 0 + exp_sign = 1 + first_digit = -1 + has_sign = false + + # Sign? + if s[i] == '+' or s[i] == '-': + has_sign = true + if s[i] == '-': + sign = -1.0 + inc(i) + + # NaN? + if s[i] == 'N' or s[i] == 'n': + if s[i+1] == 'A' or s[i+1] == 'a': + if s[i+2] == 'N' or s[i+2] == 'n': + if s[i+3] notin IdentChars: + number = NaN + return i+3 - start + return 0 + + # Inf? + if s[i] == 'I' or s[i] == 'i': + if s[i+1] == 'N' or s[i+1] == 'n': + if s[i+2] == 'F' or s[i+2] == 'f': + if s[i+3] notin IdentChars: + number = Inf*sign + return i+3 - start + return 0 + + if s[i] in {'0'..'9'}: + first_digit = (s[i].ord - '0'.ord) + # Integer part? + while s[i] in {'0'..'9'}: + inc(kdigits) + integer = integer * 10'u64 + (s[i].ord - '0'.ord).uint64 + inc(i) + while s[i] == '_': inc(i) + + # Fractional part? + if s[i] == '.': + inc(i) + # if no integer part, Skip leading zeros + if kdigits <= 0: + while s[i] == '0': + inc(frac_exponent) + inc(i) + while s[i] == '_': inc(i) + + if first_digit == -1 and s[i] in {'0'..'9'}: + first_digit = (s[i].ord - '0'.ord) + # get fractional part + while s[i] in {'0'..'9'}: + inc(fdigits) + inc(frac_exponent) + integer = integer * 10'u64 + (s[i].ord - '0'.ord).uint64 + inc(i) + while s[i] == '_': inc(i) + + # if has no digits: return error + if kdigits + fdigits <= 0 and + (i == start or # no char consumed (empty string). + (i == start + 1 and has_sign)): # or only '+' or '- + return 0 + + if s[i] in {'e', 'E'}: + inc(i) + if s[i] == '+' or s[i] == '-': + if s[i] == '-': + exp_sign = -1 + + inc(i) + if s[i] notin {'0'..'9'}: + return 0 + while s[i] in {'0'..'9'}: + exponent = exponent * 10 + (ord(s[i]) - ord('0')) + inc(i) + while s[i] == '_': inc(i) # underscores are allowed and ignored + + var real_exponent = exp_sign*exponent - frac_exponent + let exp_negative = real_exponent < 0 + var abs_exponent = abs(real_exponent) + + # if exponent greater than can be represented: +/- zero or infinity + if abs_exponent > 999: + if exp_negative: + number = 0.0*sign + else: + number = Inf*sign + return i - start + + # if integer is representable in 53 bits: fast path + # max fast path integer is 1<<53 - 1 or 8999999999999999 (16 digits) + let digits = kdigits + fdigits + if digits <= 15 or (digits <= 16 and first_digit <= 8): + # max float power of ten with set bits above the 53th bit is 10^22 + if abs_exponent <= 22: + if exp_negative: + number = sign * integer.float / powtens[abs_exponent] + else: + number = sign * integer.float * powtens[abs_exponent] + return i - start + + # if exponent is greater try to fit extra exponent above 22 by multiplying + # integer part is there is space left. + let slop = 15 - kdigits - fdigits + if abs_exponent <= 22 + slop and not exp_negative: + number = sign * integer.float * powtens[slop] * powtens[abs_exponent-slop] + return i - start + + # if failed: slow path with strtod. + var t: array[500, char] # flaviu says: 325 is the longest reasonable literal + var ti = 0 + let maxlen = t.high - "e+000".len # reserve enough space for exponent + + result = i - start + i = start + # re-parse without error checking, any error should be handled by the code above. + if s[i] == '.': i.inc + while s[i] in {'0'..'9','+','-'}: + if ti < maxlen: + t[ti] = s[i]; inc(ti) + inc(i) + while s[i] in {'.', '_'}: # skip underscore and decimal point + inc(i) + + # insert exponent + t[ti] = 'E'; inc(ti) + t[ti] = (if exp_negative: '-' else: '+'); inc(ti) + inc(ti, 3) + + # insert adjusted exponent + t[ti-1] = ('0'.ord + abs_exponent mod 10).char; abs_exponent = abs_exponent div 10 + t[ti-2] = ('0'.ord + abs_exponent mod 10).char; abs_exponent = abs_exponent div 10 + t[ti-3] = ('0'.ord + abs_exponent mod 10).char + + when defined(nimNoArrayToCstringConversion): + number = c_strtod(addr t, nil) + else: + number = c_strtod(t, nil) + +proc nimInt64ToStr(x: int64): string {.compilerRtl.} = + result = newStringOfCap(sizeof(x)*4) + result.add x + +proc nimBoolToStr(x: bool): string {.compilerRtl.} = + return if x: "true" else: "false" + +proc nimCharToStr(x: char): string {.compilerRtl.} = + result = newString(1) + result[0] = x + +proc `$`(x: uint64): string = + if x == 0: + result = "0" + else: + result = newString(60) + var i = 0 + var n = x + while n != 0: + let nn = n div 10'u64 + result[i] = char(n - 10'u64 * nn + ord('0')) + inc i + n = nn + result.setLen i + + let half = i div 2 + # Reverse + for t in 0 .. half-1: swap(result[t], result[i-t-1]) diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index 285bf1adc..40bbf97dc 100644 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -143,19 +143,17 @@ proc getFileHandle*(f: File): FileHandle = c_fileno(f) proc readLine(f: File, line: var TaintedString): bool = var pos = 0 - var sp: cint = 80 + # Use the currently reserved space for a first try - if line.string.isNil: - line = TaintedString(newStringOfCap(80)) - else: - when not defined(nimscript): - sp = cint(cast[PGenericSeq](line.string).space) + var sp = line.string.len + if sp == 0: + sp = 80 line.string.setLen(sp) while true: # memset to \L so that we can tell how far fgets wrote, even on EOF, where # fgets doesn't append an \L - c_memset(addr line.string[pos], '\L'.ord, sp) - var fgetsSuccess = c_fgets(addr line.string[pos], sp, f) != nil + nimSetMem(addr line.string[pos], '\L'.ord, sp) + var fgetsSuccess = c_fgets(addr line.string[pos], sp.cint, f) != nil if not fgetsSuccess: checkErr(f) let m = c_memchr(addr line.string[pos], '\L'.ord, sp) if m != nil: @@ -200,9 +198,9 @@ proc write(f: File, b: bool) = if b: write(f, "true") else: write(f, "false") proc write(f: File, r: float32) = - if c_fprintf(f, "%g", r) < 0: checkErr(f) + if c_fprintf(f, "%.16g", r) < 0: checkErr(f) proc write(f: File, r: BiggestFloat) = - if c_fprintf(f, "%g", r) < 0: checkErr(f) + if c_fprintf(f, "%.16g", r) < 0: checkErr(f) proc write(f: File, c: char) = discard c_putc(cint(c), f) proc write(f: File, a: varargs[string, `$`]) = @@ -417,18 +415,26 @@ proc setStdIoUnbuffered() = discard c_setvbuf(stdin, nil, IONBF, 0) when declared(stdout): + when defined(windows) and compileOption("threads"): + var echoLock: SysLock + initSysLock echoLock + proc echoBinSafe(args: openArray[string]) {.compilerProc.} = # flockfile deadlocks some versions of Android 5.x.x - when not defined(windows) and not defined(android): + when not defined(windows) and not defined(android) and not defined(nintendoswitch): proc flockfile(f: File) {.importc, noDecl.} proc funlockfile(f: File) {.importc, noDecl.} flockfile(stdout) + when defined(windows) and compileOption("threads"): + acquireSys echoLock for s in args: discard c_fwrite(s.cstring, s.len, 1, stdout) const linefeed = "\n" # can be 1 or more chars discard c_fwrite(linefeed.cstring, linefeed.len, 1, stdout) discard c_fflush(stdout) - when not defined(windows) and not defined(android): + when not defined(windows) and not defined(android) and not defined(nintendoswitch): funlockfile(stdout) + when defined(windows) and compileOption("threads"): + releaseSys echoLock {.pop.} diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index 31a59c0c0..6438a0541 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -20,24 +20,6 @@ proc resize(old: int): int {.inline.} = elif old < 65536: result = old * 2 else: result = old * 3 div 2 # for large arrays * 3/2 is better -proc cmpStrings(a, b: NimString): int {.inline, compilerProc.} = - if a == b: return 0 - if a == nil: return -1 - if b == nil: return 1 - let minlen = min(a.len, b.len) - if minlen > 0: - result = c_memcmp(addr a.data, addr b.data, minlen.csize) - if result == 0: - result = a.len - b.len - else: - result = 0 - -proc eqStrings(a, b: NimString): bool {.inline, compilerProc.} = - if a == b: return true - if a == nil or b == nil: return false - return a.len == b.len and - equalMem(addr(a.data), addr(b.data), a.len) - when declared(allocAtomic): template allocStr(size: untyped): untyped = cast[NimString](allocAtomic(size)) @@ -63,6 +45,7 @@ proc rawNewStringNoInit(space: int): NimString {.compilerProc.} = if s < 7: s = 7 result = allocStrNoInit(sizeof(TGenericSeq) + s + 1) result.reserved = s + result.len = 0 when defined(gogc): result.elemSize = 1 @@ -71,6 +54,7 @@ proc rawNewString(space: int): NimString {.compilerProc.} = if s < 7: s = 7 result = allocStr(sizeof(TGenericSeq) + s + 1) result.reserved = s + result.len = 0 when defined(gogc): result.elemSize = 1 @@ -79,8 +63,11 @@ proc mnewString(len: int): NimString {.compilerProc.} = result.len = len proc copyStrLast(s: NimString, start, last: int): NimString {.compilerProc.} = - var start = max(start, 0) - var len = min(last, s.len-1) - start + 1 + # This is not used by most recent versions of the compiler anymore, but + # required for bootstrapping purposes. + let start = max(start, 0) + if s == nil: return nil + let len = min(last, s.len-1) - start + 1 if len > 0: result = rawNewStringNoInit(len) result.len = len @@ -90,8 +77,15 @@ proc copyStrLast(s: NimString, start, last: int): NimString {.compilerProc.} = result = rawNewString(len) proc copyStr(s: NimString, start: int): NimString {.compilerProc.} = + # This is not used by most recent versions of the compiler anymore, but + # required for bootstrapping purposes. + if s == nil: return nil result = copyStrLast(s, start, s.len-1) +proc nimToCStringConv(s: NimString): cstring {.compilerProc, inline.} = + if s == nil or s.len == 0: result = cstring"" + else: result = cstring(addr s.data) + proc toNimStr(str: cstring, len: int): NimString {.compilerProc.} = result = rawNewStringNoInit(len) result.len = len @@ -101,9 +95,6 @@ proc cstrToNimstr(str: cstring): NimString {.compilerRtl.} = if str == nil: NimString(nil) else: toNimStr(str, str.len) -template wasMoved(x: NimString): bool = false -# (x.reserved and seqShallowFlag) != 0 - proc copyString(src: NimString): NimString {.compilerRtl.} = if src != nil: if (src.reserved and seqShallowFlag) != 0: @@ -146,29 +137,23 @@ proc copyDeepString(src: NimString): NimString {.inline.} = result.len = src.len copyMem(addr(result.data), addr(src.data), src.len + 1) -proc hashString(s: string): int {.compilerproc.} = - # the compiler needs exactly the same hash function! - # this used to be used for efficient generation of string case statements - var h = 0 - for i in 0..len(s)-1: - h = h +% ord(s[i]) - h = h +% h shl 10 - h = h xor (h shr 6) - h = h +% h shl 3 - h = h xor (h shr 11) - h = h +% h shl 15 - result = h - proc addChar(s: NimString, c: char): NimString = # is compilerproc! - result = s - if result.len >= result.space: - let r = resize(result.space) - result = cast[NimString](growObj(result, - sizeof(TGenericSeq) + r + 1)) - result.reserved = r - elif wasMoved(s): - result = newOwnedString(s, s.len) + if s == nil: + result = rawNewStringNoInit(1) + result.len = 0 + else: + result = s + if result.len >= result.space: + let r = resize(result.space) + when defined(nimIncrSeqV3): + result = rawNewStringNoInit(r) + result.len = s.len + copyMem(addr result.data[0], unsafeAddr(s.data[0]), s.len+1) + else: + result = cast[NimString](growObj(result, + sizeof(TGenericSeq) + r + 1)) + result.reserved = r result.data[result.len] = c result.data[result.len+1] = '\0' inc(result.len) @@ -205,19 +190,27 @@ proc addChar(s: NimString, c: char): NimString = # s = rawNewString(0); proc resizeString(dest: NimString, addlen: int): NimString {.compilerRtl.} = - if dest.len + addlen <= dest.space and not wasMoved(dest): + if dest == nil: + result = rawNewStringNoInit(addlen) + elif dest.len + addlen <= dest.space: result = dest else: # slow path: - var sp = max(resize(dest.space), dest.len + addlen) - result = cast[NimString](growObj(dest, sizeof(TGenericSeq) + sp + 1)) + let sp = max(resize(dest.space), dest.len + addlen) + when defined(nimIncrSeqV3): + result = rawNewStringNoInit(sp) + result.len = dest.len + copyMem(addr result.data[0], unsafeAddr(dest.data[0]), dest.len+1) + else: + result = cast[NimString](growObj(dest, sizeof(TGenericSeq) + sp + 1)) result.reserved = sp #result = rawNewString(sp) #copyMem(result, dest, dest.len + sizeof(TGenericSeq)) # DO NOT UPDATE LEN YET: dest.len = newLen proc appendString(dest, src: NimString) {.compilerproc, inline.} = - copyMem(addr(dest.data[dest.len]), addr(src.data), src.len + 1) - inc(dest.len, src.len) + if src != nil: + copyMem(addr(dest.data[dest.len]), addr(src.data), src.len + 1) + inc(dest.len, src.len) proc appendChar(dest: NimString, c: char) {.compilerproc, inline.} = dest.data[dest.len] = c @@ -225,13 +218,21 @@ proc appendChar(dest: NimString, c: char) {.compilerproc, inline.} = inc(dest.len) proc setLengthStr(s: NimString, newLen: int): NimString {.compilerRtl.} = - var n = max(newLen, 0) - if wasMoved(s): - result = newOwnedString(s, n) + let n = max(newLen, 0) + if s == nil: + result = mnewString(newLen) elif n <= s.space: result = s else: - result = resizeString(s, n) + let sp = max(resize(s.space), newLen) + when defined(nimIncrSeqV3): + result = rawNewStringNoInit(sp) + result.len = s.len + copyMem(addr result.data[0], unsafeAddr(s.data[0]), s.len+1) + zeroMem(addr result.data[s.len], newLen - s.len) + result.reserved = sp + else: + result = resizeString(s, n) result.len = n result.data[n] = '\0' @@ -261,6 +262,28 @@ proc incrSeqV2(seq: PGenericSeq, elemSize: int): PGenericSeq {.compilerProc.} = GenericSeqSize)) result.reserved = r +template `+!`(p: pointer, s: int): pointer = + cast[pointer](cast[int](p) +% s) + +proc incrSeqV3(s: PGenericSeq, typ: PNimType): PGenericSeq {.compilerProc.} = + if s == nil: + result = cast[PGenericSeq](newSeq(typ, 1)) + result.len = 0 + else: + result = s + if result.len >= result.space: + let r = resize(result.space) + when defined(nimIncrSeqV3): + result = cast[PGenericSeq](newSeq(typ, r)) + result.len = s.len + copyMem(result +! GenericSeqSize, s +! GenericSeqSize, s.len * typ.base.size) + # since we steal the content from 's', it's crucial to set s's len to 0. + s.len = 0 + else: + result = cast[PGenericSeq](growObj(result, typ.base.size * r + + GenericSeqSize)) + result.reserved = r + proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {. compilerRtl, inl.} = result = seq @@ -293,7 +316,7 @@ proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {. # XXX: zeroing out the memory can still result in crashes if a wiped-out # cell is aliased by another pointer (ie proc parameter or a let variable). - # This is a tought problem, because even if we don't zeroMem here, in the + # This is a tough problem, because even if we don't zeroMem here, in the # presence of user defined destructors, the user will expect the cell to be # "destroyed" thus creating the same problem. We can destoy the cell in the # finalizer of the sequence, but this makes destruction non-deterministic. @@ -301,257 +324,42 @@ proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {. (newLen*%elemSize)), (result.len-%newLen) *% elemSize) result.len = newLen -# --------------- other string routines ---------------------------------- -proc add*(result: var string; x: int64) = - let base = result.len - setLen(result, base + sizeof(x)*4) - var i = 0 - var y = x - while true: - var d = y div 10 - result[base+i] = chr(abs(int(y - d*10)) + ord('0')) - inc(i) - y = d - if y == 0: break - if x < 0: - result[base+i] = '-' - inc(i) - setLen(result, base+i) - # mirror the string: - for j in 0..i div 2 - 1: - swap(result[base+j], result[base+i-j-1]) - -proc nimIntToStr(x: int): string {.compilerRtl.} = - result = newStringOfCap(sizeof(x)*4) - result.add x - -proc add*(result: var string; x: float) = - when nimvm: - result.add $x +proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int): PGenericSeq {. + compilerRtl.} = + sysAssert typ.kind == tySequence, "setLengthSeqV2: type is not a seq" + if s == nil: + result = cast[PGenericSeq](newSeq(typ, newLen)) else: - var buf: array[0..64, char] - when defined(nimNoArrayToCstringConversion): - var n: int = c_sprintf(addr buf, "%.16g", x) - else: - var n: int = c_sprintf(buf, "%.16g", x) - var hasDot = false - for i in 0..n-1: - if buf[i] == ',': - buf[i] = '.' - hasDot = true - elif buf[i] in {'a'..'z', 'A'..'Z', '.'}: - hasDot = true - if not hasDot: - buf[n] = '.' - buf[n+1] = '0' - buf[n+2] = '\0' - # On Windows nice numbers like '1.#INF', '-1.#INF' or '1.#NAN' - # of '-1.#IND' are produced. - # We want to get rid of these here: - if buf[n-1] in {'n', 'N', 'D', 'd'}: - result.add "nan" - elif buf[n-1] == 'F': - if buf[0] == '-': - result.add "-inf" - else: - result.add "inf" - else: - var i = 0 - while buf[i] != '\0': - result.add buf[i] - inc i - -proc nimFloatToStr(f: float): string {.compilerproc.} = - result = newStringOfCap(8) - result.add f - -proc c_strtod(buf: cstring, endptr: ptr cstring): float64 {. - importc: "strtod", header: "<stdlib.h>", noSideEffect.} - -const - IdentChars = {'a'..'z', 'A'..'Z', '0'..'9', '_'} - powtens = [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, - 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, - 1e20, 1e21, 1e22] - -proc nimParseBiggestFloat(s: string, number: var BiggestFloat, - start = 0): int {.compilerProc.} = - # This routine attempt to parse float that can parsed quickly. - # ie whose integer part can fit inside a 53bits integer. - # their real exponent must also be <= 22. If the float doesn't follow - # these restrictions, transform the float into this form: - # INTEGER * 10 ^ exponent and leave the work to standard `strtod()`. - # This avoid the problems of decimal character portability. - # see: http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ - var - i = start - sign = 1.0 - kdigits, fdigits = 0 - exponent: int - integer: uint64 - frac_exponent = 0 - exp_sign = 1 - first_digit = -1 - has_sign = false - - # Sign? - if s[i] == '+' or s[i] == '-': - has_sign = true - if s[i] == '-': - sign = -1.0 - inc(i) - - # NaN? - if s[i] == 'N' or s[i] == 'n': - if s[i+1] == 'A' or s[i+1] == 'a': - if s[i+2] == 'N' or s[i+2] == 'n': - if s[i+3] notin IdentChars: - number = NaN - return i+3 - start - return 0 - - # Inf? - if s[i] == 'I' or s[i] == 'i': - if s[i+1] == 'N' or s[i+1] == 'n': - if s[i+2] == 'F' or s[i+2] == 'f': - if s[i+3] notin IdentChars: - number = Inf*sign - return i+3 - start - return 0 - - if s[i] in {'0'..'9'}: - first_digit = (s[i].ord - '0'.ord) - # Integer part? - while s[i] in {'0'..'9'}: - inc(kdigits) - integer = integer * 10'u64 + (s[i].ord - '0'.ord).uint64 - inc(i) - while s[i] == '_': inc(i) - - # Fractional part? - if s[i] == '.': - inc(i) - # if no integer part, Skip leading zeros - if kdigits <= 0: - while s[i] == '0': - inc(frac_exponent) - inc(i) - while s[i] == '_': inc(i) - - if first_digit == -1 and s[i] in {'0'..'9'}: - first_digit = (s[i].ord - '0'.ord) - # get fractional part - while s[i] in {'0'..'9'}: - inc(fdigits) - inc(frac_exponent) - integer = integer * 10'u64 + (s[i].ord - '0'.ord).uint64 - inc(i) - while s[i] == '_': inc(i) - - # if has no digits: return error - if kdigits + fdigits <= 0 and - (i == start or # no char consumed (empty string). - (i == start + 1 and has_sign)): # or only '+' or '- - return 0 - - if s[i] in {'e', 'E'}: - inc(i) - if s[i] == '+' or s[i] == '-': - if s[i] == '-': - exp_sign = -1 - - inc(i) - if s[i] notin {'0'..'9'}: - return 0 - while s[i] in {'0'..'9'}: - exponent = exponent * 10 + (ord(s[i]) - ord('0')) - inc(i) - while s[i] == '_': inc(i) # underscores are allowed and ignored - - var real_exponent = exp_sign*exponent - frac_exponent - let exp_negative = real_exponent < 0 - var abs_exponent = abs(real_exponent) - - # if exponent greater than can be represented: +/- zero or infinity - if abs_exponent > 999: - if exp_negative: - number = 0.0*sign - else: - number = Inf*sign - return i - start - - # if integer is representable in 53 bits: fast path - # max fast path integer is 1<<53 - 1 or 8999999999999999 (16 digits) - let digits = kdigits + fdigits - if digits <= 15 or (digits <= 16 and first_digit <= 8): - # max float power of ten with set bits above the 53th bit is 10^22 - if abs_exponent <= 22: - if exp_negative: - number = sign * integer.float / powtens[abs_exponent] + when defined(nimIncrSeqV3): + let elemSize = typ.base.size + if s.space < newLen: + let r = max(resize(s.space), newLen) + result = cast[PGenericSeq](newSeq(typ, r)) + copyMem(result +! GenericSeqSize, s +! GenericSeqSize, s.len * elemSize) + # since we steal the content from 's', it's crucial to set s's len to 0. + s.len = 0 + elif newLen < s.len: + result = s + # we need to decref here, otherwise the GC leaks! + when not defined(boehmGC) and not defined(nogc) and + not defined(gcMarkAndSweep) and not defined(gogc) and + not defined(gcRegions): + if ntfNoRefs notin typ.base.flags: + for i in newLen..result.len-1: + forAllChildrenAux(cast[pointer](cast[ByteAddress](result) +% + GenericSeqSize +% (i*%elemSize)), + extGetCellType(result).base, waZctDecRef) + + # XXX: zeroing out the memory can still result in crashes if a wiped-out + # cell is aliased by another pointer (ie proc parameter or a let variable). + # This is a tough problem, because even if we don't zeroMem here, in the + # presence of user defined destructors, the user will expect the cell to be + # "destroyed" thus creating the same problem. We can destoy the cell in the + # finalizer of the sequence, but this makes destruction non-deterministic. + zeroMem(cast[pointer](cast[ByteAddress](result) +% GenericSeqSize +% + (newLen*%elemSize)), (result.len-%newLen) *% elemSize) else: - number = sign * integer.float * powtens[abs_exponent] - return i - start - - # if exponent is greater try to fit extra exponent above 22 by multiplying - # integer part is there is space left. - let slop = 15 - kdigits - fdigits - if abs_exponent <= 22 + slop and not exp_negative: - number = sign * integer.float * powtens[slop] * powtens[abs_exponent-slop] - return i - start - - # if failed: slow path with strtod. - var t: array[500, char] # flaviu says: 325 is the longest reasonable literal - var ti = 0 - let maxlen = t.high - "e+000".len # reserve enough space for exponent - - result = i - start - i = start - # re-parse without error checking, any error should be handled by the code above. - if s[i] == '.': i.inc - while s[i] in {'0'..'9','+','-'}: - if ti < maxlen: - t[ti] = s[i]; inc(ti) - inc(i) - while s[i] in {'.', '_'}: # skip underscore and decimal point - inc(i) - - # insert exponent - t[ti] = 'E'; inc(ti) - t[ti] = (if exp_negative: '-' else: '+'); inc(ti) - inc(ti, 3) - - # insert adjusted exponent - t[ti-1] = ('0'.ord + abs_exponent mod 10).char; abs_exponent = abs_exponent div 10 - t[ti-2] = ('0'.ord + abs_exponent mod 10).char; abs_exponent = abs_exponent div 10 - t[ti-3] = ('0'.ord + abs_exponent mod 10).char - - when defined(nimNoArrayToCstringConversion): - number = c_strtod(addr t, nil) - else: - number = c_strtod(t, nil) - -proc nimInt64ToStr(x: int64): string {.compilerRtl.} = - result = newStringOfCap(sizeof(x)*4) - result.add x - -proc nimBoolToStr(x: bool): string {.compilerRtl.} = - return if x: "true" else: "false" - -proc nimCharToStr(x: char): string {.compilerRtl.} = - result = newString(1) - result[0] = x - -proc binaryStrSearch(x: openArray[string], y: string): int {.compilerproc.} = - var - a = 0 - b = len(x) - while a < b: - var mid = (a + b) div 2 - if x[mid] < y: - a = mid + 1 + result = s + result.len = newLen else: - b = mid - if a < len(x) and x[a] == y: - result = a - else: - result = -1 + result = setLengthSeq(s, typ.base.size, newLen) diff --git a/lib/system/threads.nim b/lib/system/threads.nim index f61cc4280..aaf0164fd 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -116,6 +116,7 @@ when defined(windows): importc: "SetThreadAffinityMask", stdcall, header: "<windows.h>".} elif defined(genode): + import genode/env const GenodeHeader = "genode_cpp/threads.h" type @@ -125,11 +126,12 @@ elif defined(genode): ThreadVarSlot = int proc initThread(s: var SysThread, + env: GenodeEnv, stackSize: culonglong, entry: GenodeThreadProc, arg: pointer, affinity: cuint) {. - importcpp: "#.initThread(genodeEnv, @)".} + importcpp: "#.initThread(@)".} proc threadVarAlloc(): ThreadVarSlot = 0 @@ -160,10 +162,12 @@ elif defined(genode): mainTls else: - when not defined(macosx): + when not (defined(macosx) or defined(haiku)): {.passL: "-pthread".} - {.passC: "-pthread".} + when not defined(haiku): + {.passC: "-pthread".} + const schedh = "#define _GNU_SOURCE\n#include <sched.h>" pthreadh = "#define _GNU_SOURCE\n#include <pthread.h>" @@ -174,7 +178,7 @@ else: else: type Time = int - when defined(linux) and defined(amd64): + when (defined(linux) or defined(nintendoswitch)) and defined(amd64): type SysThread* {.importc: "pthread_t", header: "<sys/types.h>" .} = distinct culong @@ -389,8 +393,9 @@ proc onThreadDestruction*(handler: proc () {.closure, gcsafe.}) = ## A thread is destructed when the ``.thread`` proc returns ## normally or when it raises an exception. Note that unhandled exceptions ## in a thread nevertheless cause the whole process to die. - if threadDestructionHandlers.isNil: - threadDestructionHandlers = @[] + when not defined(nimNoNilSeqs): + if threadDestructionHandlers.isNil: + threadDestructionHandlers = @[] threadDestructionHandlers.add handler template afterThreadRuns() = @@ -440,7 +445,7 @@ proc threadProcWrapStackFrame[TArg](thrd: ptr Thread[TArg]) = threadProcWrapDispatch[TArg] when not hasSharedHeap: # init the GC for refc/markandsweep - setStackBottom(addr(p)) + nimGC_setStackBottom(addr(p)) initGC() when declared(threadType): threadType = ThreadType.NimThread @@ -569,7 +574,7 @@ when hostOS == "windows": elif defined(genode): var affinityOffset: cuint = 1 - # CPU affinity offset for next thread, safe to roll-over + ## CPU affinity offset for next thread, safe to roll-over proc createThread*[TArg](t: var Thread[TArg], tp: proc (arg: TArg) {.thread, nimcall.}, @@ -580,6 +585,7 @@ elif defined(genode): t.dataFn = tp when hasSharedHeap: t.stackSize = ThreadStackSize t.sys.initThread( + runtimeEnv, ThreadStackSize.culonglong, threadProcWrapper[TArg], addr(t), affinityOffset) inc affinityOffset @@ -642,7 +648,10 @@ when defined(windows): elif defined(linux): proc syscall(arg: clong): clong {.varargs, importc: "syscall", header: "<unistd.h>".} - var NR_gettid {.importc: "__NR_gettid", header: "<sys/syscall.h>".}: int + when defined(amd64): + const NR_gettid = clong(186) + else: + var NR_gettid {.importc: "__NR_gettid", header: "<sys/syscall.h>".}: clong proc getThreadId*(): int = ## get the ID of the currently running thread. @@ -708,3 +717,13 @@ elif defined(solaris): if threadId == 0: threadId = int(thr_self()) result = threadId + +elif defined(haiku): + type thr_id {.importc: "thread_id", header: "<OS.h>".} = distinct int32 + proc find_thread(name: cstring): thr_id {.importc, header: "<OS.h>".} + + proc getThreadId*(): int = + ## get the ID of the currently running thread. + if threadId == 0: + threadId = int(find_thread(nil)) + result = threadId diff --git a/lib/system/widestrs.nim b/lib/system/widestrs.nim index a8b28c279..85e5e1462 100644 --- a/lib/system/widestrs.nim +++ b/lib/system/widestrs.nim @@ -10,13 +10,12 @@ # Nim support for C/C++'s `wide strings`:idx:. This is part of the system # module! Do not import it directly! -when not declared(NimString): +when not declared(ThisIsSystem): {.error: "You must not import this module explicitly".} type Utf16Char* = distinct int16 WideCString* = ref UncheckedArray[Utf16Char] -{.deprecated: [TUtf16Char: Utf16Char].} proc len*(w: WideCString): int = ## returns the length of a widestring. This traverses the whole string to diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index b2c1cc1f5..60a6e5d9b 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -10,7 +10,7 @@ ## This module implements a small wrapper for some needed Win API procedures, ## so that the Nim compiler does not depend on the huge Windows module. -{.deadCodeElim:on.} +{.deadCodeElim: on.} # dce option deprecated import dynlib @@ -129,7 +129,6 @@ const PIPE_ACCESS_OUTBOUND* = 2'i32 PIPE_NOWAIT* = 0x00000001'i32 SYNCHRONIZE* = 0x00100000'i32 - FILE_FLAG_WRITE_THROUGH* = 0x80000000'i32 CREATE_NO_WINDOW* = 0x08000000'i32 @@ -281,15 +280,31 @@ else: importc:"CreateHardLinkA", dynlib: "kernel32", stdcall.} const - FILE_ATTRIBUTE_ARCHIVE* = 32'i32 - FILE_ATTRIBUTE_COMPRESSED* = 2048'i32 - FILE_ATTRIBUTE_NORMAL* = 128'i32 - FILE_ATTRIBUTE_DIRECTORY* = 16'i32 - FILE_ATTRIBUTE_HIDDEN* = 2'i32 - FILE_ATTRIBUTE_READONLY* = 1'i32 - FILE_ATTRIBUTE_REPARSE_POINT* = 1024'i32 - FILE_ATTRIBUTE_SYSTEM* = 4'i32 - FILE_ATTRIBUTE_TEMPORARY* = 256'i32 + FILE_ATTRIBUTE_READONLY* = 0x00000001'i32 + FILE_ATTRIBUTE_HIDDEN* = 0x00000002'i32 + FILE_ATTRIBUTE_SYSTEM* = 0x00000004'i32 + FILE_ATTRIBUTE_DIRECTORY* = 0x00000010'i32 + FILE_ATTRIBUTE_ARCHIVE* = 0x00000020'i32 + FILE_ATTRIBUTE_DEVICE* = 0x00000040'i32 + FILE_ATTRIBUTE_NORMAL* = 0x00000080'i32 + FILE_ATTRIBUTE_TEMPORARY* = 0x00000100'i32 + FILE_ATTRIBUTE_SPARSE_FILE* = 0x00000200'i32 + FILE_ATTRIBUTE_REPARSE_POINT* = 0x00000400'i32 + FILE_ATTRIBUTE_COMPRESSED* = 0x00000800'i32 + FILE_ATTRIBUTE_OFFLINE* = 0x00001000'i32 + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED* = 0x00002000'i32 + + FILE_FLAG_FIRST_PIPE_INSTANCE* = 0x00080000'i32 + FILE_FLAG_OPEN_NO_RECALL* = 0x00100000'i32 + FILE_FLAG_OPEN_REPARSE_POINT* = 0x00200000'i32 + FILE_FLAG_POSIX_SEMANTICS* = 0x01000000'i32 + FILE_FLAG_BACKUP_SEMANTICS* = 0x02000000'i32 + FILE_FLAG_DELETE_ON_CLOSE* = 0x04000000'i32 + FILE_FLAG_SEQUENTIAL_SCAN* = 0x08000000'i32 + FILE_FLAG_RANDOM_ACCESS* = 0x10000000'i32 + FILE_FLAG_NO_BUFFERING* = 0x20000000'i32 + FILE_FLAG_OVERLAPPED* = 0x40000000'i32 + FILE_FLAG_WRITE_THROUGH* = 0x80000000'i32 MAX_PATH* = 260 @@ -381,7 +396,7 @@ else: proc moveFileA*(lpExistingFileName, lpNewFileName: cstring): WINBOOL {. importc: "MoveFileA", stdcall, dynlib: "kernel32".} - proc moveFileExA*(lpExistingFileName, lpNewFileName: WideCString, + proc moveFileExA*(lpExistingFileName, lpNewFileName: cstring, flags: DWORD): WINBOOL {. importc: "MoveFileExA", stdcall, dynlib: "kernel32".} @@ -481,6 +496,13 @@ type sin6_flowinfo*: int32 # unsigned sin6_addr*: In6_addr + Sockaddr_storage* {.importc: "SOCKADDR_STORAGE", + header: "winsock2.h".} = object + ss_family*: int16 + ss_pad1: array[6, byte] + ss_align: int64 + ss_pad2: array[112, byte] + Servent* = object s_name*: cstring s_aliases*: cstringArray @@ -676,8 +698,6 @@ const FILE_MAP_WRITE* = 2'i32 INVALID_FILE_SIZE* = -1'i32 - FILE_FLAG_BACKUP_SEMANTICS* = 33554432'i32 - FILE_FLAG_OPEN_REPARSE_POINT* = 0x00200000'i32 DUPLICATE_SAME_ACCESS* = 2 FILE_READ_DATA* = 0x00000001 # file & pipe FILE_WRITE_DATA* = 0x00000002 # file & pipe @@ -688,6 +708,7 @@ const ERROR_PATH_NOT_FOUND* = 3 ERROR_ACCESS_DENIED* = 5 ERROR_NO_MORE_FILES* = 18 + ERROR_LOCK_VIOLATION* = 33 ERROR_HANDLE_EOF* = 38 ERROR_BAD_ARGUMENTS* = 165 @@ -697,6 +718,11 @@ proc duplicateHandle*(hSourceProcessHandle: HANDLE, hSourceHandle: HANDLE, dwDesiredAccess: DWORD, bInheritHandle: WINBOOL, dwOptions: DWORD): WINBOOL{.stdcall, dynlib: "kernel32", importc: "DuplicateHandle".} + +proc setHandleInformation*(hObject: HANDLE, dwMask: DWORD, + dwFlags: DWORD): WINBOOL {.stdcall, + dynlib: "kernel32", importc: "SetHandleInformation".} + proc getCurrentProcess*(): HANDLE{.stdcall, dynlib: "kernel32", importc: "GetCurrentProcess".} @@ -751,6 +777,9 @@ when not useWinUnicode: proc unmapViewOfFile*(lpBaseAddress: pointer): WINBOOL {.stdcall, dynlib: "kernel32", importc: "UnmapViewOfFile".} +proc flushViewOfFile*(lpBaseAddress: pointer, dwNumberOfBytesToFlush: DWORD): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "FlushViewOfFile".} + type OVERLAPPED* {.pure, inheritable.} = object internal*: PULONG @@ -773,7 +802,6 @@ type const ERROR_IO_PENDING* = 997 # a.k.a WSA_IO_PENDING - FILE_FLAG_OVERLAPPED* = 1073741824 WSAECONNABORTED* = 10053 WSAEADDRINUSE* = 10048 WSAECONNRESET* = 10054 @@ -1074,3 +1102,14 @@ proc ConvertThreadToFiberEx*(param: pointer, flags: int32): pointer {.stdcall, d proc DeleteFiber*(fiber: pointer): void {.stdcall, discardable, dynlib: "kernel32", importc.} proc SwitchToFiber*(fiber: pointer): void {.stdcall, discardable, dynlib: "kernel32", importc.} proc GetCurrentFiber*(): pointer {.stdcall, importc, header: "Windows.h".} + +proc toFILETIME*(t: int64): FILETIME = + ## Convert the Windows file time timestamp ``t`` to ``FILETIME``. + result = FILETIME(dwLowDateTime: cast[DWORD](t), dwHighDateTime: DWORD(t shr 32)) + +type + LPFILETIME* = ptr FILETIME + +proc setFileTime*(hFile: HANDLE, lpCreationTime: LPFILETIME, + lpLastAccessTime: LPFILETIME, lpLastWriteTime: LPFILETIME): WINBOOL + {.stdcall, dynlib: "kernel32", importc: "SetFileTime".} diff --git a/lib/wrappers/iup.nim b/lib/wrappers/iup.nim index d910173ca..dee2e14c7 100644 --- a/lib/wrappers/iup.nim +++ b/lib/wrappers/iup.nim @@ -34,7 +34,7 @@ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # **************************************************************************** -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated when defined(windows): const dllname = "iup(|30|27|26|25|24).dll" diff --git a/lib/wrappers/mysql.nim b/lib/wrappers/mysql.nim index e53d5308a..06c663822 100644 --- a/lib/wrappers/mysql.nim +++ b/lib/wrappers/mysql.nim @@ -7,16 +7,16 @@ # distribution, for details about the copyright. # -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated {.push, callconv: cdecl.} when defined(Unix): when defined(macosx): const - lib = "(libmysqlclient|libmariadbclient).(|20|19|18|17|16|15).dylib" + lib = "(libmysqlclient|libmariadbclient)(|.20|.19|.18|.17|.16|.15).dylib" else: const - lib = "(libmysqlclient|libmariadbclient).so.(|20|19|18|17|16|15)" + lib = "(libmysqlclient|libmariadbclient).so(|.20|.19|.18|.17|.16|.15)" when defined(Windows): const lib = "(libmysql.dll|libmariadb.dll)" diff --git a/lib/wrappers/odbcsql.nim b/lib/wrappers/odbcsql.nim index 1b2544ec0..43d1c504d 100644 --- a/lib/wrappers/odbcsql.nim +++ b/lib/wrappers/odbcsql.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated when not defined(ODBCVER): const diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim index eefc09cb9..47fff8397 100644 --- a/lib/wrappers/openssl.nim +++ b/lib/wrappers/openssl.nim @@ -22,7 +22,7 @@ ## ./bin/nim c -d:ssl -p:. -r tests/untestable/tssl.nim ## ./bin/nim c -d:ssl -p:. --dynlibOverride:ssl --passL:-lcrypto --passL:-lssl -r tests/untestable/tssl.nim -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated const useWinVersion = defined(Windows) or defined(nimdoc) @@ -38,12 +38,16 @@ when useWinVersion: from winlean import SocketHandle else: - const versions = "(.1.1|.38|.39|.41|.43|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|)" + const versions = "(.1.1|.38|.39|.41|.43|.44|.45|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|)" when defined(macosx): const DLLSSLName* = "libssl" & versions & ".dylib" DLLUtilName* = "libcrypto" & versions & ".dylib" + elif defined(genode): + const + DLLSSLName* = "libssl.lib.so" + DLLUtilName* = "libcrypto.lib.so" else: const DLLSSLName* = "libssl.so" & versions @@ -323,6 +327,7 @@ proc ERR_load_BIO_strings*(){.cdecl, dynlib: DLLUtilName, importc.} proc SSL_new*(context: SslCtx): SslPtr{.cdecl, dynlib: DLLSSLName, importc.} proc SSL_free*(ssl: SslPtr){.cdecl, dynlib: DLLSSLName, importc.} proc SSL_get_SSL_CTX*(ssl: SslPtr): SslCtx {.cdecl, dynlib: DLLSSLName, importc.} +proc SSL_set_SSL_CTX*(ssl: SslPtr, ctx: SslCtx): SslCtx {.cdecl, dynlib: DLLSSLName, importc.} proc SSL_CTX_new*(meth: PSSL_METHOD): SslCtx{.cdecl, dynlib: DLLSSLName, importc.} proc SSL_CTX_load_verify_locations*(ctx: SslCtx, CAfile: cstring, @@ -390,7 +395,7 @@ proc ERR_peek_last_error*(): cInt{.cdecl, dynlib: DLLUtilName, importc.} proc OPENSSL_config*(configName: cstring){.cdecl, dynlib: DLLSSLName, importc.} -when not useWinVersion and not defined(macosx) and not defined(android): +when not useWinVersion and not defined(macosx) and not defined(android) and not defined(nimNoAllocForSSL): proc CRYPTO_set_mem_functions(a,b,c: pointer){.cdecl, dynlib: DLLUtilName, importc.} @@ -404,7 +409,7 @@ when not useWinVersion and not defined(macosx) and not defined(android): if p != nil: dealloc(p) proc CRYPTO_malloc_init*() = - when not useWinVersion and not defined(macosx) and not defined(android): + when not useWinVersion and not defined(macosx) and not defined(android) and not defined(nimNoAllocForSSL): CRYPTO_set_mem_functions(allocWrapper, reallocWrapper, deallocWrapper) proc SSL_CTX_ctrl*(ctx: SslCtx, cmd: cInt, larg: int, parg: pointer): int{. diff --git a/lib/wrappers/pcre.nim b/lib/wrappers/pcre.nim index 6c7088bbf..e9e11960c 100644 --- a/lib/wrappers/pcre.nim +++ b/lib/wrappers/pcre.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated # The current PCRE version information. diff --git a/lib/wrappers/postgres.nim b/lib/wrappers/postgres.nim index f9a10dccb..7cb816f68 100644 --- a/lib/wrappers/postgres.nim +++ b/lib/wrappers/postgres.nim @@ -15,7 +15,7 @@ # connection-protocol. # -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated when defined(windows): const diff --git a/lib/wrappers/sqlite3.nim b/lib/wrappers/sqlite3.nim index a12945832..0276a0a65 100644 --- a/lib/wrappers/sqlite3.nim +++ b/lib/wrappers/sqlite3.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -{.deadCodeElim: on.} +{.deadCodeElim: on.} # dce option deprecated when defined(windows): when defined(nimOldDlls): const Lib = "sqlite3.dll" diff --git a/tools/nimpretty.nim b/nimpretty/nimpretty.nim index 36d1382cf..aa9756c45 100644 --- a/tools/nimpretty.nim +++ b/nimpretty/nimpretty.nim @@ -12,7 +12,7 @@ when not defined(nimpretty): {.error: "This needs to be compiled with --define:nimPretty".} -import ../compiler / [idents, msgs, ast, syntaxes, renderer] +import ../compiler / [idents, msgs, ast, syntaxes, renderer, options] import parseopt, strutils, os @@ -24,7 +24,8 @@ const Usage: nimpretty [options] file.nim Options: - --backup:ON|OFF create a backup file before overwritting (default: ON) + --backup:on|off create a backup file before overwritting (default: ON) + --output:file set the output file (default: overwrite the .nim file) --version show the version --help show this help """ @@ -39,14 +40,18 @@ proc writeVersion() = stdout.flushFile() quit(0) -proc prettyPrint(infile: string) = - let fileIdx = fileInfoIdx(infile) - let tree = parseFile(fileIdx, newIdentCache()) - let outfile = changeFileExt(infile, ".pretty.nim") - renderModule(tree, infile, outfile, {}) +proc prettyPrint(infile, outfile: string) = + var conf = newConfigRef() + let fileIdx = fileInfoIdx(conf, infile) + conf.outFile = outfile + when defined(nimpretty2): + discard parseFile(fileIdx, newIdentCache(), conf) + else: + let tree = parseFile(fileIdx, newIdentCache(), conf) + renderModule(tree, infile, outfile, {}, fileIdx, conf) proc main = - var infile: string + var infile, outfile: string var backup = true for kind, key, val in getopt(): case kind @@ -57,12 +62,14 @@ proc main = of "help", "h": writeHelp() of "version", "v": writeVersion() of "backup": backup = parseBool(val) + of "output", "o": outfile = val else: writeHelp() of cmdEnd: assert(false) # cannot happen if infile.len == 0: quit "[Error] no input file." if backup: os.copyFile(source=infile, dest=changeFileExt(infile, ".nim.backup")) - prettyPrint(infile) + if outfile.len == 0: outfile = infile + prettyPrint(infile, outfile) main() diff --git a/nimpretty/nimpretty.nim.cfg b/nimpretty/nimpretty.nim.cfg new file mode 100644 index 000000000..5fafa6d2a --- /dev/null +++ b/nimpretty/nimpretty.nim.cfg @@ -0,0 +1,2 @@ +--define: nimpretty +--define: nimpretty2 diff --git a/nimpretty/tester.nim b/nimpretty/tester.nim new file mode 100644 index 000000000..8798ce06a --- /dev/null +++ b/nimpretty/tester.nim @@ -0,0 +1,29 @@ +# Small program that runs the test cases + +import strutils, os + +const + dir = "nimpretty/tests/" + +var + failures = 0 + +proc test(infile, outfile: string) = + if execShellCmd("nimpretty -o:$2 --backup:off $1" % [infile, outfile]) != 0: + quit("FAILURE") + let nimFile = splitFile(infile).name + let expected = dir / "expected" / nimFile & ".nim" + let produced = dir / nimFile & ".pretty" + if strip(readFile(expected)) != strip(readFile(produced)): + echo "FAILURE: files differ: ", nimFile + discard execShellCmd("diff -uNdr " & expected & " " & produced) + failures += 1 + else: + echo "SUCCESS: files identical: ", nimFile + +for t in walkFiles(dir / "*.nim"): + let res = t.changeFileExt("pretty") + test(t, res) + removeFile(res) + +if failures > 0: quit($failures & " failures occurred.") diff --git a/nimpretty/tests/exhaustive.nim b/nimpretty/tests/exhaustive.nim new file mode 100644 index 000000000..a2501a193 --- /dev/null +++ b/nimpretty/tests/exhaustive.nim @@ -0,0 +1,316 @@ +discard """ + outputsub: '''ObjectAssignmentError''' + exitcode: "1" +""" + +import verylongnamehere,verylongnamehere,verylongnamehereverylongnamehereverylong,namehere,verylongnamehere + +proc `[]=`() = discard "index setter" +proc `putter=`() = discard cast[pointer](cast[int](buffer) + size) + +(not false) + +let expr = if true: "true" else: "false" + +var body = newNimNode(nnkIfExpr).add( + newNimNode(nnkElifBranch).add( + infix(newDotExpr(ident("a"), ident("kind")), "==", newDotExpr(ident("b"), ident("kind"))), + condition + ), + newNimNode(nnkElse).add(newStmtList(newNimNode(nnkReturnStmt).add(ident("false")))) +) + +# comment + +var x = 1 + +type + GeneralTokenizer* = object of RootObj ## comment here + kind*: TokenClass ## and here + start*, length*: int ## you know how it goes... + buf: cstring + pos: int # other comment here. + state: TokenClass + +var x*: string +var y: seq[string] #[ yay inline comments. So nice I have to care bout these. ]# + +echo "#", x, "##", y, "#" & "string" & $test + +echo (tup, here) +echo(argA, argB) + +import macros + +## A documentation comment here. +## That spans multiple lines. +## And is not to be touched. + +const numbers = [4u8, 5'u16, 89898_00] + +macro m(n): untyped = + result = foo"string literal" + +{.push m.} +proc p() = echo "p", 1+4 * 5, if true: 5 else: 6 +proc q(param: var ref ptr string) = + p() + if true: + echo a and b or not c and not -d +{.pop.} + +q() + +when false: + # bug #4766 + type + Plain = ref object + discard + + Wrapped[T] = object + value: T + + converter toWrapped[T](value: T): Wrapped[T] = + Wrapped[T](value: value) + + let result = Plain() + discard $result + +when false: + # bug #3670 + template someTempl(someConst: bool) = + when someConst: + var a: int + if true: + when not someConst: + var a: int + a = 5 + + someTempl(true) + + +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Layouter for nimpretty. Still primitive but useful. + +import idents, lexer, lineinfos, llstream, options, msgs, strutils +from os import changeFileExt + +const + MaxLineLen = 80 + LineCommentColumn = 30 + +type + SplitKind = enum + splitComma, splitParLe, splitAnd, splitOr, splitIn, splitBinary + + Emitter* = object + f: PLLStream + config: ConfigRef + fid: FileIndex + lastTok: TTokType + inquote {.pragmaHereWrongCurlyEnd}: bool + col, lastLineNumber, lineSpan, indentLevel: int + content: string + fixedUntil: int # marks where we must not go in the content + altSplitPos: array[SplitKind, int] # alternative split positions + +proc openEmitter*[T, S](em: var Emitter; config: ConfigRef, fileIdx: FileIndex) {.pragmaHereWrongCurlyEnd} = + let outfile = changeFileExt(config.toFullPath(fileIdx), ".pretty.nim") + em.f = llStreamOpen(outfile, fmWrite) + em.config = config + em.fid = fileIdx + em.lastTok = tkInvalid + em.inquote = false + em.col = 0 + em.content = newStringOfCap(16_000) + if em.f == nil: + rawMessage(config, errGenerated, "cannot open file: " & outfile) + +proc closeEmitter*(em: var Emitter) {.inline.} = + em.f.llStreamWrite em.content + llStreamClose(em.f) + +proc countNewlines(s: string): int = + result = 0 + for i in 0..<s.len: + if s[i+1] == '\L': inc result + +proc calcCol(em: var Emitter; s: string) = + var i = s.len-1 + em.col = 0 + while i >= 0 and s[i] != '\L': + dec i + inc em.col + +template wr(x) = + em.content.add x + inc em.col, x.len + +template goodCol(col): bool = col in 40..MaxLineLen + +const splitters = {tkComma, tkSemicolon, tkParLe, tkParDotLe, + tkBracketLe, tkBracketLeColon, tkCurlyDotLe, + tkCurlyLe} + +template rememberSplit(kind) = + if goodCol(em.col): + em.altSplitPos[kind] = em.content.len + +proc softLinebreak(em: var Emitter, lit: string) = + # XXX Use an algorithm that is outlined here: + # https://llvm.org/devmtg/2013-04/jasper-slides.pdf + # +2 because we blindly assume a comma or ' &' might follow + if not em.inquote and em.col+lit.len+2 >= MaxLineLen: + if em.lastTok in splitters: + wr("\L") + em.col = 0 + for i in 1..em.indentLevel+2: wr(" ") + else: + # search backwards for a good split position: + for a in em.altSplitPos: + if a > em.fixedUntil: + let ws = "\L" & repeat(' ',em.indentLevel+2) + em.col = em.content.len - a + em.content.insert(ws, a) + break + +proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = + + template endsInWhite(em): bool = + em.content.len > 0 and em.content[em.content.high] in {' ', '\L'} + template endsInAlpha(em): bool = + em.content.len > 0 and em.content[em.content.high] in SymChars+{'_'} + + proc emitComment(em: var Emitter; tok: TToken) = + let lit = strip fileSection(em.config, em.fid, tok.commentOffsetA, tok.commentOffsetB) + em.lineSpan = countNewlines(lit) + if em.lineSpan > 0: calcCol(em, lit) + if not endsInWhite(em): + wr(" ") + if em.lineSpan == 0 and max(em.col, LineCommentColumn) + lit.len <= MaxLineLen: + for i in 1 .. LineCommentColumn - em.col: wr(" ") + wr lit + + var preventComment = case tok.tokType + of tokKeywordLow..tokKeywordHigh: + if endsInAlpha(em): wr(" ") + wr(TokTypeToStr[tok.tokType]) + + case tok.tokType + of tkAnd: rememberSplit(splitAnd) + of tkOr: rememberSplit(splitOr) + of tkIn: rememberSplit(splitIn) + else: 90 + else: + "case returns value" + + + if tok.tokType == tkComment and tok.line == em.lastLineNumber and tok.indent >= 0: + # we have an inline comment so handle it before the indentation token: + emitComment(em, tok) + preventComment = true + em.fixedUntil = em.content.high + + elif tok.indent >= 0: + em.indentLevel = tok.indent + # remove trailing whitespace: + while em.content.len > 0 and em.content[em.content.high] == ' ': + setLen(em.content, em.content.len-1) + wr("\L") + for i in 2..tok.line - em.lastLineNumber: wr("\L") + em.col = 0 + for i in 1..tok.indent: + wr(" ") + em.fixedUntil = em.content.high + + case tok.tokType + of tokKeywordLow..tokKeywordHigh: + if endsInAlpha(em): wr(" ") + wr(TokTypeToStr[tok.tokType]) + + case tok.tokType + of tkAnd: rememberSplit(splitAnd) + of tkOr: rememberSplit(splitOr) + of tkIn: rememberSplit(splitIn) + else: discard + + of tkColon: + wr(TokTypeToStr[tok.tokType]) + wr(" ") + of tkSemicolon, + tkComma: + wr(TokTypeToStr[tok.tokType]) + wr(" ") + rememberSplit(splitComma) + of tkParLe, tkParRi, tkBracketLe, + tkBracketRi, tkCurlyLe, tkCurlyRi, + tkBracketDotLe, tkBracketDotRi, + tkCurlyDotLe, tkCurlyDotRi, + tkParDotLe, tkParDotRi, + tkColonColon, tkDot, tkBracketLeColon: + wr(TokTypeToStr[tok.tokType]) + if tok.tokType in splitters: + rememberSplit(splitParLe) + of tkEquals: + if not em.endsInWhite: wr(" ") + wr(TokTypeToStr[tok.tokType]) + wr(" ") + of tkOpr, tkDotDot: + if not em.endsInWhite: wr(" ") + wr(tok.ident.s) + template isUnary(tok): bool = + tok.strongSpaceB == 0 and tok.strongSpaceA > 0 + + if not isUnary(tok) or em.lastTok in {tkOpr, tkDotDot}: + wr(" ") + rememberSplit(splitBinary) + of tkAccent: + wr(TokTypeToStr[tok.tokType]) + em.inquote = not em.inquote + of tkComment: + if not preventComment: + emitComment(em, tok) + of tkIntLit..tkStrLit, tkRStrLit, tkTripleStrLit, tkGStrLit, tkGTripleStrLit, tkCharLit: + let lit = fileSection(em.config, em.fid, tok.offsetA, tok.offsetB) + softLinebreak(em, lit) + if endsInAlpha(em) and tok.tokType notin {tkGStrLit, tkGTripleStrLit}: wr(" ") + em.lineSpan = countNewlines(lit) + if em.lineSpan > 0: calcCol(em, lit) + wr lit + of tkEof: discard + else: + let lit = if tok.ident != nil: tok.ident.s else: tok.literal + softLinebreak(em, lit) + if endsInAlpha(em): wr(" ") + wr lit + + em.lastTok = tok.tokType + em.lastLineNumber = tok.line + em.lineSpan + em.lineSpan = 0 + +proc starWasExportMarker*(em: var Emitter) = + if em.content.endsWith(" * "): + setLen(em.content, em.content.len-3) + em.content.add("*") + dec em.col, 2 + +type + Thing = ref object + grade: string + # this name is great + name: string + +proc f() = + var c: char + var str: string + if c == '\\': + # escape char + str &= c diff --git a/nimpretty/tests/expected/exhaustive.nim b/nimpretty/tests/expected/exhaustive.nim new file mode 100644 index 000000000..95071fce3 --- /dev/null +++ b/nimpretty/tests/expected/exhaustive.nim @@ -0,0 +1,325 @@ +discard """ + outputsub: '''ObjectAssignmentError''' + exitcode: "1" +""" + +import verylongnamehere, verylongnamehere, + verylongnamehereverylongnamehereverylong, namehere, verylongnamehere + +proc `[]=`() = discard "index setter" +proc `putter=`() = discard cast[pointer](cast[int](buffer) + size) + +(not false) + +let expr = if true: "true" else: "false" + +var body = newNimNode(nnkIfExpr).add( + newNimNode(nnkElifBranch).add( + infix(newDotExpr(ident("a"), ident("kind")), "==", newDotExpr(ident("b"), + ident("kind"))), + condition + ), + newNimNode(nnkElse).add(newStmtList(newNimNode(nnkReturnStmt).add(ident( + "false")))) +) + +# comment + +var x = 1 + +type + GeneralTokenizer* = object of RootObj ## comment here + kind*: TokenClass ## and here + start*, length*: int ## you know how it goes... + buf: cstring + pos: int # other comment here. + state: TokenClass + +var x*: string +var y: seq[string] #[ yay inline comments. So nice I have to care bout these. ]# + +echo "#", x, "##", y, "#" & "string" & $test + +echo (tup, here) +echo(argA, argB) + +import macros + +## A documentation comment here. +## That spans multiple lines. +## And is not to be touched. + +const numbers = [4u8, 5'u16, 89898_00] + +macro m(n): untyped = + result = foo"string literal" + +{.push m.} +proc p() = echo "p", 1+4 * 5, if true: 5 else: 6 +proc q(param: var ref ptr string) = + p() + if true: + echo a and b or not c and not -d +{.pop.} + +q() + +when false: + # bug #4766 + type + Plain = ref object + discard + + Wrapped[T] = object + value: T + + converter toWrapped[T](value: T): Wrapped[T] = + Wrapped[T](value: value) + + let result = Plain() + discard $result + +when false: + # bug #3670 + template someTempl(someConst: bool) = + when someConst: + var a: int + if true: + when not someConst: + var a: int + a = 5 + + someTempl(true) + + +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Layouter for nimpretty. Still primitive but useful. + +import idents, lexer, lineinfos, llstream, options, msgs, strutils +from os import changeFileExt + +const + MaxLineLen = 80 + LineCommentColumn = 30 + +type + SplitKind = enum + splitComma, splitParLe, splitAnd, splitOr, splitIn, splitBinary + + Emitter* = object + f: PLLStream + config: ConfigRef + fid: FileIndex + lastTok: TTokType + inquote {.pragmaHereWrongCurlyEnd.}: bool + col, lastLineNumber, lineSpan, indentLevel: int + content: string + fixedUntil: int # marks where we must not go in the content + altSplitPos: array[SplitKind, int] # alternative split positions + +proc openEmitter*[T, S](em: var Emitter; config: ConfigRef; + fileIdx: FileIndex) {.pragmaHereWrongCurlyEnd.} = + let outfile = changeFileExt(config.toFullPath(fileIdx), ".pretty.nim") + em.f = llStreamOpen(outfile, fmWrite) + em.config = config + em.fid = fileIdx + em.lastTok = tkInvalid + em.inquote = false + em.col = 0 + em.content = newStringOfCap(16_000) + if em.f == nil: + rawMessage(config, errGenerated, "cannot open file: " & outfile) + +proc closeEmitter*(em: var Emitter) {.inline.} = + em.f.llStreamWrite em.content + llStreamClose(em.f) + +proc countNewlines(s: string): int = + result = 0 + for i in 0..<s.len: + if s[i+1] == '\L': inc result + +proc calcCol(em: var Emitter; s: string) = + var i = s.len-1 + em.col = 0 + while i >= 0 and s[i] != '\L': + dec i + inc em.col + +template wr(x) = + em.content.add x + inc em.col, x.len + +template goodCol(col): bool = col in 40..MaxLineLen + +const splitters = {tkComma, tkSemicolon, tkParLe, tkParDotLe, + tkBracketLe, tkBracketLeColon, tkCurlyDotLe, + tkCurlyLe} + +template rememberSplit(kind) = + if goodCol(em.col): + em.altSplitPos[kind] = em.content.len + +proc softLinebreak(em: var Emitter; lit: string) = + # XXX Use an algorithm that is outlined here: + # https://llvm.org/devmtg/2013-04/jasper-slides.pdf + # +2 because we blindly assume a comma or ' &' might follow + if not em.inquote and em.col+lit.len+2 >= MaxLineLen: + if em.lastTok in splitters: + wr("\L") + em.col = 0 + for i in 1..em.indentLevel+2: wr(" ") + else: + # search backwards for a good split position: + for a in em.altSplitPos: + if a > em.fixedUntil: + let ws = "\L" & repeat(' ', em.indentLevel+2) + em.col = em.content.len - a + em.content.insert(ws, a) + break + +proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = + + template endsInWhite(em): bool = + em.content.len > 0 and em.content[em.content.high] in {' ', '\L'} + template endsInAlpha(em): bool = + em.content.len > 0 and em.content[em.content.high] in SymChars+{'_'} + + proc emitComment(em: var Emitter; tok: TToken) = + let lit = strip fileSection(em.config, em.fid, tok.commentOffsetA, + tok.commentOffsetB) + em.lineSpan = countNewlines(lit) + if em.lineSpan > 0: calcCol(em, lit) + if not endsInWhite(em): + wr(" ") + if em.lineSpan == 0 and max(em.col, + LineCommentColumn) + lit.len <= MaxLineLen: + for i in 1 .. LineCommentColumn - em.col: wr(" ") + wr lit + + var preventComment = case tok.tokType + of tokKeywordLow..tokKeywordHigh: + if endsInAlpha(em): wr(" ") + wr(TokTypeToStr[tok.tokType]) + + case tok.tokType + of tkAnd: rememberSplit(splitAnd) + of tkOr: rememberSplit(splitOr) + of tkIn: rememberSplit(splitIn) + else: 90 + else: + "case returns value" + + + if tok.tokType == tkComment and tok.line == em.lastLineNumber and + tok.indent >= 0: + # we have an inline comment so handle it before the indentation token: + emitComment(em, tok) + preventComment = true + em.fixedUntil = em.content.high + + elif tok.indent >= 0: + em.indentLevel = tok.indent + # remove trailing whitespace: + while em.content.len > 0 and em.content[em.content.high] == ' ': + setLen(em.content, em.content.len-1) + wr("\L") + for i in 2..tok.line - em.lastLineNumber: wr("\L") + em.col = 0 + for i in 1..tok.indent: + wr(" ") + em.fixedUntil = em.content.high + + case tok.tokType + of tokKeywordLow..tokKeywordHigh: + if endsInAlpha(em): wr(" ") + wr(TokTypeToStr[tok.tokType]) + + case tok.tokType + of tkAnd: rememberSplit(splitAnd) + of tkOr: rememberSplit(splitOr) + of tkIn: rememberSplit(splitIn) + else: discard + + of tkColon: + wr(TokTypeToStr[tok.tokType]) + wr(" ") + of tkSemicolon, + tkComma: + wr(TokTypeToStr[tok.tokType]) + wr(" ") + rememberSplit(splitComma) + of tkParLe, tkParRi, tkBracketLe, + tkBracketRi, tkCurlyLe, tkCurlyRi, + tkBracketDotLe, tkBracketDotRi, + tkCurlyDotLe, tkCurlyDotRi, + tkParDotLe, tkParDotRi, + tkColonColon, tkDot, tkBracketLeColon: + wr(TokTypeToStr[tok.tokType]) + if tok.tokType in splitters: + rememberSplit(splitParLe) + of tkEquals: + if not em.endsInWhite: wr(" ") + wr(TokTypeToStr[tok.tokType]) + wr(" ") + of tkOpr, tkDotDot: + if not em.endsInWhite: wr(" ") + wr(tok.ident.s) + template isUnary(tok): bool = + tok.strongSpaceB == 0 and tok.strongSpaceA > 0 + + if not isUnary(tok) or em.lastTok in {tkOpr, tkDotDot}: + wr(" ") + rememberSplit(splitBinary) + of tkAccent: + wr(TokTypeToStr[tok.tokType]) + em.inquote = not em.inquote + of tkComment: + if not preventComment: + emitComment(em, tok) + of tkIntLit..tkStrLit, tkRStrLit, tkTripleStrLit, tkGStrLit, + tkGTripleStrLit, tkCharLit: + let lit = fileSection(em.config, em.fid, tok.offsetA, tok.offsetB) + softLinebreak(em, lit) + if endsInAlpha(em) and tok.tokType notin {tkGStrLit, tkGTripleStrLit}: wr( + " ") + em.lineSpan = countNewlines(lit) + if em.lineSpan > 0: calcCol(em, lit) + wr lit + of tkEof: discard + else: + let lit = if tok.ident != nil: tok.ident.s else: tok.literal + softLinebreak(em, lit) + if endsInAlpha(em): wr(" ") + wr lit + + em.lastTok = tok.tokType + em.lastLineNumber = tok.line + em.lineSpan + em.lineSpan = 0 + +proc starWasExportMarker*(em: var Emitter) = + if em.content.endsWith(" * "): + setLen(em.content, em.content.len-3) + em.content.add("*") + dec em.col, 2 + +type + Thing = ref object + grade: string + # this name is great + name: string + +proc f() = + var c: char + var str: string + if c == '\\': + # escape char + str &= c diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index 0328b817a..b20572b0e 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -9,6 +9,9 @@ ## Nimsuggest is a tool that helps to give editors IDE like capabilities. +when not defined(nimcore): + {.error: "nimcore MUST be defined for Nim's core tooling".} + import strutils, os, parseopt, parseutils, sequtils, net, rdstdin, sexp # Do NOT import suggest. It will lead to wierd bugs with # suggestionResultHook, because suggest.nim is included by sigmatch. @@ -17,7 +20,7 @@ import compiler / [options, commands, modules, sem, passes, passaux, msgs, nimconf, extccomp, condsyms, sigmatch, ast, scriptconfig, - idents, modulegraphs, vm, prefixmatches] + idents, modulegraphs, vm, prefixmatches, lineinfos, cmdlinehelper] when defined(windows): import winlean @@ -74,8 +77,8 @@ proc writelnToChannel(line: string) = proc sugResultHook(s: Suggest) = results.send(s) -proc errorHook(info: TLineInfo; msg: string; sev: Severity) = - results.send(Suggest(section: ideChk, filePath: toFullPath(info), +proc errorHook(conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) = + results.send(Suggest(section: ideChk, filePath: toFullPath(conf, info), line: toLinenumber(info), column: toColumn(info), doc: msg, forth: $sev)) @@ -106,10 +109,10 @@ proc sexp(s: IdeCmd|TSymKind|PrefixMatch): SexpNode = sexp($s) proc sexp(s: Suggest): SexpNode = # If you change the order here, make sure to change it over in # nim-mode.el too. - let qp = if s.qualifiedPath.isNil: @[] else: s.qualifiedPath + let qp = if s.qualifiedPath.len == 0: @[] else: s.qualifiedPath result = convertSexp([ s.section, - s.symkind, + TSymKind s.symkind, qp.map(newSString), s.filePath, s.forth, @@ -141,73 +144,73 @@ proc listEpc(): SexpNode = methodDesc.add(docstring) result.add(methodDesc) -proc findNode(n: PNode): PSym = +proc findNode(n: PNode; trackPos: TLineInfo): PSym = #echo "checking node ", n.info if n.kind == nkSym: - if isTracked(n.info, n.sym.name.s.len): return n.sym + if isTracked(n.info, trackPos, n.sym.name.s.len): return n.sym else: for i in 0 ..< safeLen(n): - let res = n.sons[i].findNode + let res = findNode(n[i], trackPos) if res != nil: return res -proc symFromInfo(graph: ModuleGraph; gTrackPos: TLineInfo): PSym = - let m = graph.getModule(gTrackPos.fileIndex) - #echo m.isNil, " I knew it ", gTrackPos.fileIndex +proc symFromInfo(graph: ModuleGraph; trackPos: TLineInfo): PSym = + let m = graph.getModule(trackPos.fileIndex) if m != nil and m.ast != nil: - result = m.ast.findNode + result = findNode(m.ast, trackPos) proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; - graph: ModuleGraph; cache: IdentCache) = + graph: ModuleGraph) = + let conf = graph.config myLog("cmd: " & $cmd & ", file: " & file & ", dirtyFile: " & dirtyfile & "[" & $line & ":" & $col & "]") - gIdeCmd = cmd + conf.ideCmd = cmd if cmd == ideChk: - msgs.structuredErrorHook = errorHook - msgs.writelnHook = myLog + conf.structuredErrorHook = errorHook + conf.writelnHook = myLog else: - msgs.structuredErrorHook = nil - msgs.writelnHook = myLog - if cmd == ideUse and suggestVersion != 0: + conf.structuredErrorHook = nil + conf.writelnHook = myLog + if cmd == ideUse and conf.suggestVersion != 0: graph.resetAllModules() var isKnownFile = true - let dirtyIdx = file.fileInfoIdx(isKnownFile) + let dirtyIdx = fileInfoIdx(conf, file, isKnownFile) - if dirtyfile.len != 0: msgs.setDirtyFile(dirtyIdx, dirtyfile) - else: msgs.setDirtyFile(dirtyIdx, nil) + if dirtyfile.len != 0: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile) + else: msgs.setDirtyFile(conf, dirtyIdx, "") - gTrackPos = newLineInfo(dirtyIdx, line, col) - gTrackPosAttached = false - gErrorCounter = 0 - if suggestVersion == 1: + conf.m.trackPos = newLineInfo(dirtyIdx, line, col) + conf.m.trackPosAttached = false + conf.errorCounter = 0 + if conf.suggestVersion == 1: graph.usageSym = nil if not isKnownFile: - graph.compileProject(cache) - if suggestVersion == 0 and gIdeCmd in {ideUse, ideDus} and + graph.compileProject() + if conf.suggestVersion == 0 and conf.ideCmd in {ideUse, ideDus} and dirtyfile.len == 0: discard "no need to recompile anything" else: let modIdx = graph.parentModule(dirtyIdx) graph.markDirty dirtyIdx graph.markClientsDirty dirtyIdx - if gIdeCmd != ideMod: - graph.compileProject(cache, modIdx) - if gIdeCmd in {ideUse, ideDus}: - let u = if suggestVersion != 1: graph.symFromInfo(gTrackPos) else: graph.usageSym + if conf.ideCmd != ideMod: + graph.compileProject(modIdx) + if conf.ideCmd in {ideUse, ideDus}: + let u = if conf.suggestVersion != 1: graph.symFromInfo(conf.m.trackPos) else: graph.usageSym if u != nil: - listUsages(u) + listUsages(conf, u) else: - localError(gTrackPos, "found no symbol at this position " & $gTrackPos) + localError(conf, conf.m.trackPos, "found no symbol at this position " & (conf $ conf.m.trackPos)) proc executeEpc(cmd: IdeCmd, args: SexpNode; - graph: ModuleGraph; cache: IdentCache) = + graph: ModuleGraph) = let file = args[0].getStr line = args[1].getNum column = args[2].getNum var dirtyfile = "" if len(args) > 3: - dirtyfile = args[3].getStr(nil) - execute(cmd, file, dirtyfile, int(line), int(column), graph, cache) + dirtyfile = args[3].getStr("") + execute(cmd, file, dirtyfile, int(line), int(column), graph) proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string, return_symbol = "return") = @@ -257,7 +260,7 @@ proc toEpc(client: Socket; uid: BiggestInt) {.gcsafe.} = template setVerbosity(level: typed) = gVerbosity = level - gNotes = NotesVerbosity[gVerbosity] + conf.notes = NotesVerbosity[gVerbosity] proc connectToNextFreePort(server: Socket, host: string): Port = server.bindaddr(Port(0), host) @@ -349,16 +352,18 @@ proc replEpc(x: ThreadParams) {.thread.} = of "call": let uid = message[1].getNum + cmd = message[2].getSymbol args = message[3] - gIdeCmd = parseIdeCmd(message[2].getSymbol) - case gIdeCmd - of ideSug, ideCon, ideDef, ideUse, ideDus, ideOutline, ideHighlight: - setVerbosity(0) - else: discard - let cmd = $gIdeCmd & " " & args.argsToStr - myLog "MSG CMD: " & cmd - requests.send(cmd) + when false: + x.ideCmd[] = parseIdeCmd(message[2].getSymbol) + case x.ideCmd[] + of ideSug, ideCon, ideDef, ideUse, ideDus, ideOutline, ideHighlight: + setVerbosity(0) + else: discard + let fullCmd = cmd & " " & args.argsToStr + myLog "MSG CMD: " & fullCmd + requests.send(fullCmd) toEpc(client, uid) of "methods": returnEpc(client, message[1].getNum, listEpc()) @@ -374,16 +379,18 @@ proc replEpc(x: ThreadParams) {.thread.} = "unexpected call: " & epcAPI quit errMessage -proc execCmd(cmd: string; graph: ModuleGraph; cache: IdentCache; cachedMsgs: CachedMsgs) = +proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) = + let conf = graph.config + template sentinel() = # send sentinel for the input reading thread: results.send(Suggest(section: ideNone)) template toggle(sw) = - if sw in gGlobalOptions: - excl(gGlobalOptions, sw) + if sw in conf.globalOptions: + excl(conf.globalOptions, sw) else: - incl(gGlobalOptions, sw) + incl(conf.globalOptions, sw) sentinel() return @@ -395,21 +402,21 @@ proc execCmd(cmd: string; graph: ModuleGraph; cache: IdentCache; cachedMsgs: Cac var opc = "" var i = parseIdent(cmd, opc, 0) case opc.normalize - of "sug": gIdeCmd = ideSug - of "con": gIdeCmd = ideCon - of "def": gIdeCmd = ideDef - of "use": gIdeCmd = ideUse - of "dus": gIdeCmd = ideDus - of "mod": gIdeCmd = ideMod - of "chk": gIdeCmd = ideChk - of "highlight": gIdeCmd = ideHighlight - of "outline": gIdeCmd = ideOutline + of "sug": conf.ideCmd = ideSug + of "con": conf.ideCmd = ideCon + of "def": conf.ideCmd = ideDef + of "use": conf.ideCmd = ideUse + of "dus": conf.ideCmd = ideDus + of "mod": conf.ideCmd = ideMod + of "chk": conf.ideCmd = ideChk + of "highlight": conf.ideCmd = ideHighlight + of "outline": conf.ideCmd = ideOutline of "quit": sentinel() quit() of "debug": toggle optIdeDebug of "terse": toggle optIdeTerse - of "known": gIdeCmd = ideKnown + of "known": conf.ideCmd = ideKnown else: err() var dirtyfile = "" var orig = "" @@ -423,26 +430,27 @@ proc execCmd(cmd: string; graph: ModuleGraph; cache: IdentCache; cachedMsgs: Cac i += skipWhile(cmd, seps, i) i += parseInt(cmd, col, i) - if gIdeCmd == ideKnown: - results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(orig)))) + if conf.ideCmd == ideKnown: + results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, orig)))) else: - if gIdeCmd == ideChk: - for cm in cachedMsgs: errorHook(cm.info, cm.msg, cm.sev) - execute(gIdeCmd, orig, dirtyfile, line, col, graph, cache) + if conf.ideCmd == ideChk: + for cm in cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev) + execute(conf.ideCmd, orig, dirtyfile, line, col, graph) sentinel() -proc recompileFullProject(graph: ModuleGraph; cache: IdentCache) = +proc recompileFullProject(graph: ModuleGraph) = #echo "recompiling full project" - resetSystemArtifacts() - vm.globalCtx = nil + resetSystemArtifacts(graph) + graph.vm = nil graph.resetAllModules() GC_fullcollect() - compileProject(graph, cache) + compileProject(graph) #echo GC_getStatistics() -proc mainThread(graph: ModuleGraph; cache: IdentCache) = +proc mainThread(graph: ModuleGraph) = + let conf = graph.config if gLogging: - for it in searchPaths: + for it in conf.searchPaths: log(it) proc wrHook(line: string) {.closure.} = @@ -451,56 +459,56 @@ proc mainThread(graph: ModuleGraph; cache: IdentCache) = else: writelnToChannel(line) - msgs.writelnHook = wrHook - suggestionResultHook = sugResultHook + conf.writelnHook = wrHook + conf.suggestionResultHook = sugResultHook graph.doStopCompile = proc (): bool = requests.peek() > 0 var idle = 0 var cachedMsgs: CachedMsgs = @[] while true: let (hasData, req) = requests.tryRecv() if hasData: - msgs.writelnHook = wrHook - suggestionResultHook = sugResultHook - execCmd(req, graph, cache, cachedMsgs) + conf.writelnHook = wrHook + conf.suggestionResultHook = sugResultHook + execCmd(req, graph, cachedMsgs) idle = 0 else: os.sleep 250 idle += 1 if idle == 20 and gRefresh: # we use some nimsuggest activity to enable a lazy recompile: - gIdeCmd = ideChk - msgs.writelnHook = proc (s: string) = discard + conf.ideCmd = ideChk + conf.writelnHook = proc (s: string) = discard cachedMsgs.setLen 0 - msgs.structuredErrorHook = proc (info: TLineInfo; msg: string; sev: Severity) = + conf.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) = cachedMsgs.add(CachedMsg(info: info, msg: msg, sev: sev)) - suggestionResultHook = proc (s: Suggest) = discard - recompileFullProject(graph, cache) + conf.suggestionResultHook = proc (s: Suggest) = discard + recompileFullProject(graph) var inputThread: Thread[ThreadParams] -proc mainCommand(graph: ModuleGraph; cache: IdentCache) = - clearPasses() - registerPass verbosePass - registerPass semPass - gCmd = cmdIdeTools - incl gGlobalOptions, optCaasEnabled - wantMainModule() +proc mainCommand(graph: ModuleGraph) = + let conf = graph.config + clearPasses(graph) + registerPass graph, verbosePass + registerPass graph, semPass + conf.cmd = cmdIdeTools + wantMainModule(conf) - if not fileExists(gProjectFull): - quit "cannot find file: " & gProjectFull + if not fileExists(conf.projectFull): + quit "cannot find file: " & conf.projectFull - add(searchPaths, options.libpath) + add(conf.searchPaths, conf.libpath) # do not stop after the first error: - msgs.gErrorMax = high(int) + conf.errorMax = high(int) # do not print errors, but log them - msgs.writelnHook = proc (s: string) = log(s) - msgs.structuredErrorHook = nil + conf.writelnHook = proc (s: string) = log(s) + conf.structuredErrorHook = nil # compile the project before showing any input so that we already # can answer questions right away: - compileProject(graph, cache) + compileProject(graph) open(requests) open(results) @@ -510,15 +518,15 @@ proc mainCommand(graph: ModuleGraph; cache: IdentCache) = of mtcp: createThread(inputThread, replTcp, (gPort, gAddress)) of mepc: createThread(inputThread, replEpc, (gPort, gAddress)) of mcmdsug: createThread(inputThread, replCmdline, - (gPort, "sug \"" & options.gProjectFull & "\":" & gAddress)) + (gPort, "sug \"" & conf.projectFull & "\":" & gAddress)) of mcmdcon: createThread(inputThread, replCmdline, - (gPort, "con \"" & options.gProjectFull & "\":" & gAddress)) - mainThread(graph, cache) + (gPort, "con \"" & conf.projectFull & "\":" & gAddress)) + mainThread(graph) joinThread(inputThread) close(requests) close(results) -proc processCmdLine*(pass: TCmdLinePass, cmd: string) = +proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) = var p = parseopt.initOptParser(cmd) while true: parseopt.next(p) @@ -526,7 +534,7 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) = of cmdEnd: break of cmdLongoption, cmdShortOption: case p.key.normalize - of "help": + of "help", "h": stdout.writeline(Usage) quit() of "port": @@ -539,17 +547,17 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) = of "cmdsug": gMode = mcmdsug gAddress = p.val - incl(gGlobalOptions, optIdeDebug) + incl(conf.globalOptions, optIdeDebug) of "cmdcon": gMode = mcmdcon gAddress = p.val - incl(gGlobalOptions, optIdeDebug) + incl(conf.globalOptions, optIdeDebug) of "epc": gMode = mepc - gVerbosity = 0 # Port number gotta be first. - of "debug": incl(gGlobalOptions, optIdeDebug) - of "v2": suggestVersion = 0 - of "v1": suggestVersion = 1 + conf.verbosity = 0 # Port number gotta be first. + of "debug": incl(conf.globalOptions, optIdeDebug) + of "v2": conf.suggestVersion = 0 + of "v1": conf.suggestVersion = 1 of "tester": gMode = mstdin gEmitEof = true @@ -561,67 +569,45 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) = else: gRefresh = true of "maxresults": - suggestMaxResults = parseInt(p.val) - else: processSwitch(pass, p) + conf.suggestMaxResults = parseInt(p.val) + else: processSwitch(pass, p, conf) of cmdArgument: let a = unixToNativePath(p.key) if dirExists(a) and not fileExists(a.addFileExt("nim")): - options.gProjectName = findProjectNimFile(a) + conf.projectName = findProjectNimFile(conf, a) # don't make it worse, report the error the old way: - if options.gProjectName.len == 0: options.gProjectName = a + if conf.projectName.len == 0: conf.projectName = a else: - options.gProjectName = a + conf.projectName = a # if processArgument(pass, p, argsCount): break -proc handleCmdLine(cache: IdentCache; config: ConfigRef) = +proc handleCmdLine(cache: IdentCache; conf: ConfigRef) = + let self = NimProg( + suggestMode: true, + processCmdLine: processCmdLine, + mainCommand: mainCommand + ) + self.initDefinesProg(conf, "nimsuggest") + if paramCount() == 0: stdout.writeline(Usage) - else: - processCmdLine(passCmd1, "") - if gMode != mstdin: - msgs.writelnHook = proc (msg: string) = discard - if gProjectName != "": - try: - gProjectFull = canonicalizePath(gProjectName) - except OSError: - gProjectFull = gProjectName - var p = splitFile(gProjectFull) - gProjectPath = canonicalizePath p.dir - gProjectName = p.name - else: - gProjectPath = canonicalizePath getCurrentDir() - - # Find Nim's prefix dir. - let binaryPath = findExe("nim") - if binaryPath == "": - raise newException(IOError, - "Cannot find Nim standard library: Nim compiler not in PATH") - gPrefixDir = binaryPath.splitPath().head.parentDir() - if not dirExists(gPrefixDir / "lib"): gPrefixDir = "" - - #msgs.writelnHook = proc (line: string) = log(line) - myLog("START " & gProjectFull) - - loadConfigs(DefaultConfig, cache, config) # load all config files - # now process command line arguments again, because some options in the - # command line can overwite the config file's settings - options.command = "nimsuggest" - let scriptFile = gProjectFull.changeFileExt("nims") - if fileExists(scriptFile): - runNimScript(cache, scriptFile, freshDefines=false, config) - # 'nim foo.nims' means to just run the NimScript file and do nothing more: - if scriptFile == gProjectFull: return - elif fileExists(gProjectPath / "config.nims"): - # directory wide NimScript file - runNimScript(cache, gProjectPath / "config.nims", freshDefines=false, config) - - extccomp.initVars() - processCmdLine(passCmd2, "") - - let graph = newModuleGraph(config) - graph.suggestMode = true - mainCommand(graph, cache) - -condsyms.initDefines() -defineSymbol "nimsuggest" + return + + self.processCmdLineAndProjectPath(conf) + + if gMode != mstdin: + conf.writelnHook = proc (msg: string) = discard + # Find Nim's prefix dir. + let binaryPath = findExe("nim") + if binaryPath == "": + raise newException(IOError, + "Cannot find Nim standard library: Nim compiler not in PATH") + conf.prefixDir = binaryPath.splitPath().head.parentDir() + if not dirExists(conf.prefixDir / "lib"): conf.prefixDir = "" + + #msgs.writelnHook = proc (line: string) = log(line) + myLog("START " & conf.projectFull) + + discard self.loadConfigsAndRunMainCommand(cache, conf) + handleCmdline(newIdentCache(), newConfigRef()) diff --git a/nimsuggest/nimsuggest.nim.cfg b/nimsuggest/nimsuggest.nim.cfg index 38e74b3c7..820db0dba 100644 --- a/nimsuggest/nimsuggest.nim.cfg +++ b/nimsuggest/nimsuggest.nim.cfg @@ -8,6 +8,8 @@ path:"$lib/packages/docutils" define:useStdoutAsStdmsg define:nimsuggest +define:nimcore + # die when nimsuggest uses more than 4GB: @if cpu32: define:"nimMaxHeap=2000" @@ -15,7 +17,6 @@ define:nimsuggest define:"nimMaxHeap=4000" @end -#cs:partial #define:useNodeIds #define:booting #define:noDocgen diff --git a/nimsuggest/tester.nim b/nimsuggest/tester.nim index 4cda272af..63095d490 100644 --- a/nimsuggest/tester.nim +++ b/nimsuggest/tester.nim @@ -7,9 +7,10 @@ import os, osproc, strutils, streams, re, sexp, net type Test = object - cmd, dest: string + filename, cmd, dest: string startup: seq[string] script: seq[(string, string)] + disabled: bool const curDir = when defined(windows): "" else: "" @@ -21,6 +22,7 @@ proc parseTest(filename: string; epcMode=false): Test = const cursorMarker = "#[!]#" let nimsug = curDir & addFileExt("nimsuggest", ExeExt) let libpath = findExe("nim").splitFile().dir /../ "lib" + result.filename = filename result.dest = getTempDir() / extractFilename(filename) result.cmd = nimsug & " --tester " & result.dest result.script = @[] @@ -42,7 +44,14 @@ proc parseTest(filename: string; epcMode=false): Test = if x.contains("""""""""): inc specSection elif specSection == 1: - if x.startsWith("$nimsuggest"): + if x.startsWith("disabled:"): + if x.startsWith("disabled:true"): + result.disabled = true + else: + # be strict about format + doAssert x.startsWith("disabled:false") + result.disabled = false + elif x.startsWith("$nimsuggest"): result.cmd = x % ["nimsuggest", nimsug, "file", filename, "lib", libpath] elif x.startsWith("!"): if result.cmd.len == 0: @@ -70,22 +79,22 @@ proc parseCmd(c: string): seq[string] = result = @[] var i = 0 var a = "" - while true: + while i < c.len: setLen(a, 0) # eat all delimiting whitespace - while c[i] in {' ', '\t', '\l', '\r'}: inc(i) + while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i) + if i >= c.len: break case c[i] of '"': raise newException(ValueError, "double quotes not yet supported: " & c) of '\'': var delim = c[i] inc(i) # skip ' or " - while c[i] != '\0' and c[i] != delim: + while i < c.len and c[i] != delim: add a, c[i] inc(i) - if c[i] != '\0': inc(i) - of '\0': break + if i < c.len: inc(i) else: - while c[i] > ' ': + while i < c.len and c[i] > ' ': add(a, c[i]) inc(i) add(result, a) @@ -223,8 +232,14 @@ proc doReport(filename, answer, resp: string; report: var string) = report.add "\n Expected: " & resp report.add "\n But got: " & answer +proc skipDisabledTest(test: Test): bool = + if test.disabled: + echo "disabled: " & test.filename + result = test.disabled + proc runEpcTest(filename: string): int = let s = parseTest(filename, true) + if s.skipDisabledTest: return 0 for cmd in s.startup: if not runCmd(cmd, s.dest): quit "invalid command: " & cmd @@ -267,6 +282,7 @@ proc runEpcTest(filename: string): int = proc runTest(filename: string): int = let s = parseTest filename + if s.skipDisabledTest: return 0 for cmd in s.startup: if not runCmd(cmd, s.dest): quit "invalid command: " & cmd diff --git a/nimsuggest/tests/tchk1.nim b/nimsuggest/tests/tchk1.nim index 7b0b5d5b4..2b60ed094 100644 --- a/nimsuggest/tests/tchk1.nim +++ b/nimsuggest/tests/tchk1.nim @@ -15,6 +15,7 @@ proc main = #[!]# discard """ +disabled:true $nimsuggest --tester $file >chk $1 chk;;skUnknown;;;;Hint;;???;;-1;;-1;;"tchk1 [Processing]";;0 diff --git a/nimsuggest/tests/tdot4.nim b/nimsuggest/tests/tdot4.nim index 25e77ed04..762534310 100644 --- a/nimsuggest/tests/tdot4.nim +++ b/nimsuggest/tests/tdot4.nim @@ -1,8 +1,9 @@ discard """ +disabled:true $nimsuggest --tester --maxresults:2 $file >sug $1 sug;;skProc;;tdot4.main;;proc (inp: string): string;;$file;;10;;5;;"";;100;;None -sug;;skProc;;strutils.replace;;proc (s: string, sub: string, by: string): string{.noSideEffect, gcsafe, locks: 0.};;$lib/pure/strutils.nim;;1575;;5;;"Replaces `sub` in `s` by the string `by`.";;100;;None +sug;;skProc;;strutils.replace;;proc (s: string, sub: string, by: string): string{.noSideEffect, gcsafe, locks: 0.};;$lib/pure/strutils.nim;;1506;;5;;"Replaces `sub` in `s` by the string `by`.";;100;;None """ import strutils diff --git a/nimsuggest/tests/tinclude.nim b/nimsuggest/tests/tinclude.nim index 12d40d1e7..0fda43911 100644 --- a/nimsuggest/tests/tinclude.nim +++ b/nimsuggest/tests/tinclude.nim @@ -1,7 +1,8 @@ discard """ +disabled:true $nimsuggest --tester compiler/nim.nim ->def compiler/semexprs.nim:13:50 -def;;skType;;ast.PSym;;PSym;;*ast.nim;;691;;2;;"";;100 ->def compiler/semexprs.nim:13:50 -def;;skType;;ast.PSym;;PSym;;*ast.nim;;691;;2;;"";;100 +>def compiler/semexprs.nim:25:50 +def;;skType;;ast.PSym;;PSym;;*ast.nim;;707;;2;;"";;100 +>def compiler/semexprs.nim:25:50 +def;;skType;;ast.PSym;;PSym;;*ast.nim;;707;;2;;"";;100 """ diff --git a/nimsuggest/tests/tstrutils.nim b/nimsuggest/tests/tstrutils.nim index ce3e29a15..9462c3d99 100644 --- a/nimsuggest/tests/tstrutils.nim +++ b/nimsuggest/tests/tstrutils.nim @@ -1,4 +1,5 @@ discard """ +disabled:true $nimsuggest --tester lib/pure/strutils.nim >def lib/pure/strutils.nim:2529:6 def;;skTemplate;;system.doAssert;;proc (cond: bool, msg: string): typed;;*/lib/system.nim;;*;;9;;"same as `assert` but is always turned on and not affected by the\x0A``--assertions`` command line switch.";;100 diff --git a/nimsuggest/tests/tsug_regression.nim b/nimsuggest/tests/tsug_regression.nim index ce66aafb2..1b23da806 100644 --- a/nimsuggest/tests/tsug_regression.nim +++ b/nimsuggest/tests/tsug_regression.nim @@ -17,6 +17,7 @@ proc main = map0.#[!]# discard """ +disabled:true $nimsuggest --tester $file >sug $1 sug;;skProc;;tables.getOrDefault;;proc (t: Table[getOrDefault.A, getOrDefault.B], key: A): B;;$lib/pure/collections/tables.nim;;178;;5;;"";;100;;None diff --git a/nimsuggest/tests/ttype_decl.nim b/nimsuggest/tests/ttype_decl.nim index 846eb7b1b..e66d39a1c 100644 --- a/nimsuggest/tests/ttype_decl.nim +++ b/nimsuggest/tests/ttype_decl.nim @@ -1,4 +1,5 @@ discard """ +disabled:true $nimsuggest --tester --maxresults:3 $file >sug $1 sug;;skType;;ttype_decl.Other;;Other;;$file;;10;;2;;"";;0;;None diff --git a/nimsuggest/tests/twithin_macro.nim b/nimsuggest/tests/twithin_macro.nim index e0df03542..9c36ffd0f 100644 --- a/nimsuggest/tests/twithin_macro.nim +++ b/nimsuggest/tests/twithin_macro.nim @@ -202,6 +202,7 @@ echo r.age_human_yrs() echo r discard """ +disabled:true $nimsuggest --tester $file >sug $1 sug;;skField;;age;;int;;$file;;167;;6;;"";;100;;None diff --git a/nimsuggest/tests/twithin_macro_prefix.nim b/nimsuggest/tests/twithin_macro_prefix.nim index 86e406c5d..1402b762a 100644 --- a/nimsuggest/tests/twithin_macro_prefix.nim +++ b/nimsuggest/tests/twithin_macro_prefix.nim @@ -202,6 +202,7 @@ echo r.age_human_yrs() echo r discard """ +disabled:true $nimsuggest --tester $file >sug $1 sug;;skField;;age;;int;;$file;;167;;6;;"";;100;;Prefix diff --git a/readme.md b/readme.md index ebcf5f802..e4fb52bd0 100644 --- a/readme.md +++ b/readme.md @@ -16,6 +16,8 @@ the latest release, check out [Nim's website][nim-site]. Also where most development decisions get made. * [Gitter][nim-gitter] - an additional place to discuss Nim in real-time. There is a bridge between Gitter and the IRC channel. +* [Telegram][nim-telegram] - an additional place to discuss Nim in real-time. There + is the official Telegram channel. * [Stack Overflow][nim-stackoverflow] - a popular Q/A site for programming related topics that includes posts about Nim. * [Github Wiki][nim-wiki] - Misc user-contributed content. @@ -48,19 +50,33 @@ Next, to build from source you will need: other distros as well). Then, if you are on a \*nix system or Windows, the following steps should compile -Nim from source using ``gcc``, ``git`` and the ``koch`` build tool (in the place -of ``sh build.sh`` you should substitute ``build.bat`` on x86 Windows or -``build64.bat`` on x86_64 Windows): +Nim from source using ``gcc``, ``git`` and the ``koch`` build tool. + +**Note: The following commands are for the development version of the compiler.** +For most users, installing the latest stable version is enough. Check out +the installation instructions on the website to do so: https://nim-lang.org/install.html. ``` +# step 1: git clone https://github.com/nim-lang/Nim.git cd Nim + +# step 2 (posix) clones `csources.git`, bootstraps Nim compiler and compiles tools +sh build_all.sh + +# step 2 (windows) git clone --depth 1 https://github.com/nim-lang/csources.git + cd csources -sh build.sh -cd ../ -bin/nim c koch -./koch boot -d:release +# requires `gcc` in your PATH, see also https://nim-lang.org/install_windows.html +build.bat # x86 Windows +build64.bat # x86_64 Windows +cd .. + +bin\nim c koch +koch boot -d:release +koch tools # Compile Nimble and other tools +# end of step 2 (windows) ``` Finally, once you have finished the build steps (on Windows, Mac or Linux) you @@ -80,11 +96,9 @@ For more information on the ``koch`` build tool please see the documentation within the [doc/koch.rst](doc/koch.rst) file. ## Nimble -``nimble`` is Nim's package manager and it can be acquired from the -[``nim-lang/nimble``][nimble-repo] repository. Assuming that you added Nim's -``bin`` directory to your PATH, you may install Nimble from source by running -``koch nimble`` within the root of the cloned repository. +``nimble`` is Nim's package manager. To learn more about it, see the +[``nim-lang/nimble``][nimble-repo] repository. ## Contributors @@ -177,7 +191,7 @@ Nim. You are explicitly permitted to develop commercial applications using Nim. Please read the [copying.txt](copying.txt) file for more details. -Copyright © 2006-2017 Andreas Rumpf, all rights reserved. +Copyright © 2006-2018 Andreas Rumpf, all rights reserved. [nim-site]: https://nim-lang.org [nim-forum]: https://forum.nim-lang.org @@ -189,6 +203,7 @@ Copyright © 2006-2017 Andreas Rumpf, all rights reserved. [nim-stackoverflow]: https://stackoverflow.com/questions/tagged/nim [nim-stackoverflow-newest]: https://stackoverflow.com/questions/tagged/nim?sort=newest&pageSize=15 [nim-gitter]: https://gitter.im/nim-lang/Nim +[nim-telegram]: https://t.me/nim_lang [nim-bountysource]: https://www.bountysource.com/teams/nim [nim-bitcoin]: https://blockchain.info/address/1BXfuKM2uvoD6mbx4g5xM3eQhLzkCK77tJ [nimble-repo]: https://github.com/nim-lang/nimble diff --git a/readme.txt b/readme.txt index 7f9f50da3..8c6f25b29 100644 --- a/readme.txt +++ b/readme.txt @@ -14,5 +14,5 @@ allowing you to create commercial applications. Read copying.txt for more details. -Copyright (c) 2006-2016 Andreas Rumpf. +Copyright (c) 2006-2018 Andreas Rumpf. All rights reserved. diff --git a/tests/arithm/tashr.nim b/tests/arithm/tashr.nim new file mode 100644 index 000000000..aeb3b6843 --- /dev/null +++ b/tests/arithm/tashr.nim @@ -0,0 +1,46 @@ +discard """ + output: '''''' + targets: '''c js''' +""" + +# issue #6255, feature request +# arithmetic right shift + +var x1 = -123'i8 +var x2 = -123'i16 +var x3 = -123'i32 +var x4 = -123'i64 +var x5 = -123 + +block codegen_test: + doAssert ashr(x1, 1) == -62 + doAssert ashr(x2, 1) == -62 + doAssert ashr(x3, 1) == -62 + doAssert ashr(x4, 1) == -62 + doAssert ashr(x5, 1) == -62 + +block semfold_test: + doAssert ashr(-123'i8 , 1) == -62 + doAssert ashr(-123'i16, 1) == -62 + doAssert ashr(-123'i32, 1) == -62 + doAssert ashr(-123'i64, 1) == -62 + doAssert ashr(-123 , 1) == -62 + +static: # VM test + doAssert ashr(-123'i8 , 1) == -62 + doAssert ashr(-123'i16, 1) == -62 + doAssert ashr(-123'i32, 1) == -62 + doAssert ashr(-123'i64, 1) == -62 + doAssert ashr(-123 , 1) == -62 + + var y1 = -123'i8 + var y2 = -123'i16 + var y3 = -123'i32 + var y4 = -123'i64 + var y5 = -123 + + doAssert ashr(y1, 1) == -62 + doAssert ashr(y2, 1) == -62 + doAssert ashr(y3, 1) == -62 + doAssert ashr(y4, 1) == -62 + doAssert ashr(y5, 1) == -62 diff --git a/tests/arithm/tcast.nim b/tests/arithm/tcast.nim new file mode 100644 index 000000000..4017ed1c5 --- /dev/null +++ b/tests/arithm/tcast.nim @@ -0,0 +1,95 @@ +discard """ + output: ''' +B0 +B1 +B2 +B3 +B4 +B5 +B6 +''' +""" + +template crossCheck(ty: untyped, exp: untyped) = + let rt = ty(exp) + const ct = ty(exp) + if $rt != $ct: + echo "Got ", ct + echo "Expected ", rt + +template add1(x: uint8): untyped = x + 1 +template add1(x: uint16): untyped = x + 1 +template add1(x: uint32): untyped = x + 1 + +template sub1(x: uint8): untyped = x - 1 +template sub1(x: uint16): untyped = x - 1 +template sub1(x: uint32): untyped = x - 1 + +block: + when true: + echo "B0" + crossCheck(int8, 0'i16 - 5'i16) + crossCheck(int16, 0'i32 - 5'i32) + crossCheck(int32, 0'i64 - 5'i64) + + echo "B1" + crossCheck(uint8, 0'u8 - 5'u8) + crossCheck(uint16, 0'u16 - 5'u16) + crossCheck(uint32, 0'u32 - 5'u32) + crossCheck(uint64, 0'u64 - 5'u64) + + echo "B2" + crossCheck(uint8, uint8.high + 5'u8) + crossCheck(uint16, uint16.high + 5'u16) + crossCheck(uint32, uint32.high + 5'u32) + crossCheck(uint64, (-1).uint64 + 5'u64) + + echo "B3" + doAssert $sub1(0'u8) == "255" + doAssert $sub1(0'u16) == "65535" + doAssert $sub1(0'u32) == "4294967295" + + echo "B4" + doAssert $add1(255'u8) == "0" + doAssert $add1(65535'u16) == "0" + doAssert $add1(4294967295'u32) == "0" + + echo "B5" + crossCheck(int32, high(int32)) + crossCheck(int32, high(int32).int32) + crossCheck(int32, low(int32)) + crossCheck(int32, low(int32).int32) + crossCheck(int64, high(int8).int16.int32.int64) + crossCheck(int64, low(int8).int16.int32.int64) + + echo "B6" + crossCheck(int64, 0xFFFFFFFFFFFFFFFF'u64) + crossCheck(int32, 0xFFFFFFFFFFFFFFFF'u64) + crossCheck(int16, 0xFFFFFFFFFFFFFFFF'u64) + crossCheck(int8 , 0xFFFFFFFFFFFFFFFF'u64) + + # Out of range conversion, caught for `let`s only + # crossCheck(int8, 0'u8 - 5'u8) + # crossCheck(int16, 0'u16 - 5'u16) + # crossCheck(int32, 0'u32 - 5'u32) + # crossCheck(int64, 0'u64 - 5'u64) + + # crossCheck(int8, 0'u16 - 129'u16) + # crossCheck(uint8, 0'i16 + 257'i16) + + # Signed integer {under,over}flow is guarded against + + # crossCheck(int8, int8.high + 5'i8) + # crossCheck(int16, int16.high + 5'i16) + # crossCheck(int32, int32.high + 5'i32) + # crossCheck(int64, int64.high + 5'i64) + + # crossCheck(int8, int8.low - 5'i8) + # crossCheck(int16, int16.low - 5'i16) + # crossCheck(int32, int32.low - 5'i32) + # crossCheck(int64, int64.low - 5'i64) + + # crossCheck(uint8, 0'i8 - 5'i8) + # crossCheck(uint16, 0'i16 - 5'i16) + # crossCheck(uint32, 0'i32 - 5'i32) + # crossCheck(uint64, 0'i64 - 5'i64) diff --git a/tests/arithm/tnot.nim b/tests/arithm/tnot.nim new file mode 100644 index 000000000..6a4877b2c --- /dev/null +++ b/tests/arithm/tnot.nim @@ -0,0 +1,58 @@ +discard """ + output: ''' +-5 +-5 +-5 +-5 +4 +4 +4 +4 +251 +65531 +4294967291 +18446744073709551611 +4 +4 +4 +4 +''' +""" + +# Signed types +block: + const t0: int8 = not 4 + const t1: int16 = not 4 + const t2: int32 = not 4 + const t3: int64 = not 4 + const t4: int8 = not -5 + const t5: int16 = not -5 + const t6: int32 = not -5 + const t7: int64 = not -5 + echo t0 + echo t1 + echo t2 + echo t3 + echo t4 + echo t5 + echo t6 + echo t7 + +# Unsigned types +block: + const t0: uint8 = not 4'u8 + const t1: uint16 = not 4'u16 + const t2: uint32 = not 4'u32 + const t3: uint64 = not 4'u64 + const t4: uint8 = not 251'u8 + const t5: uint16 = not 65531'u16 + const t6: uint32 = not 4294967291'u32 + const t7: uint64 = not 18446744073709551611'u64 + echo t0 + echo t1 + echo t2 + echo t3 + echo t4 + echo t5 + echo t6 + echo t7 diff --git a/tests/arithm/tshl.nim b/tests/arithm/tshl.nim new file mode 100644 index 000000000..0aa46d021 --- /dev/null +++ b/tests/arithm/tshl.nim @@ -0,0 +1,34 @@ +discard """ + output: ''' +0 +0 +1 +1 +0 +0 +0 +1 +''' +""" + +# Signed types +block: + const t0: int8 = 1'i8 shl 8 + const t1: int16 = 1'i16 shl 16 + const t2: int32 = 1'i32 shl 32 + const t3: int64 = 1'i64 shl 64 + echo t0 + echo t1 + echo t2 + echo t3 + +# Unsigned types +block: + const t0: uint8 = 1'u8 shl 8 + const t1: uint16 = 1'u16 shl 16 + const t2: uint32 = 1'u32 shl 32 + const t3: uint64 = 1'u64 shl 64 + echo t0 + echo t1 + echo t2 + echo t3 diff --git a/tests/array/t7818.nim b/tests/array/t7818.nim new file mode 100644 index 000000000..4e43bff85 --- /dev/null +++ b/tests/array/t7818.nim @@ -0,0 +1,132 @@ +discard """ + output: "OK" +""" + +# bug #7818 +# this is not a macro bug, but array construction bug +# I use macro to avoid object slicing +# see #7712 and #7637 +import macros + +type + Vehicle[T] = object of RootObj + tire: T + Car[T] = object of Vehicle[T] + Bike[T] = object of Vehicle[T] + +macro peek(n: typed): untyped = + let val = getTypeImpl(n).treeRepr + newLit(val) + +block test_t7818: + var v = Vehicle[int](tire: 3) + var c = Car[int](tire: 4) + var b = Bike[int](tire: 2) + + let y = peek([c, b, v]) + let z = peek([v, c, b]) + doAssert(y == z) + +block test_t7906_1: + proc init(x: typedesc, y: int): ref x = + result = new(ref x) + result.tire = y + + var v = init(Vehicle[int], 3) + var c = init(Car[int], 4) + var b = init(Bike[int], 2) + + let y = peek([c, b, v]) + let z = peek([v, c, b]) + doAssert(y == z) + +block test_t7906_2: + var v = Vehicle[int](tire: 3) + var c = Car[int](tire: 4) + var b = Bike[int](tire: 2) + + let y = peek([c.addr, b.addr, v.addr]) + let z = peek([v.addr, c.addr, b.addr]) + doAssert(y == z) + +block test_t7906_3: + type + Animal[T] = object of RootObj + hair: T + Mammal[T] = object of Animal[T] + Monkey[T] = object of Mammal[T] + + var v = Animal[int](hair: 3) + var c = Mammal[int](hair: 4) + var b = Monkey[int](hair: 2) + + let z = peek([c.addr, b.addr, v.addr]) + let y = peek([v.addr, c.addr, b.addr]) + doAssert(y == z) + +type + Fruit[T] = ref object of RootObj + color: T + Apple[T] = ref object of Fruit[T] + Banana[T] = ref object of Fruit[T] + +proc testArray[T](x: array[3, Fruit[T]]): string = + result = "" + for c in x: + result.add $c.color + +proc testOpenArray[T](x: openArray[Fruit[T]]): string = + result = "" + for c in x: + result.add $c.color + +block test_t7906_4: + var v = Fruit[int](color: 3) + var c = Apple[int](color: 4) + var b = Banana[int](color: 2) + + let y = peek([c, b, v]) + let z = peek([v, c, b]) + doAssert(y == z) + +block test_t7906_5: + var a = Fruit[int](color: 1) + var b = Apple[int](color: 2) + var c = Banana[int](color: 3) + + doAssert(testArray([a, b, c]) == "123") + doAssert(testArray([b, c, a]) == "231") + + doAssert(testOpenArray([a, b, c]) == "123") + doAssert(testOpenArray([b, c, a]) == "231") + + doAssert(testOpenArray(@[a, b, c]) == "123") + doAssert(testOpenArray(@[b, c, a]) == "231") + +proc testArray[T](x: array[3, ptr Vehicle[T]]): string = + result = "" + for c in x: + result.add $c.tire + +proc testOpenArray[T](x: openArray[ptr Vehicle[T]]): string = + result = "" + for c in x: + result.add $c.tire + +block test_t7906_6: + var u = Vehicle[int](tire: 1) + var v = Bike[int](tire: 2) + var w = Car[int](tire: 3) + + doAssert(testArray([u.addr, v.addr, w.addr]) == "123") + doAssert(testArray([w.addr, u.addr, v.addr]) == "312") + + doAssert(testOpenArray([u.addr, v.addr, w.addr]) == "123") + doAssert(testOpenArray([w.addr, u.addr, v.addr]) == "312") + + doAssert(testOpenArray(@[u.addr, v.addr, w.addr]) == "123") + doAssert(testOpenArray(@[w.addr, u.addr, v.addr]) == "312") + +echo "OK" + + diff --git a/tests/array/tarray.nim b/tests/array/tarray.nim index 95d1bb7cc..5e947e745 100644 --- a/tests/array/tarray.nim +++ b/tests/array/tarray.nim @@ -46,3 +46,7 @@ let arr3: array[0, string] = [] doAssert(arr1.len == 0) doAssert(arr2.len == 0) doAssert(arr3.len == 0) + +# Negative array length is not allowed (#6852) +doAssert(not compiles(block: + var arr: array[-1, int])) \ No newline at end of file diff --git a/tests/array/tarraycons_ptr_generic.nim b/tests/array/tarraycons_ptr_generic.nim new file mode 100644 index 000000000..eb89a196f --- /dev/null +++ b/tests/array/tarraycons_ptr_generic.nim @@ -0,0 +1,51 @@ +discard """ + output: '''apple +banana +Fruit +2 +4 +3 +none +skin +paper +''' +""" +type + Fruit = object of RootObj + name: string + Apple = object of Fruit + Banana = object of Fruit + +var + ir = Fruit(name: "Fruit") + ia = Apple(name: "apple") + ib = Banana(name: "banana") + +let x = [ia.addr, ib.addr, ir.addr] +for c in x: echo c.name + +type + Vehicle[T] = object of RootObj + tire: T + Car[T] = object of Vehicle[T] + Bike[T] = object of Vehicle[T] + +var v = Vehicle[int](tire: 3) +var c = Car[int](tire: 4) +var b = Bike[int](tire: 2) + +let y = [b.addr, c.addr, v.addr] +for c in y: echo c.tire + +type + Book[T] = ref object of RootObj + cover: T + Hard[T] = ref object of Book[T] + Soft[T] = ref object of Book[T] + +var bn = Book[string](cover: "none") +var hs = Hard[string](cover: "skin") +var bp = Soft[string](cover: "paper") + +let z = [bn, hs, bp] +for c in z: echo c.cover diff --git a/tests/array/tarraycons_ptr_generic2.nim b/tests/array/tarraycons_ptr_generic2.nim new file mode 100644 index 000000000..fce7af669 --- /dev/null +++ b/tests/array/tarraycons_ptr_generic2.nim @@ -0,0 +1,17 @@ +discard """ + file: "tarraycons_ptr_generic2.nim" + line: 17 + errormsg: "type mismatch: got <ptr Hard[system.string]> but expected 'Book[system.string]'" +""" + +type + Book[T] = ref object of RootObj + cover: T + Hard[T] = ref object of Book[T] + Soft[T] = ref object of Book[T] + +var bn = Book[string](cover: "none") +var hs = Hard[string](cover: "skin") +var bp = Soft[string](cover: "paper") + +let z = [bn, hs.addr, bp] diff --git a/tests/array/tarrindx.nim b/tests/array/tarrindx.nim index 3bb6b0148..9e8ec31b6 100644 --- a/tests/array/tarrindx.nim +++ b/tests/array/tarrindx.nim @@ -1,6 +1,8 @@ discard """ output: '''0 -0''' +0 +bc +bcdefg''' """ # test another strange bug ... (I hate this compiler; it is much too buggy!) @@ -29,3 +31,13 @@ var echo obj.s1[0] echo obj.s1[0u] + + +# bug #8049 + +when true: + type ustring* = distinct string + converter toUString*(s: string): ustring = ustring(s) + proc `[]`*(s: ustring, i: int): ustring = s + echo "abcdefgh"[1..2] + echo "abcdefgh"[1..^2] diff --git a/tests/assert/tassert.nim b/tests/assert/tassert.nim index b3df30bd1..b5f2fb715 100644 --- a/tests/assert/tassert.nim +++ b/tests/assert/tassert.nim @@ -11,7 +11,7 @@ proc callC() = callA() try: callC() -except EAssertionFailed: +except AssertionError: write(stdout, "assertion failure!") except: write(stdout, "unknown exception!") diff --git a/tests/assert/testhelper.nim b/tests/assert/testhelper.nim new file mode 100644 index 000000000..754d562ec --- /dev/null +++ b/tests/assert/testhelper.nim @@ -0,0 +1,12 @@ +from strutils import endsWith, split +from ospaths import isAbsolute + +proc checkMsg*(msg, expectedEnd, name: string)= + let filePrefix = msg.split(' ', maxSplit = 1)[0] + if not filePrefix.isAbsolute: + echo name, ":not absolute: `", msg & "`" + elif not msg.endsWith expectedEnd: + echo name, ":expected suffix:\n`" & expectedEnd & "`\ngot:\n`" & msg & "`" + else: + echo name, ":ok" + diff --git a/tests/assert/tfailedassert.nim b/tests/assert/tfailedassert.nim index f0ca149f8..c3231bb8d 100644 --- a/tests/assert/tfailedassert.nim +++ b/tests/assert/tfailedassert.nim @@ -1,20 +1,65 @@ discard """ output: ''' -WARNING: false first assertion from bar -ERROR: false second assertion from bar +test1:ok +test2:ok +test3:ok +test4:ok +test5:ok +test6:ok +test7:ok -1 -tfailedassert.nim:27 false assertion from foo +tfailedassert.nim +test7:ok ''' """ -type - TLineInfo = tuple[filename: string, line: int] +import testhelper +type + TLineInfo = tuple[filename: string, line: int, column: int] TMyError = object of Exception lineinfo: TLineInfo - EMyError = ref TMyError +echo("") + + +# NOTE: when entering newlines, adjust `expectedEnd` ouptuts + +try: + doAssert(false, "msg1") # doAssert test +except AssertionError as e: + checkMsg(e.msg, "tfailedassert.nim(30, 11) `false` msg1", "test1") + +try: + assert false, "msg2" # assert test +except AssertionError as e: + checkMsg(e.msg, "tfailedassert.nim(35, 10) `false` msg2", "test2") + +try: + assert false # assert test with no msg +except AssertionError as e: + checkMsg(e.msg, "tfailedassert.nim(40, 10) `false` ", "test3") + +try: + let a = 1 + doAssert(a+a==1) # assert test with Ast expression + # BUG: const folding would make "1+1==1" appear as `false` in + # assert message +except AssertionError as e: + checkMsg(e.msg, "`a + a == 1` ", "test4") + +try: + let a = 1 + doAssert a+a==1 # ditto with `doAssert` and no parens +except AssertionError as e: + checkMsg(e.msg, "`a + a == 1` ", "test5") + +proc fooStatic() = + # protect against https://github.com/nim-lang/Nim/issues/8758 + static: doAssert(true) +fooStatic() + # module-wide policy to change the failed assert # exception type in order to include a lineinfo onFailedAssert(msg): @@ -26,26 +71,26 @@ onFailedAssert(msg): proc foo = assert(false, "assertion from foo") + proc bar: int = - # local overrides that are active only - # in this proc - onFailedAssert(msg): echo "WARNING: " & msg + # local overrides that are active only in this proc + onFailedAssert(msg): + checkMsg(msg, "tfailedassert.nim(80, 9) `false` first assertion from bar", "test6") assert(false, "first assertion from bar") onFailedAssert(msg): - echo "ERROR: " & msg + checkMsg(msg, "tfailedassert.nim(86, 9) `false` second assertion from bar", "test7") return -1 assert(false, "second assertion from bar") return 10 -echo("") echo(bar()) try: foo() except: let e = EMyError(getCurrentException()) - echo e.lineinfo.filename, ":", e.lineinfo.line, " ", e.msg - + echo e.lineinfo.filename + checkMsg(e.msg, "tfailedassert.nim(72, 9) `false` assertion from foo", "test7") diff --git a/tests/assert/tfaileddoassert.nim b/tests/assert/tfaileddoassert.nim new file mode 100644 index 000000000..e1245f578 --- /dev/null +++ b/tests/assert/tfaileddoassert.nim @@ -0,0 +1,20 @@ +discard """ + cmd: "nim $target -d:release $options $file" + output: ''' +test1:ok +test2:ok +''' +""" + +import testhelper + +onFailedAssert(msg): + checkMsg(msg, "tfaileddoassert.nim(15, 9) `a == 2` foo", "test1") + +var a = 1 +doAssert(a == 2, "foo") + +onFailedAssert(msg): + checkMsg(msg, "tfaileddoassert.nim(20, 10) `a == 3` ", "test2") + +doAssert a == 3 diff --git a/tests/assign/tobjasgn.nim b/tests/assign/tobjasgn.nim index e731d9e93..adfcfb087 100644 --- a/tests/assign/tobjasgn.nim +++ b/tests/assign/tobjasgn.nim @@ -8,7 +8,7 @@ assignment test a:test b:1 c:2 haha:3 # bug #1005 type - TSomeObj = object of TObject + TSomeObj = object of RootObj a, b: int PSomeObj = ref object a, b: int @@ -20,7 +20,7 @@ echo a.a, " ", b.a, " ", a.b, " ", b.b # bug #575 type - Something = object of Tobject + Something = object of RootObj a: string b, c: int32 diff --git a/tests/assign/toverload_asgn1.nim b/tests/assign/toverload_asgn1.nim index dbc3a71c4..01e7e7aa7 100644 --- a/tests/assign/toverload_asgn1.nim +++ b/tests/assign/toverload_asgn1.nim @@ -14,6 +14,7 @@ GenericT[T] '=' bool GenericT[T] '=' bool GenericT[T] '=' bool GenericT[T] '=' bool''' + disabled: "true" """ import typetraits diff --git a/tests/assign/toverload_asgn2.nim b/tests/assign/toverload_asgn2.nim index 243c90494..1104be92b 100644 --- a/tests/assign/toverload_asgn2.nim +++ b/tests/assign/toverload_asgn2.nim @@ -1,6 +1,7 @@ discard """ output: '''i value 88 2aa''' + disabled: "true" """ import moverload_asgn2 diff --git a/tests/async/hello.txt b/tests/async/hello.txt new file mode 100644 index 000000000..854d6c20a --- /dev/null +++ b/tests/async/hello.txt @@ -0,0 +1 @@ +hello humans! \ No newline at end of file diff --git a/tests/async/t6100.nim b/tests/async/t6100.nim deleted file mode 100644 index b4dc0f146..000000000 --- a/tests/async/t6100.nim +++ /dev/null @@ -1,15 +0,0 @@ -discard """ - file: "t6100.nim" - exitcode: 0 - output: "10000000" -""" -import asyncdispatch - -let done = newFuture[int]() -done.complete(1) - -proc asyncSum: Future[int] {.async.} = - for _ in 1..10_000_000: - result += await done - -echo waitFor asyncSum() \ No newline at end of file diff --git a/tests/async/t6846.nim b/tests/async/t6846.nim new file mode 100644 index 000000000..687a3f865 --- /dev/null +++ b/tests/async/t6846.nim @@ -0,0 +1,16 @@ +discard """ + exitcode: 0 + output: "hello world" + disabled: windows +""" + +import asyncdispatch +import asyncfile +import times + +var asyncStdout = 1.AsyncFD.newAsyncFile() +proc doStuff: Future[void] {.async.} = + await asyncStdout.write "hello world\n" + +let fut = doStuff() +doAssert fut.finished, "Poll is needed unnecessarily. See #6846." \ No newline at end of file diff --git a/tests/async/t7758.nim b/tests/async/t7758.nim new file mode 100644 index 000000000..102a4ce4c --- /dev/null +++ b/tests/async/t7758.nim @@ -0,0 +1,19 @@ +discard """ + file: "t7758.nim" + exitcode: 0 +""" +import asyncdispatch + +proc task() {.async.} = + await sleepAsync(40) + +proc main() = + var counter = 0 + var f = task() + while not f.finished: + inc(counter) + poll(10) + + doAssert counter <= 4 + +for i in 0 .. 10: main() \ No newline at end of file diff --git a/tests/async/tasync_in_seq_constr.nim b/tests/async/tasync_in_seq_constr.nim index 46ad74451..3d6dae245 100644 --- a/tests/async/tasync_in_seq_constr.nim +++ b/tests/async/tasync_in_seq_constr.nim @@ -1,18 +1,25 @@ discard """ - errormsg: "invalid control flow: 'yield' within a constructor" - line: 16 + output: ''' +@[1, 2, 3, 4] +123 +''' """ # bug #5314, bug #6626 import asyncdispatch -proc bar(): Future[int] {.async.} = - await sleepAsync(500) - result = 3 +proc bar(i: int): Future[int] {.async.} = + await sleepAsync(2) + result = i proc foo(): Future[seq[int]] {.async.} = - await sleepAsync(500) - result = @[1, 2, await bar(), 4] # <--- The bug is here + await sleepAsync(2) + result = @[1, 2, await bar(3), 4] # <--- The bug is here + +proc foo2() {.async.} = + await sleepAsync(2) + echo(await bar(1), await bar(2), await bar(3)) echo waitFor foo() +waitFor foo2() diff --git a/tests/async/tasync_misc.nim b/tests/async/tasync_misc.nim new file mode 100644 index 000000000..dbc2ea434 --- /dev/null +++ b/tests/async/tasync_misc.nim @@ -0,0 +1,46 @@ +discard """ + exitcode: 0 + output: "ok" +""" + +import json, asyncdispatch +block: #6100 + let done = newFuture[int]() + done.complete(1) + + proc asyncSum: Future[int] {.async.} = + for _ in 1..10_000_000: + result += await done + + let res = waitFor asyncSum() + doAssert(res == 10000000) + +block: #7985 + proc getData(): Future[JsonNode] {.async.} = + result = %*{"value": 1} + + type + MyData = object + value: BiggestInt + + proc main() {.async.} = + let data = to(await(getData()), MyData) + doAssert($data == "(value: 1)") + + waitFor(main()) + +block: #8399 + proc bar(): Future[string] {.async.} = discard + + proc foo(line: string) {.async.} = + var res = + case line[0] + of '+', '-': @[] + of '$': (let x = await bar(); @[""]) + else: @[] + + doAssert(res == @[""]) + + waitFor foo("$asd") + +echo "ok" diff --git a/tests/async/tasync_traceback.nim b/tests/async/tasync_traceback.nim index e4c8a67b3..b6c6a916b 100644 --- a/tests/async/tasync_traceback.nim +++ b/tests/async/tasync_traceback.nim @@ -3,7 +3,7 @@ discard """ disabled: "windows" output: "Matched" """ -import asyncdispatch +import asyncdispatch, strutils # Tests to ensure our exception trace backs are friendly. @@ -82,7 +82,7 @@ Async traceback: asyncmacro\.nim\(\d+?\)\s+?a asyncmacro\.nim\(\d+?\)\s+?a_continue ## Resumes an async procedure - asyncmacro\.nim\(\d+?\)\s+?aIter + tasync_traceback\.nim\(\d+?\)\s+?aIter asyncfutures\.nim\(\d+?\)\s+?read \]# Exception message: b failure @@ -110,17 +110,33 @@ Async traceback: ## Executes pending callbacks asyncmacro\.nim\(\d+?\)\s+?foo_continue ## Resumes an async procedure - asyncmacro\.nim\(\d+?\)\s+?fooIter + tasync_traceback\.nim\(\d+?\)\s+?fooIter asyncfutures\.nim\(\d+?\)\s+?read \]# Exception message: bar failure Exception type: """ -if result.match(re(expected)): - echo("Matched") -else: - echo("Not matched!") +let resLines = splitLines(result.strip) +let expLines = splitLines(expected.strip) + +if resLines.len != expLines.len: + echo("Not matched! Wrong number of lines!") echo() echo(result) quit(QuitFailure) + +var ok = true +for i in 0 ..< resLines.len: + if not resLines[i].match(re(expLines[i])): + echo "Not matched! Line ", i + 1 + echo "Expected:" + echo expLines[i] + echo "Actual:" + echo resLines[i] + ok = false + +if ok: + echo("Matched") +else: + quit(QuitFailure) diff --git a/tests/async/tasyncawait.nim b/tests/async/tasyncawait.nim index 9fe9507ad..74933f063 100644 --- a/tests/async/tasyncawait.nim +++ b/tests/async/tasyncawait.nim @@ -12,11 +12,11 @@ const var clientCount = 0 -proc sendMessages(client: TAsyncFD) {.async.} = +proc sendMessages(client: AsyncFD) {.async.} = for i in 0 .. <messagesToSend: await send(client, "Message " & $i & "\c\L") -proc launchSwarm(port: TPort) {.async.} = +proc launchSwarm(port: Port) {.async.} = for i in 0 .. <swarmSize: var sock = newAsyncNativeSocket() @@ -24,7 +24,7 @@ proc launchSwarm(port: TPort) {.async.} = await sendMessages(sock) closeSocket(sock) -proc readMessages(client: TAsyncFD) {.async.} = +proc readMessages(client: AsyncFD) {.async.} = while true: var line = await recvLine(client) if line == "": @@ -37,7 +37,7 @@ proc readMessages(client: TAsyncFD) {.async.} = else: doAssert false -proc createServer(port: TPort) {.async.} = +proc createServer(port: Port) {.async.} = var server = newAsyncNativeSocket() block: var name: Sockaddr_in @@ -55,8 +55,8 @@ proc createServer(port: TPort) {.async.} = while true: asyncCheck readMessages(await accept(server)) -asyncCheck createServer(TPort(10335)) -asyncCheck launchSwarm(TPort(10335)) +asyncCheck createServer(Port(10335)) +asyncCheck launchSwarm(Port(10335)) while true: poll() if clientCount == swarmSize: break diff --git a/tests/async/tasyncexceptions.nim b/tests/async/tasyncexceptions.nim index efe31ef27..7aa1d7fb0 100644 --- a/tests/async/tasyncexceptions.nim +++ b/tests/async/tasyncexceptions.nim @@ -20,7 +20,7 @@ proc processClient(fd: int) {.async.} = var line = await recvLine(fd) var foo = line[0] if foo == 'g': - raise newException(EBase, "foobar") + raise newException(Exception, "foobar") proc serve() {.async.} = diff --git a/tests/async/tasyncfile.nim b/tests/async/tasyncfile.nim index aa7f03ab1..c7b71a2f7 100644 --- a/tests/async/tasyncfile.nim +++ b/tests/async/tasyncfile.nim @@ -1,4 +1,8 @@ discard """ + output: '''13 +hello humans! +13 +''' file: "tasyncfile.nim" exitcode: 0 """ @@ -48,5 +52,12 @@ proc main() {.async.} = doAssert data == "t3" file.close() + # Issue #7347 + block: + let appDir = getAppDir() + var file = openAsync(appDir & DirSep & "hello.txt") + echo file.getFileSize() + echo await file.readAll() + echo file.getFilePos() waitFor main() diff --git a/tests/async/tasyncssl.nim b/tests/async/tasyncssl.nim index 3dc131036..0607cf3c6 100644 --- a/tests/async/tasyncssl.nim +++ b/tests/async/tasyncssl.nim @@ -15,11 +15,11 @@ when defined(ssl): var clientCount = 0 proc sendMessages(client: AsyncSocket) {.async.} = - for i in 0 .. <messagesToSend: + for i in 0 ..< messagesToSend: await send(client, "Message " & $i & "\c\L") proc launchSwarm(port: Port) {.async.} = - for i in 0 .. <swarmSize: + for i in 0 ..< swarmSize: var sock = newAsyncSocket() var clientContext = newContext(verifyMode = CVerifyNone) clientContext.wrapSocket(sock) diff --git a/tests/async/tasynctry.nim b/tests/async/tasynctry.nim index 5930f296f..0fe9efdc1 100644 --- a/tests/async/tasynctry.nim +++ b/tests/async/tasynctry.nim @@ -9,43 +9,43 @@ Multiple except branches Multiple except branches 2 ''' """ -import asyncdispatch +import asyncdispatch, strutils # Here we are testing the ability to catch exceptions. proc foobar() {.async.} = if 5 == 5: - raise newException(EInvalidIndex, "Test") + raise newException(IndexError, "Test") proc catch() {.async.} = # TODO: Create a test for when exceptions are not caught. try: await foobar() except: - echo("Generic except: ", getCurrentExceptionMsg()) + echo("Generic except: ", getCurrentExceptionMsg().splitLines[0]) try: await foobar() - except EInvalidIndex: + except IndexError: echo("Specific except") try: await foobar() - except OSError, EInvalidField, EInvalidIndex: + except OSError, FieldError, IndexError: echo("Multiple idents in except") try: await foobar() - except OSError, EInvalidField: + except OSError, FieldError: assert false - except EInvalidIndex: + except IndexError: echo("Multiple except branches") try: await foobar() - except EInvalidIndex: + except IndexError: echo("Multiple except branches 2") - except OSError, EInvalidField: + except OSError, FieldError: assert false waitFor catch() diff --git a/tests/async/tasynctry2.nim b/tests/async/tasynctry2.nim deleted file mode 100644 index 444a058be..000000000 --- a/tests/async/tasynctry2.nim +++ /dev/null @@ -1,16 +0,0 @@ -discard """ - file: "tasynctry2.nim" - errormsg: "\'yield\' cannot be used within \'try\' in a non-inlined iterator" - line: 15 -""" -import asyncdispatch - -proc foo(): Future[bool] {.async.} = discard - -proc test5(): Future[int] {.async.} = - try: - discard await foo() - raise newException(ValueError, "Test5") - except: - discard await foo() - result = 0 diff --git a/tests/async/tfuturevar.nim b/tests/async/tfuturevar.nim index 73c0fddf7..ea2c63e03 100644 --- a/tests/async/tfuturevar.nim +++ b/tests/async/tfuturevar.nim @@ -35,7 +35,7 @@ proc main() {.async.} = fut = newFutureVar[string]() let retFut = failureTest(fut, true) yield retFut - doAssert(fut.read().isNil) + doAssert(fut.read().len == 0) doAssert(fut.finished) fut = newFutureVar[string]() diff --git a/tests/async/tgeneric_async.nim b/tests/async/tgeneric_async.nim index af6370181..bab2d1a31 100644 --- a/tests/async/tgeneric_async.nim +++ b/tests/async/tgeneric_async.nim @@ -1,9 +1,40 @@ +discard """ +output: "1\nmessa" +""" -import asyncdispatch +import async -when true: - # bug #2377 - proc test[T](v: T) {.async.} = - echo $v +# bug #2377 +proc test[T](v: T) {.async.} = + echo $v + +asyncCheck test[int](1) + +# More complex case involving typedesc and static params +type + SomeMsg = object + data: string + +template msgId(M: type SomeMsg): int = 1 + +proc recvMsg(): Future[tuple[msgId: int, msgData: string]] {.async.} = + return (1, "message") + +proc read(data: string, T: type SomeMsg, maxBytes: int): T = + result.data = data[0 ..< min(data.len, maxBytes)] + +proc nextMsg*(MsgType: typedesc, + maxBytes: static[int]): Future[MsgType] {.async.} = + const wantedId = MsgType.msgId + + while true: + var (nextMsgId, nextMsgData) = await recvMsg() + if nextMsgId == wantedId: + return nextMsgData.read(MsgType, maxBytes) + +proc main {.async.} = + let msg = await nextMsg(SomeMsg, 5) + echo msg.data + +asyncCheck main() - asyncCheck test[int](1) diff --git a/tests/async/tioselectors.nim b/tests/async/tioselectors.nim index d2e4cfec1..a556b6dd2 100644 --- a/tests/async/tioselectors.nim +++ b/tests/async/tioselectors.nim @@ -11,7 +11,9 @@ template processTest(t, x: untyped) = #stdout.flushFile() if not x: echo(t & " FAILED\r\n") -when not defined(windows): +when defined(macosx): + echo "All tests passed!" +elif not defined(windows): import os, posix, nativesockets, times when ioselSupportedPlatform: diff --git a/tests/async/tlambda.nim b/tests/async/tlambda.nim index d187c0d50..8f570689b 100644 --- a/tests/async/tlambda.nim +++ b/tests/async/tlambda.nim @@ -1,7 +1,7 @@ # bug 2007 -import asyncdispatch, asyncnet, logging, json, uri, strutils, future +import asyncdispatch, asyncnet, logging, json, uri, strutils, sugar type Builder = ref object @@ -27,7 +27,7 @@ proc newBuild*(onProgress: ProgressCB): Build = result.onProgress = onProgress proc start(build: Build, repo, hash: string) {.async.} = - let path = repo.parseUri().path.toLower() + let path = repo.parseUri().path.toLowerAscii() proc onProgress(builder: Builder, message: string) {.async.} = debug($message) diff --git a/tests/async/twinasyncrw.nim b/tests/async/twinasyncrw.nim index 17b7d1cf5..42a7e3058 100644 --- a/tests/async/twinasyncrw.nim +++ b/tests/async/twinasyncrw.nim @@ -14,7 +14,7 @@ when defined(windows): var clientCount = 0 proc winConnect*(socket: AsyncFD, address: string, port: Port, - domain = Domain.AF_INET): Future[void] = + domain = Domain.AF_INET): Future[void] = var retFuture = newFuture[void]("winConnect") proc cb(fd: AsyncFD): bool = var ret = SocketHandle(fd).getSockOptInt(cint(SOL_SOCKET), cint(SO_ERROR)) @@ -183,7 +183,7 @@ when defined(windows): ## **Note**: This procedure is mostly used for testing. You likely want to ## use ``asyncnet.recvLine`` instead. - template addNLIfEmpty(): stmt = + template addNLIfEmpty() = if result.len == 0: result.add("\c\L") diff --git a/tests/bind/tmixin.nim b/tests/bind/tmixin.nim index d841326a5..65c650261 100644 --- a/tests/bind/tmixin.nim +++ b/tests/bind/tmixin.nim @@ -3,7 +3,7 @@ discard """ """ type - TFoo1 = object of TObject + TFoo1 = object of RootObj v: int TFoo2 = object of TFoo1 v2: int diff --git a/tests/casestmt/t7699.nim b/tests/casestmt/t7699.nim new file mode 100644 index 000000000..ea08388eb --- /dev/null +++ b/tests/casestmt/t7699.nim @@ -0,0 +1,15 @@ +discard """ + line: 13 + errormsg: "case statement cannot work on enums with holes for computed goto" +""" + +type + X = enum + A = 0, B = 100 + +var z = A +while true: + {.computedGoto.} + case z + of A: discard + of B: discard diff --git a/tests/casestmt/t8333.nim b/tests/casestmt/t8333.nim new file mode 100644 index 000000000..ca3523358 --- /dev/null +++ b/tests/casestmt/t8333.nim @@ -0,0 +1,10 @@ +discard """ + output: "1" +""" + +converter toInt*(x: char): int = + x.int + +case 0 +of 'a': echo 0 +else: echo 1 diff --git a/tests/casestmt/tcasestm.nim b/tests/casestmt/tcasestm.nim index b005d8120..ff912ffab 100644 --- a/tests/casestmt/tcasestm.nim +++ b/tests/casestmt/tcasestm.nim @@ -41,23 +41,32 @@ let str2 = "NN" let a = case str1: of "Y": true of "N": false - else: + else: echo "no good" quit("quiting") -let b = case str2: - of nil, "": raise newException(ValueError, "Invalid boolean") - elif str2[0] == 'Y': true - elif str2[0] == 'N': false +proc toBool(s: string): bool = + case s: + of "": raise newException(ValueError, "Invalid boolean") + elif s[0] == 'Y': true + elif s[0] == 'N': false else: "error".quit(2) + +let b = "NN".toBool() + doAssert(a == true) doAssert(b == false) +static: + #bug #7407 + let bstatic = "N".toBool() + doAssert(bstatic == false) + var bb: bool doassert(not compiles( bb = case str2: - of nil, "": raise newException(ValueError, "Invalid boolean") + of "": raise newException(ValueError, "Invalid boolean") elif str.startsWith("Y"): true elif str.startsWith("N"): false )) @@ -85,7 +94,7 @@ doassert(not compiles( bb = case str2: of "Y": raise newException(ValueError, "Invalid Y") - true + true else: raise newException(ValueError, "Invalid") )) @@ -94,6 +103,6 @@ doassert(not compiles( bb = case str2: of "Y": "invalid Y".quit(3) - true + true else: raise newException(ValueError, "Invalid") )) \ No newline at end of file diff --git a/tests/ccgbugs/mymodule.nim b/tests/ccgbugs/mymodule.nim new file mode 100644 index 000000000..8c78cdf9b --- /dev/null +++ b/tests/ccgbugs/mymodule.nim @@ -0,0 +1,14 @@ +type + MyRefObject* = ref object + s: string + + BaseObj* = ref object of RootObj + ChildObj* = ref object of BaseObj + +proc newMyRefObject*(s: string): MyRefObject = + new(result) + result.s = s + +proc `$`*(o: MyRefObject): string = + o.s + \ No newline at end of file diff --git a/tests/ccgbugs/pkg8616/rtarray.nim b/tests/ccgbugs/pkg8616/rtarray.nim new file mode 100644 index 000000000..286dbb8cd --- /dev/null +++ b/tests/ccgbugs/pkg8616/rtarray.nim @@ -0,0 +1,2 @@ +proc head*[T](pp: var array[1,T]): var T = + result = pp[0] diff --git a/tests/ccgbugs/pkg8616/scheduler.nim b/tests/ccgbugs/pkg8616/scheduler.nim new file mode 100644 index 000000000..0730000c4 --- /dev/null +++ b/tests/ccgbugs/pkg8616/scheduler.nim @@ -0,0 +1,10 @@ +import rtarray + +type + T = tuple[x:int] + +var + arr: array[1,T] + +proc init*() = + discard head(arr) diff --git a/tests/ccgbugs/t5345.nim b/tests/ccgbugs/t5345.nim new file mode 100644 index 000000000..f9ee833c4 --- /dev/null +++ b/tests/ccgbugs/t5345.nim @@ -0,0 +1,10 @@ +discard """ + output: true +""" + +proc cmpx(d: int): bool {.inline.} = d > 0 + +proc abc[C](cx: C, d: int) = + echo cx(d) + +abc(cmpx, 10) diff --git a/tests/ccgbugs/t5701.nim b/tests/ccgbugs/t5701.nim new file mode 100644 index 000000000..e69acbf31 --- /dev/null +++ b/tests/ccgbugs/t5701.nim @@ -0,0 +1,17 @@ +discard """ + output: '''(Field0: 1, Field1: 1) +(Field0: 2, Field1: 2) +(Field0: 3, Field1: 3) +''' +""" + +iterator zip[T1, T2](a: openarray[T1], b: openarray[T2]): iterator() {.inline.} = + let len = min(a.len, b.len) + for i in 0..<len: + echo (a[i], b[i]) + +proc foo(args: varargs[int]) = + for i in zip(args,args): + discard + +foo(1,2,3) diff --git a/tests/ccgbugs/t8616.nim b/tests/ccgbugs/t8616.nim new file mode 100644 index 000000000..54068652a --- /dev/null +++ b/tests/ccgbugs/t8616.nim @@ -0,0 +1,4 @@ +import pkg8616 / scheduler + +when isMainModule: + init() diff --git a/tests/ccgbugs/t8781.nim b/tests/ccgbugs/t8781.nim new file mode 100644 index 000000000..1fa8ec8a5 --- /dev/null +++ b/tests/ccgbugs/t8781.nim @@ -0,0 +1,25 @@ +discard """ +output: "" +""" + +type + Drawable = object of RootObj + discard + + # issue #8781, following type was broken due to 'U' suffix + # on `animatedU`. U also added as union identifier for C. + # replaced by "_U" prefix, which is not allowed as an + # identifier + TypeOne = ref object of Drawable + animatedU: bool + case animated: bool + of true: + frames: seq[int] + of false: + region: float + +when isMainModule: + let r = 1.5 + let a = TypeOne(animatedU: true, + animated: false, + region: r) diff --git a/tests/ccgbugs/tcodegendecllambda.nim b/tests/ccgbugs/tcodegendecllambda.nim new file mode 100644 index 000000000..6dce68db5 --- /dev/null +++ b/tests/ccgbugs/tcodegendecllambda.nim @@ -0,0 +1,13 @@ +discard """ + targets: "c cpp js" + ccodecheck: "'HELLO'" +""" + +when defined(JS): + var foo = proc(): void{.codegenDecl: "/*HELLO*/function $2($3)".} = + echo "baa" +else: + var foo = proc(): void{.codegenDecl: "/*HELLO*/$1 $2 $3".} = + echo "baa" + +foo() diff --git a/tests/ccgbugs/tforward_decl_only.nim b/tests/ccgbugs/tforward_decl_only.nim new file mode 100644 index 000000000..2a867bc3b --- /dev/null +++ b/tests/ccgbugs/tforward_decl_only.nim @@ -0,0 +1,34 @@ +discard """ +ccodecheck: "\\i !@('struct tyObject_MyRefObject'[0-z]+' {')" +output: "hello" +""" + +# issue #7339 +# Test that MyRefObject is only forward declared as it used only by reference + +import mymodule +type AnotherType = object + f: MyRefObject + +let x = AnotherType(f: newMyRefObject("hello")) +echo $x.f + + +# bug #7363 + +type + Foo = object + a: cint + Foo2 = object + b: cint + +proc f(foo: ptr Foo, foo2: ptr Foo2): cint = + if foo != nil: {.emit: "`result` = `foo`->a;".} + if foo2 != nil: {.emit: [result, " = ", foo2[], ".b;"].} + +discard f(nil, nil) + + +# bug #7392 +var x1: BaseObj +var x2 = ChildObj(x1) diff --git a/tests/ccgbugs/tmissinginit.nim b/tests/ccgbugs/tmissinginit.nim index d440608e6..b4087008a 100644 --- a/tests/ccgbugs/tmissinginit.nim +++ b/tests/ccgbugs/tmissinginit.nim @@ -3,8 +3,8 @@ discard """ 0 0 0 -[[a = nil, -b = nil]]''' +[[a = "", +b = []]]''' """ # bug #1475 diff --git a/tests/ccgbugs/topenarraycast.nim b/tests/ccgbugs/topenarraycast.nim new file mode 100644 index 000000000..7d1bc8d03 --- /dev/null +++ b/tests/ccgbugs/topenarraycast.nim @@ -0,0 +1,8 @@ +proc foo[T](s: var openArray[T]): T = + for x in s: result += x + +proc bar(xyz: var seq[int]) = + doAssert 6 == (seq[int](xyz)).foo() + +var t = @[1,2,3] +bar(t) diff --git a/tests/ccgbugs/tresult_of_array.nim b/tests/ccgbugs/tresult_of_array.nim new file mode 100644 index 000000000..fb5abf18a --- /dev/null +++ b/tests/ccgbugs/tresult_of_array.nim @@ -0,0 +1,29 @@ +discard """ + output: '''false +true +false +[false, false, false] +''' +""" + +# bug #7332 +# resetLoc generate incorrect memset code +# because of array passed as argument decaying into a pointer + +import tables +const tableOfArray = { + "one": [true, false, false], + "two": [false, true, false], + "three": [false, false, true] +}.toTable() +for i in 0..2: + echo tableOfArray["two"][i] + +var seqOfArray = @[ + [true, false, false], + [false, true, false], + [false, false, true] +] +proc crashingProc*[B](t: seq[B], index: Natural): B = + discard +echo seqOfArray.crashingProc(0) diff --git a/tests/closure/t8550.nim b/tests/closure/t8550.nim new file mode 100644 index 000000000..153246f08 --- /dev/null +++ b/tests/closure/t8550.nim @@ -0,0 +1,12 @@ +discard """ + output: "@[\"42\"]" +""" + +proc chk_fail(): seq[string] = + iterator x(): int {.closure.} = yield 42 + proc f(cl: iterator(): int {.closure.}): seq[string] = + result = @[] + for i in cl(): result.add($i) + result = f(x) + +echo(chk_fail()) diff --git a/tests/closure/tclosure3.nim b/tests/closure/tclosure3.nim index d5ffb5ab2..4de07bdb5 100644 --- a/tests/closure/tclosure3.nim +++ b/tests/closure/tclosure3.nim @@ -15,7 +15,7 @@ proc main = let val = s[i]() if val != $(i*i): echo "bug ", val - if getOccupiedMem() > 3000_000: quit("still a leak!") + if getOccupiedMem() > 5000_000: quit("still a leak!") echo "success" main() diff --git a/tests/closure/tclosurebug2.nim b/tests/closure/tclosurebug2.nim index f131406a3..5f8911dfa 100644 --- a/tests/closure/tclosurebug2.nim +++ b/tests/closure/tclosurebug2.nim @@ -54,7 +54,7 @@ proc len*[A, B](t: TOrderedTable[A, B]): int {.inline.} = ## returns the number of keys in `t`. result = t.counter -template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} = +template forAllOrderedPairs(yieldStmt: untyped) {.dirty.} = var h = t.first while h >= 0: var nxt = t.data[h].next diff --git a/tests/collections/tcollections_to_string.nim b/tests/collections/tcollections_to_string.nim index 6cc8a84ff..0c4f1e91c 100644 --- a/tests/collections/tcollections_to_string.nim +++ b/tests/collections/tcollections_to_string.nim @@ -68,15 +68,15 @@ block: block: var t: CritBitTree[int] t["a"] = 1 - doAssert $t == "{a: 1}" + doAssert $t == """{"a": 1}""" block: var t: CritBitTree[string] t["a"] = "1" - doAssert $t == """{a: "1"}""" + doAssert $t == """{"a": "1"}""" block: var t: CritBitTree[char] t["a"] = '1' - doAssert $t == "{a: '1'}" + doAssert $t == """{"a": '1'}""" # Test escaping behavior @@ -85,14 +85,20 @@ block: s.addQuoted('\0') s.addQuoted('\31') s.addQuoted('\127') - s.addQuoted('\255') - doAssert s == "'\\x00''\\x1F''\\x7F''\\xFF'" + doAssert s == "'\\x00''\\x1F''\\x7F'" block: var s = "" s.addQuoted('\\') s.addQuoted('\'') s.addQuoted('\"') doAssert s == """'\\''\'''\"'""" +block: + var s = "" + s.addQuoted("å") + s.addQuoted("ä") + s.addQuoted("ö") + s.addEscapedChar('\xFF') + doAssert s == """"å""ä""ö"\xFF""" # Test customized element representation type CustomString = object diff --git a/tests/collections/thashes.nim b/tests/collections/thashes.nim index 76b99313c..5cc3cc8bb 100644 --- a/tests/collections/thashes.nim +++ b/tests/collections/thashes.nim @@ -3,7 +3,7 @@ discard """ """ import tables -from hashes import THash +from hashes import Hash # Test with int block: @@ -66,7 +66,7 @@ block: # The same test with a custom hash(s: string) does # work though. block: - proc hash(x: int): THash {.inline.} = + proc hash(x: int): Hash {.inline.} = echo "overloaded hash" result = x var t = initTable[int, int]() diff --git a/tests/collections/tsets.nim b/tests/collections/tsets.nim index 6139560bd..61e14260a 100644 --- a/tests/collections/tsets.nim +++ b/tests/collections/tsets.nim @@ -1,4 +1,6 @@ import sets +import hashes +import algorithm block setEquality: var @@ -35,7 +37,7 @@ block setWithSequences: doAssert( not s.contains(@[4, 5, 6]) ) block setClearWorked: - var s = initSet[char]() + var s = initSet[char]() for c in "this is a test": s.incl(c) @@ -68,12 +70,54 @@ block orderedSetClearWorked: for c in "eat at joes": s.incl(c) - r = "" + r = "" for c in items(s): add(r, c) doAssert r == "zeat jos" +block hashForHashedSet: + let + seq1 = "This is the test." + seq2 = "the test is This." + s1 = seq1.toSet() + s2 = seq2.toSet() + var hashSeq: seq[Hash] = @[] + doAssert s1 == s2 + doAssert hash(s1) == hash(s2) + +block hashForOrderdSet: + let + str = "This is the test." + rstr = str.reversed - - + var + s1 = initOrderedSet[char]() + s2 = initOrderedSet[char]() + r = initOrderedSet[char]() + expected: Hash + added: seq[char] = @[] + reversed: Hash + radded: seq[char] = @[] + + expected = 0 + for c in str: + if (not (c in added)): + expected = expected !& hash(c) + added.add(c) + s1.incl(c) + s2.incl(c) + expected = !$expected + doAssert hash(s1) == expected + doAssert hash(s1) == hash(s2) + doAssert hash(s1) != hash(r) + + reversed = 0 + for c in rstr: + if (not (c in radded)): + reversed = reversed !& hash(c) + radded.add(c) + r.incl(c) + reversed = !$reversed + doAssert hash(r) == reversed + doAssert hash(s1) != reversed diff --git a/tests/compilerapi/exposed.nim b/tests/compilerapi/exposed.nim new file mode 100644 index 000000000..73becd93d --- /dev/null +++ b/tests/compilerapi/exposed.nim @@ -0,0 +1,3 @@ + +proc addFloats*(x, y, z: float): float = + discard "implementation overriden by tcompilerapi.nim" diff --git a/tests/compilerapi/myscript.nim b/tests/compilerapi/myscript.nim new file mode 100644 index 000000000..539b07de1 --- /dev/null +++ b/tests/compilerapi/myscript.nim @@ -0,0 +1,9 @@ + +import exposed + +echo "top level statements are executed!" + +proc hostProgramRunsThis*(a, b: float): float = + result = addFloats(a, b, 1.0) + +let hostProgramWantsThis* = "my secret" diff --git a/tests/compilerapi/tcompilerapi.nim b/tests/compilerapi/tcompilerapi.nim new file mode 100644 index 000000000..90d343264 --- /dev/null +++ b/tests/compilerapi/tcompilerapi.nim @@ -0,0 +1,47 @@ +discard """ + output: '''top level statements are executed! +2.0 +my secret +''' +""" + +## Example program that demonstrates how to use the +## compiler as an API to embed into your own projects. + +import "../../compiler" / [ast, vmdef, vm, nimeval] +import std / [os] + +proc main() = + let std = findNimStdLib() + if std.len == 0: + quit "cannot find Nim's standard library" + + var intr = createInterpreter("myscript.nim", [std, getAppDir()]) + intr.implementRoutine("*", "exposed", "addFloats", proc (a: VmArgs) = + setResult(a, getFloat(a, 0) + getFloat(a, 1) + getFloat(a, 2)) + ) + + intr.evalScript() + + let foreignProc = selectRoutine(intr, "hostProgramRunsThis") + if foreignProc == nil: + quit "script does not export a proc of the name: 'hostProgramRunsThis'" + let res = intr.callRoutine(foreignProc, [newFloatNode(nkFloatLit, 0.9), + newFloatNode(nkFloatLit, 0.1)]) + if res.kind == nkFloatLit: + echo res.floatVal + else: + echo "bug!" + + let foreignValue = selectUniqueSymbol(intr, "hostProgramWantsThis") + if foreignValue == nil: + quit "script does not export a global of the name: hostProgramWantsThis" + let val = intr.getGlobalValue(foreignValue) + if val.kind in {nkStrLit..nkTripleStrLit}: + echo val.strVal + else: + echo "bug!" + + destroyInterpreter(intr) + +main() \ No newline at end of file diff --git a/tests/compiles/t8630.nim b/tests/compiles/t8630.nim new file mode 100644 index 000000000..aa2be11cd --- /dev/null +++ b/tests/compiles/t8630.nim @@ -0,0 +1,13 @@ +discard """ + output: ''' +foo +bar +''' +""" + +proc test(strings: seq[string]) = + for s in strings: + var p3 = unsafeAddr(s) + echo p3[] + +test(@["foo", "bar"]) diff --git a/tests/compiles/trecursive_generic_in_compiles.nim b/tests/compiles/trecursive_generic_in_compiles.nim index 77bf0bb02..9c7fd10b3 100644 --- a/tests/compiles/trecursive_generic_in_compiles.nim +++ b/tests/compiles/trecursive_generic_in_compiles.nim @@ -1,6 +1,6 @@ # bug #3313 -import unittest, future - +import unittest, sugar +{.experimental: "notnil".} type ListNodeKind = enum lnkNil, lnkCons diff --git a/tests/concepts/libs/trie.nim b/tests/concepts/libs/trie.nim new file mode 100644 index 000000000..bdd318492 --- /dev/null +++ b/tests/concepts/libs/trie.nim @@ -0,0 +1,26 @@ +import + hashes, tables, trie_database + +type + MemDBTable = Table[KeccakHash, string] + + MemDB* = object + tbl: MemDBTable + +proc hash*(key: KeccakHash): int = + hashes.hash(key.data) + +proc get*(db: MemDB, key: KeccakHash): string = + db.tbl[key] + +proc del*(db: var MemDB, key: KeccakHash): bool = + if db.tbl.hasKey(key): + db.tbl.del(key) + return true + else: + return false + +proc put*(db: var MemDB, key: KeccakHash, value: string): bool = + db.tbl[key] = value + return true + diff --git a/tests/concepts/libs/trie_database.nim b/tests/concepts/libs/trie_database.nim new file mode 100644 index 000000000..a45c64842 --- /dev/null +++ b/tests/concepts/libs/trie_database.nim @@ -0,0 +1,12 @@ +type + KeccakHash* = object + data*: string + + BytesRange* = object + bytes*: string + + TrieDatabase* = concept db + put(var db, KeccakHash, string) is bool + del(var db, KeccakHash) is bool + get(db, KeccakHash) is string + diff --git a/tests/concepts/t3330.nim b/tests/concepts/t3330.nim index a4fff7fb3..8021db827 100644 --- a/tests/concepts/t3330.nim +++ b/tests/concepts/t3330.nim @@ -5,19 +5,15 @@ t3330.nim(63, 4) Error: type mismatch: got <Bar[system.int]> but expected one of: proc test(foo: Foo[int]) t3330.nim(48, 8) Hint: Non-matching candidates for add(k, string, T) -proc add(x: var string; y: string) - first type mismatch at position: 1 - required type: var string - but expression 'k' is of type: Alias -proc add(x: var string; y: char) +proc add[T](x: var seq[T]; y: openArray[T]) first type mismatch at position: 1 - required type: var string + required type: var seq[T] but expression 'k' is of type: Alias -proc add(result: var string; x: int64) +proc add(result: var string; x: float) first type mismatch at position: 1 required type: var string but expression 'k' is of type: Alias -proc add(result: var string; x: float) +proc add(x: var string; y: string) first type mismatch at position: 1 required type: var string but expression 'k' is of type: Alias @@ -25,13 +21,17 @@ proc add(x: var string; y: cstring) first type mismatch at position: 1 required type: var string but expression 'k' is of type: Alias -proc add[T](x: var seq[T]; y: openArray[T]) +proc add[T](x: var seq[T]; y: T) first type mismatch at position: 1 required type: var seq[T] but expression 'k' is of type: Alias -proc add[T](x: var seq[T]; y: T) +proc add(result: var string; x: int64) first type mismatch at position: 1 - required type: var seq[T] + required type: var string + but expression 'k' is of type: Alias +proc add(x: var string; y: char) + first type mismatch at position: 1 + required type: var string but expression 'k' is of type: Alias t3330.nim(48, 8) template/generic instantiation from here diff --git a/tests/concepts/t6770.nim b/tests/concepts/t6770.nim new file mode 100644 index 000000000..1787ee670 --- /dev/null +++ b/tests/concepts/t6770.nim @@ -0,0 +1,27 @@ +discard """ +output: ''' +10 +10 +''' +""" + +type GA = concept c + c.a is int + +type A = object + a: int + +type AA = object + case exists: bool + of true: + a: int + else: + discard + +proc print(inp: GA) = + echo inp.a + +let failing = AA(exists: true, a: 10) +let working = A(a:10) +print(working) +print(failing) diff --git a/tests/concepts/t7952.nim b/tests/concepts/t7952.nim new file mode 100644 index 000000000..96ff47e4a --- /dev/null +++ b/tests/concepts/t7952.nim @@ -0,0 +1,12 @@ +discard """ + output: 5 +""" + +type + HasLen = concept iter + len(iter) is int + +proc echoLen(x: HasLen) = + echo len(x) + +echoLen([1, 2, 3, 4, 5]) diff --git a/tests/concepts/t8280.nim b/tests/concepts/t8280.nim new file mode 100644 index 000000000..ba33af34e --- /dev/null +++ b/tests/concepts/t8280.nim @@ -0,0 +1,16 @@ +discard """ + output: "()" +""" + +type + Iterable[T] = concept x + for elem in x: + elem is T + +proc max[A](iter: Iterable[A]): A = + discard + +type + MyType = object + +echo max(@[MyType()]) diff --git a/tests/concepts/tmatrixconcept.nim b/tests/concepts/tmatrixconcept.nim index d2597a212..dd5a080b6 100644 --- a/tests/concepts/tmatrixconcept.nim +++ b/tests/concepts/tmatrixconcept.nim @@ -32,7 +32,7 @@ type data: array[M*K, T] # adaptor for the concept's non-matching expectations -template N(M: type MyMatrix): expr = M.K +template N(M: type MyMatrix): untyped = M.K proc `[]`(m: MyMatrix; r, c: int): m.T = m.data[r * m.K + c] @@ -45,7 +45,7 @@ proc foo(x: MyMatrix, arr: array[15, x.T]) = discard proc genericMatrixProc[R, C, TE, FF, FC, T](m: Matrix[R, C, TE, FF, FC, T]): T = static: echo "R=", R, " C=", C, " TE=", TE, " FF=", FF, " FC=", FC, " T=", T.name - + m[0, 0] proc implicitMatrixProc(m: Matrix): m.T = @@ -57,7 +57,7 @@ proc implicitMatrixProc(m: Matrix): m.T = #" FF=", m.FromFoo, #" FC=", m.FromConst, " T=", m.T.name - + m[0, 0] proc myMatrixProc(x: MyMatrix): MyMatrix.T = genericMatrixProc(x) diff --git a/tests/concepts/tmisc_issues.nim b/tests/concepts/tmisc_issues.nim index 662eba380..e21988c73 100644 --- a/tests/concepts/tmisc_issues.nim +++ b/tests/concepts/tmisc_issues.nim @@ -9,7 +9,8 @@ implicit generic generic false true --1''' +-1 +Meow''' """ # https://github.com/nim-lang/Nim/issues/1147 @@ -98,3 +99,15 @@ let b = B() echo b is A echo b.size() +# https://github.com/nim-lang/Nim/issues/7125 +type + Thing = concept x + x.hello is string + Cat = object + +proc hello(d: Cat): string = "Meow" + +proc sayHello(c: Thing) = echo(c.hello) + +var a: Thing = Cat() +a.sayHello() diff --git a/tests/concepts/tmonoid.nim b/tests/concepts/tmonoid.nim index 49b3239bd..e0e19adbc 100644 --- a/tests/concepts/tmonoid.nim +++ b/tests/concepts/tmonoid.nim @@ -11,3 +11,15 @@ type Monoid = concept x, y proc z(x: typedesc[int]): int = 0 echo(int is Monoid) + +# https://github.com/nim-lang/Nim/issues/8126 +type AdditiveMonoid* = concept x, y, type T + x + y is T + + # some redundant checks to test an alternative approaches: + type TT = type(x) + x + y is type(x) + x + y is TT + +doAssert(1 is AdditiveMonoid) + diff --git a/tests/concepts/treversable.nim b/tests/concepts/treversable.nim new file mode 100644 index 000000000..6ebc077d9 --- /dev/null +++ b/tests/concepts/treversable.nim @@ -0,0 +1,31 @@ +# issue 7705, 7703, 7702 +discard """ + output: ''' +z +e + ''' +""" + +type + Reversable*[T] = concept a + a[int] is T + a.high is int + a.len is int + a.low is int + +proc get[T](s: Reversable[T], n: int): T = + s[n] + +proc hi[T](s: Reversable[T]): int = + s.high + +proc lo[T](s: Reversable[T]): int = + s.low + +iterator reverse*[T](s: Reversable[T]): T = + assert hi(s) - lo(s) == len(s) - 1 + for z in hi(s).countdown(lo(s)): + yield s.get(z) + +for s in @["e", "z"].reverse: + echo s diff --git a/tests/concepts/tseqofconcept.nim b/tests/concepts/tseqofconcept.nim new file mode 100644 index 000000000..5e44117ea --- /dev/null +++ b/tests/concepts/tseqofconcept.nim @@ -0,0 +1,19 @@ +discard """ +output: "1\n2\n3" +""" + +type + MyConcept = concept x + someProc(x) + + SomeSeq = seq[MyConcept] + +proc someProc(x:int) = echo x + +proc work (s: SomeSeq) = + for item in s: + someProc item + +var s = @[1, 2, 3] +work s + diff --git a/tests/concepts/tstackconcept.nim b/tests/concepts/tstackconcept.nim index 2238dacb6..cb8db566d 100644 --- a/tests/concepts/tstackconcept.nim +++ b/tests/concepts/tstackconcept.nim @@ -31,7 +31,7 @@ type s.pop() is T type ValueType = T - const ValueTypeName = T.name.toUpper + const ValueTypeName = T.name.toUpperAscii proc genericAlgorithm[T](s: var Stack[T], y: T) = static: diff --git a/tests/concepts/ttrieconcept.nim b/tests/concepts/ttrieconcept.nim new file mode 100644 index 000000000..a26e6b146 --- /dev/null +++ b/tests/concepts/ttrieconcept.nim @@ -0,0 +1,7 @@ +import libs/[trie_database, trie] + +proc takeDb(d: TrieDatabase) = discard +var mdb: MemDB + +takeDb(mdb) + diff --git a/tests/constructors/tinvalid_construction.nim b/tests/constructors/tinvalid_construction.nim index bb3b1bebb..b3e56eec6 100644 --- a/tests/constructors/tinvalid_construction.nim +++ b/tests/constructors/tinvalid_construction.nim @@ -3,12 +3,12 @@ template accept(x) = template reject(x) = static: assert(not compiles(x)) - +{.experimental: "notnil".} type TRefObj = ref object x: int - THasNotNils = object of TObject + THasNotNils = object of RootObj a: TRefObj not nil b: TRefObj not nil c: TRefObj diff --git a/tests/controlflow/tstatret.nim b/tests/controlflow/tstatret.nim index d655f5595..04cac9966 100644 --- a/tests/controlflow/tstatret.nim +++ b/tests/controlflow/tstatret.nim @@ -1,7 +1,7 @@ discard """ file: "tstatret.nim" line: 9 - errormsg: "statement not allowed after" + errormsg: "unreachable statement after 'return'" """ # no statement after return proc main() = diff --git a/tests/converter/t7098.nim b/tests/converter/t7098.nim new file mode 100644 index 000000000..66e629fa8 --- /dev/null +++ b/tests/converter/t7098.nim @@ -0,0 +1,31 @@ +type + Byte* = uint8 + Bytes* = seq[Byte] + + BytesRange* = object + bytes: Bytes + ibegin, iend: int + +proc initBytesRange*(s: var Bytes, ibegin = 0, iend = -1): BytesRange = + let e = if iend < 0: s.len + iend + 1 + else: iend + assert ibegin >= 0 and e <= s.len + + shallow(s) + result.bytes = s + result.ibegin = ibegin + result.iend = e + +converter fromSeq*(s: Bytes): BytesRange = + var seqCopy = s + return initBytesRange(seqCopy) + +type + Reader* = object + data: BytesRange + position: int + +proc readerFromBytes*(input: BytesRange): Reader = + discard + +let r = readerFromBytes(@[]) diff --git a/tests/converter/tconvert.nim b/tests/converter/tconvert.nim index a37140234..48367a85b 100644 --- a/tests/converter/tconvert.nim +++ b/tests/converter/tconvert.nim @@ -15,6 +15,6 @@ type TFoo = object converter toPtr*(some: var TFoo): ptr TFoo = (addr some) -proc zoot(x: ptr TFoo) = nil +proc zoot(x: ptr TFoo) = discard var x: Tfoo zoot(x) diff --git a/tests/converter/tconverter_with_constraint.nim b/tests/converter/tconverter_with_constraint.nim new file mode 100644 index 000000000..793264434 --- /dev/null +++ b/tests/converter/tconverter_with_constraint.nim @@ -0,0 +1,20 @@ + +discard """ + file: "tconverter_with_constraint.nim" + line: 20 + errormsg: "type mismatch: got <int>" +""" + +type + MyType = distinct int + +converter to_mytype(m: int{lit}): MyType = + m.MyType + +proc myproc(m: MyType) = + echo m.int, ".MyType" + +myproc(1) # call by literal is ok + +var x: int = 12 +myproc(x) # should fail \ No newline at end of file diff --git a/tests/cpp/t8241.nim b/tests/cpp/t8241.nim new file mode 100644 index 000000000..8e98fda10 --- /dev/null +++ b/tests/cpp/t8241.nim @@ -0,0 +1,7 @@ +discard """ + targets: "cpp" + action: "compile" +""" + +proc foo(): cstring {.importcpp: "", dynlib: "".} +echo foo() diff --git a/tests/cpp/tasync_cpp.nim b/tests/cpp/tasync_cpp.nim index a5e3374b6..50bc1853c 100644 --- a/tests/cpp/tasync_cpp.nim +++ b/tests/cpp/tasync_cpp.nim @@ -1,6 +1,7 @@ discard """ targets: "cpp" output: "hello" + cmd: "nim cpp --nilseqs:on $file" """ # bug #3299 diff --git a/tests/cpp/tcovariancerules.nim b/tests/cpp/tcovariancerules.nim index 9365a3a18..acde1b288 100644 --- a/tests/cpp/tcovariancerules.nim +++ b/tests/cpp/tcovariancerules.nim @@ -13,14 +13,8 @@ cat cat dog dog -dog value -cat value -dog value -cat value dog dog -dog value -cat value dog 1 dog 2 ''' @@ -243,11 +237,12 @@ reject modifiesCovariantArray(dogRefsArray.addr) var dogValues = @[vdog, vdog] var dogValuesArray = [vdog, vdog] -var animalValues = @[Animal(vdog), Animal(vcat)] -var animalValuesArray = [Animal(vdog), Animal(vcat)] +when false: + var animalValues = @[Animal(vdog), Animal(vcat)] + var animalValuesArray = [Animal(vdog), Animal(vcat)] -wantsNonCovariantSeq animalValues -wantsNonCovariantArray animalValuesArray + wantsNonCovariantSeq animalValues + wantsNonCovariantArray animalValuesArray reject wantsNonCovariantSeq(dogRefs) reject modifiesCovariantOperArray(dogRefs) @@ -260,7 +255,6 @@ modifiesDerivedOperArray dogRefs reject modifiesDerivedOperArray(dogValues) reject modifiesDerivedOperArray(animalRefs) -wantsNonCovariantOperArray animalValues reject wantsNonCovariantOperArray(animalRefs) reject wantsNonCovariantOperArray(dogRefs) reject wantsNonCovariantOperArray(dogValues) @@ -300,7 +294,7 @@ reject wantsVarPointer2(pcat) # covariance may be allowed for certain extern types -{.emit: """ +{.emit: """/*TYPESECTION*/ template <class T> struct FN { typedef void (*type)(T); }; template <class T> struct ARR { typedef T DataType[2]; DataType data; }; """.} diff --git a/tests/cpp/temitlist.nim b/tests/cpp/temitlist.nim index a7a8ebde4..e88bf45bd 100644 --- a/tests/cpp/temitlist.nim +++ b/tests/cpp/temitlist.nim @@ -1,6 +1,7 @@ discard """ targets: "cpp" - output: '''6.0''' + output: '''6.0 +0''' """ # bug #4730 @@ -20,3 +21,16 @@ proc main = echo v[0] main() + +#------------ + +#bug #6837 +type StdString {.importCpp: "std::string", header: "<string>", byref.} = object +proc initString(): StdString {.constructor, importCpp: "std::string(@)", header: "<string>".} +proc size(this: var StdString): csize {.importCpp: "size", header: "<string>".} + +proc f(): csize = + var myString: StdString = initString() + return myString.size() + +echo f() diff --git a/tests/cpp/tempty_generic_obj.nim b/tests/cpp/tempty_generic_obj.nim index b4c746a30..b61c699f6 100644 --- a/tests/cpp/tempty_generic_obj.nim +++ b/tests/cpp/tempty_generic_obj.nim @@ -20,3 +20,19 @@ v.doSomething() var vf = initVector[float]() vf.doSomething() # Nim uses doSomething[int] here in C++ + +# Alternative definition: +# https://github.com/nim-lang/Nim/issues/7653 + +type VectorAlt* {.importcpp: "std::vector", header: "<vector>", nodecl.} [T] = object +proc mkVector*[T]: VectorAlt[T] {.importcpp: "std::vector<'*0>()", header: "<vector>", constructor, nodecl.} + +proc foo(): VectorAlt[cint] = + mkVector[cint]() + +proc bar(): VectorAlt[cstring] = + mkVector[cstring]() + +var x = foo() +var y = bar() + diff --git a/tests/cpp/tvector_iterator.nim b/tests/cpp/tvector_iterator.nim index 9df3754ba..4d686955f 100644 --- a/tests/cpp/tvector_iterator.nim +++ b/tests/cpp/tvector_iterator.nim @@ -2,7 +2,7 @@ discard """ targets: "cpp" """ -{.emit: """ +{.emit: """/*TYPESECTION*/ template <class T> struct Vector { diff --git a/tests/deprecated/importme.nim b/tests/deprecated/importme.nim new file mode 100644 index 000000000..0a9c6e37d --- /dev/null +++ b/tests/deprecated/importme.nim @@ -0,0 +1,10 @@ +type + Ty* {.deprecated.} = uint32 + Ty1* {.deprecated: "hello".} = uint32 + +var aVar* {.deprecated.}: char + +proc aProc*() {.deprecated.} = discard +proc aProc1*() {.deprecated: "hello".} = discard + +{.deprecated: "goodbye".} diff --git a/tests/deprecated/tmodule1.nim b/tests/deprecated/tmodule1.nim new file mode 100644 index 000000000..954836889 --- /dev/null +++ b/tests/deprecated/tmodule1.nim @@ -0,0 +1,23 @@ +discard """ + nimout: '''tmodule1.nim(11, 8) Warning: goodbye; importme is deprecated [Deprecated] +tmodule1.nim(14, 10) Warning: Ty is deprecated [Deprecated] +tmodule1.nim(17, 10) Warning: hello; Ty1 is deprecated [Deprecated] +tmodule1.nim(20, 8) Warning: aVar is deprecated [Deprecated] +tmodule1.nim(22, 3) Warning: aProc is deprecated [Deprecated] +tmodule1.nim(23, 3) Warning: hello; aProc1 is deprecated [Deprecated] +''' +""" + +import importme + +block: + var z: Ty + z = 0 +block: + var z: Ty1 + z = 0 +block: + echo aVar +block: + aProc() + aProc1() diff --git a/tests/deprecated/tnoannot.nim b/tests/deprecated/tnoannot.nim new file mode 100644 index 000000000..9cc5c7e06 --- /dev/null +++ b/tests/deprecated/tnoannot.nim @@ -0,0 +1,7 @@ +discard """ + line: 7 + errormsg: "annotation to deprecated not supported here" +""" + +var foo* {.deprecated.} = 42 +var foo1* {.deprecated: "no".} = 42 diff --git a/tests/destructor/tcustomseqs.nim b/tests/destructor/tcustomseqs.nim index 97d7c07b6..83df0053f 100644 --- a/tests/destructor/tcustomseqs.nim +++ b/tests/destructor/tcustomseqs.nim @@ -43,9 +43,10 @@ proc `=destroy`*[T](x: var myseq[T]) = proc `=`*[T](a: var myseq[T]; b: myseq[T]) = if a.data == b.data: return if a.data != nil: - dealloc(a.data) - inc deallocCount - a.data = nil + `=destroy`(a) + #dealloc(a.data) + #inc deallocCount + #a.data = nil a.len = b.len a.cap = b.cap if b.data != nil: diff --git a/tests/destructor/tmove_objconstr.nim b/tests/destructor/tmove_objconstr.nim index 8aa12ed05..178ff2a7d 100644 --- a/tests/destructor/tmove_objconstr.nim +++ b/tests/destructor/tmove_objconstr.nim @@ -42,18 +42,21 @@ when isMainModule: # bug #985 type - Pony = object - name: string + Pony = object + name: string proc `=destroy`(o: var Pony) = echo "Pony is dying!" proc getPony: Pony = - result.name = "Sparkles" + result.name = "Sparkles" iterator items(p: Pony): int = - for i in 1..4: - yield i + for i in 1..4: + yield i for x in getPony(): - echo x + echo x +# XXX this needs to be enabled once top level statements +# produce destructor calls again. +echo "Pony is dying!" diff --git a/tests/destructor/turn_destroy_into_finalizer.nim b/tests/destructor/turn_destroy_into_finalizer.nim new file mode 100644 index 000000000..f5b705593 --- /dev/null +++ b/tests/destructor/turn_destroy_into_finalizer.nim @@ -0,0 +1,22 @@ +discard """ + output: '''true''' +""" + +type + Foo = object + id: int + +var destroyed: int + +proc `=destroy`(x: var Foo) = + #echo "finally ", x.id + inc destroyed + +proc main = + var r: ref Foo + for i in 1..50_000: + new(r) + r.id = i + echo destroyed > 30_000 + +main() diff --git a/tests/discard/tdiscardable.nim b/tests/discard/tdiscardable.nim index 662d2725a..f979b29c9 100644 --- a/tests/discard/tdiscardable.nim +++ b/tests/discard/tdiscardable.nim @@ -13,7 +13,7 @@ q[float](0.8, 0.2) # bug #942 -template maybeMod(x: Tinteger, module:Natural): untyped = +template maybeMod(x: SomeInteger, module: Natural): untyped = if module > 0: x mod module else: x diff --git a/tests/distinct/tnil.nim b/tests/distinct/tnil.nim index e60437a1f..16de38f60 100644 --- a/tests/distinct/tnil.nim +++ b/tests/distinct/tnil.nim @@ -1,23 +1,19 @@ discard """ file: "tnil.nim" - output: '''0x1 - -nil - -nil - + output: '''1 +0 +0 ''' - disabled: "windows" """ - +{.experimental: "notnil".} type MyPointer = distinct pointer MyString = distinct string - MyStringNotNil = distinct (string not nil) MyInt = distinct int proc foo(a: MyPointer) = - echo a.repr + # workaround a Windows 'repr' difference: + echo cast[int](a) foo(cast[MyPointer](1)) foo(cast[MyPointer](nil)) @@ -29,20 +25,6 @@ p = cast[MyPointer](nil) p = nil.MyPointer p = nil -var c: MyString -c = "Test".MyString -c = nil.MyString -c = nil - -p = nil -doAssert(compiles(c = p) == false) - -var n: MyStringNotNil = "Test".MyStringNotNil # Cannot prove warning ... -n = "Test".MyStringNotNil -doAssert(compiles(n = nil.MyStringNotNil) == false) -doAssert(compiles(n = nil.MyStringNotNil) == false) -doAssert(compiles(n = nil) == false) - var i: MyInt i = 1.MyInt doAssert(compiles(i = nil) == false) diff --git a/tests/effects/teffects3.nim b/tests/effects/teffects3.nim index 1b18f7b6d..cbd11f722 100644 --- a/tests/effects/teffects3.nim +++ b/tests/effects/teffects3.nim @@ -9,9 +9,9 @@ type a, b, c: string fn: proc (): int {.tags: [].} - EIO2 = ref object of EIO -proc raiser(): int {.tags: [TObj, FWriteIO].} = + +proc raiser(): int {.tags: [TObj, WriteIoEffect].} = writeLine stdout, "arg" var o: TObjB diff --git a/tests/effects/teffects4.nim b/tests/effects/teffects4.nim index fd5dd49e2..0025c10c5 100644 --- a/tests/effects/teffects4.nim +++ b/tests/effects/teffects4.nim @@ -7,12 +7,12 @@ type TObj = object {.pure, inheritable.} TObjB = object of TObj a, b, c: string - fn: proc (): int {.tags: [FReadIO].} + fn: proc (): int {.tags: [ReadIOEffect].} - EIO2 = ref object of EIO -proc q() {.tags: [FIO].} = - nil + +proc q() {.tags: [IoEffect].} = + discard proc raiser(): int = writeLine stdout, "arg" diff --git a/tests/effects/teffects6.nim b/tests/effects/teffects6.nim index e69fe73b6..3dd83786f 100644 --- a/tests/effects/teffects6.nim +++ b/tests/effects/teffects6.nim @@ -11,7 +11,7 @@ createMenuItem(s, "Go to definition...", proc (i: PMenuItem, p: pointer) {.cdecl.} = try: echo(i.repr) - except EInvalidValue: + except ValueError: echo("blah") ) @@ -20,8 +20,8 @@ proc noRaise(x: proc()) {.raises: [].} = # unknown call that might raise anything, but valid: x() -proc doRaise() {.raises: [EIO].} = - raise newException(EIO, "IO") +proc doRaise() {.raises: [IoError].} = + raise newException(IoError, "IO") proc use*() = noRaise(doRaise) diff --git a/tests/enum/toptions.nim b/tests/enum/toptions.nim index e53acb2b3..da66f0067 100644 --- a/tests/enum/toptions.nim +++ b/tests/enum/toptions.nim @@ -4,7 +4,7 @@ type TOption = enum optNone, optForceFullMake, optBoehmGC, optRefcGC, optRangeCheck, optBoundsCheck, optOverflowCheck, optNilCheck, optAssert, optLineDir, - optWarns, optHints, optDeadCodeElim, optListCmd, optCompileOnly, + optWarns, optHints, optListCmd, optCompileOnly, optSafeCode, # only allow safe code optStyleCheck, optOptimizeSpeed, optOptimizeSize, optGenDynLib, optGenGuiApp, optStackTrace diff --git a/tests/enum/tpure_enums_conflict.nim b/tests/enum/tpure_enums_conflict.nim new file mode 100644 index 000000000..3c7528a72 --- /dev/null +++ b/tests/enum/tpure_enums_conflict.nim @@ -0,0 +1,19 @@ +discard """ + errormsg: "ambiguous identifier: 'amb'" + line: 19 +""" + +# bug #8066 + +when true: + type + MyEnum {.pure.} = enum + valueA, valueB, valueC, valueD, amb + + OtherEnum {.pure.} = enum + valueX, valueY, valueZ, amb + + + echo valueA # MyEnum.valueA + echo MyEnum.amb # OK. + echo amb # Error: Unclear whether it's MyEnum.amb or OtherEnum.amb diff --git a/tests/errmsgs/t1154.nim b/tests/errmsgs/t1154.nim new file mode 100644 index 000000000..fee9d0ad6 --- /dev/null +++ b/tests/errmsgs/t1154.nim @@ -0,0 +1,11 @@ +discard """ +errormsg: "invalid type: 'untyped' in this context: 'proc (a: varargs[untyped])' for proc" +line: 8 +""" + +import typetraits + +proc foo(a:varargs[untyped]) = + echo a[0].type.name + +foo(1) diff --git a/tests/errmsgs/t6281.nim b/tests/errmsgs/t6281.nim new file mode 100644 index 000000000..59fda5077 --- /dev/null +++ b/tests/errmsgs/t6281.nim @@ -0,0 +1,9 @@ +discard """ +errormsg: "invalid type: 'SomeNumber' in this context: 'seq[SomeNumber]' for var" +line: 6 +""" + +var seqwat: seq[SomeNumber] = @[] + +proc foo(x: SomeNumber) = + seqwat.add(x) \ No newline at end of file diff --git a/tests/errmsgs/t8339.nim b/tests/errmsgs/t8339.nim new file mode 100644 index 000000000..f0a97658a --- /dev/null +++ b/tests/errmsgs/t8339.nim @@ -0,0 +1,8 @@ +discard """ + line: 8 + errormsg: "type mismatch: got <seq[int]> but expected 'seq[float]'" +""" + +import sequtils + +var x: seq[float] = @[1].mapIt(it) diff --git a/tests/errmsgs/t8434.nim b/tests/errmsgs/t8434.nim new file mode 100644 index 000000000..60fe2e2df --- /dev/null +++ b/tests/errmsgs/t8434.nim @@ -0,0 +1,16 @@ +discard """ + errormsg: "type mismatch: got <byte, int literal(0)>" + nimout: '''but expected one of: +proc fun0[T1: int | float | + object | array | seq](a1: T1; a2: int) + first type mismatch at position: 1 + required type: T1: int or float or object or array or seq + but expression 'byte(1)' is of type: byte + +expression: fun0(byte(1), 0) +''' +""" + +proc fun0[T1:int|float|object|array|seq](a1:T1, a2:int)=discard + +fun0(byte(1), 0) diff --git a/tests/errmsgs/tcant_overload_by_return_type.nim b/tests/errmsgs/tcant_overload_by_return_type.nim new file mode 100644 index 000000000..613a896b4 --- /dev/null +++ b/tests/errmsgs/tcant_overload_by_return_type.nim @@ -0,0 +1,9 @@ +discard """ +errormsg: "overloaded 'x' leads to ambiguous calls" +line: 9 +""" + +# bug #6393 + +proc x(): int = 7 +proc x(): string = "strange" diff --git a/tests/errmsgs/tgcsafety.nim b/tests/errmsgs/tgcsafety.nim index 4d192db90..ffc6905b0 100644 --- a/tests/errmsgs/tgcsafety.nim +++ b/tests/errmsgs/tgcsafety.nim @@ -5,7 +5,8 @@ nimout: ''' type mismatch: got <AsyncHttpServer, Port, proc (req: Request): Future[system.void]{.locks: <unknown>.}> but expected one of: proc serve(server: AsyncHttpServer; port: Port; - callback: proc (request: Request): Future[void]; address = ""): Future[void] + callback: proc (request: Request): Future[void] {.closure, gcsafe.}; + address = ""): Future[void] first type mismatch at position: 3 required type: proc (request: Request): Future[system.void]{.closure, gcsafe.} but expression 'cb' is of type: proc (req: Request): Future[system.void]{.locks: <unknown>.} diff --git a/tests/errmsgs/tinvalidinout.nim b/tests/errmsgs/tinvalidinout.nim index ce7eb6022..1fa3805ee 100644 --- a/tests/errmsgs/tinvalidinout.nim +++ b/tests/errmsgs/tinvalidinout.nim @@ -1,10 +1,10 @@ discard """ cmd: "nim check $file" -errormsg: "The `in` modifier can be used only with imported types" +errormsg: "the 'in' modifier can be used only with imported types" nimout: ''' -tinvalidinout.nim(14, 7) Error: The `out` modifier can be used only with imported types -tinvalidinout.nim(17, 9) Error: The `in` modifier can be used only with imported types -tinvalidinout.nim(18, 9) Error: The `in` modifier can be used only with imported types +tinvalidinout.nim(14, 7) Error: the 'out' modifier can be used only with imported types +tinvalidinout.nim(17, 9) Error: the 'in' modifier can be used only with imported types +tinvalidinout.nim(18, 9) Error: the 'in' modifier can be used only with imported types ''' """ diff --git a/tests/errmsgs/tnested_empty_seq.nim b/tests/errmsgs/tnested_empty_seq.nim new file mode 100644 index 000000000..ffe8bc3ee --- /dev/null +++ b/tests/errmsgs/tnested_empty_seq.nim @@ -0,0 +1,8 @@ +discard """ + errormsg: "invalid type: 'empty' in this context: 'array[0..0, tuple of (string, seq[empty])]' for var" + line: 8 +""" + +# bug #3948 + +var headers=[("headers", @[])] diff --git a/tests/errmsgs/tnested_generic_instantiation.nim b/tests/errmsgs/tnested_generic_instantiation.nim new file mode 100644 index 000000000..6aea7cbcc --- /dev/null +++ b/tests/errmsgs/tnested_generic_instantiation.nim @@ -0,0 +1,19 @@ +discard """ +errormsg: "generic instantiation too nested" +file: "system.nim" +""" + +# bug #4766 + +type + Plain = ref object + discard + + Wrapped[T] = object + value: T + +converter toWrapped[T](value: T): Wrapped[T] = + Wrapped[T](value: value) + +let result = Plain() +discard $result diff --git a/tests/errmsgs/tproper_stacktrace.nim b/tests/errmsgs/tproper_stacktrace.nim index 57e65fa6f..134946651 100644 --- a/tests/errmsgs/tproper_stacktrace.nim +++ b/tests/errmsgs/tproper_stacktrace.nim @@ -1,11 +1,141 @@ discard """ - outputsub: '''tproper_stacktrace.nim(7) tproper_stacktrace''' - exitcode: 1 + output: '''ok''' """ +import strscans, strutils -template fuzzy(x) = - echo x[] != 9 +proc raiseTestException*() = + raise newException(Exception, "test") -var p: ptr int -fuzzy p +proc matchStackTrace(actualEntries: openarray[StackTraceEntry], expected: string) = + var expectedEntries = newSeq[StackTraceEntry]() + var i = 0 + template checkEqual(actual, expected: typed, subject: string) = + if actual != expected: + echo "Unexpected ", subject, " on line ", i + echo "Actual: ", actual + echo "Expected: ", expected + doAssert(false) + + for l in splitLines(expected.strip): + var procname, filename: string + var line: int + if not scanf(l, "$s$w.nim($i) $w", filename, line, procname): + doAssert(false, "Wrong expected stack trace") + checkEqual($actualEntries[i].filename, filename & ".nim", "file name") + if line != 0: + checkEqual(actualEntries[i].line, line, "line number") + checkEqual($actualEntries[i].procname, procname, "proc name") + inc i + + doAssert(i == actualEntries.len, "Unexpected number of lines in stack trace") + +template verifyStackTrace*(expectedStackTrace: string, body: untyped) = + var verified = false + try: + body + except Exception as e: + verified = true + # echo "Stack trace:" + # echo e.getStackTrace + matchStackTrace(e.getStackTraceEntries(), expectedStackTrace) + + doAssert(verified, "No exception was raised") + + + + + + + + + + + + + + + + + + + + + + + + + +when isMainModule: +# <-- Align with line 70 in the text editor + block: + proc bar() = + raiseTestException() + + proc foo() = + bar() + + const expectedStackTrace = """ + tproper_stacktrace.nim(86) tproper_stacktrace + tproper_stacktrace.nim(76) foo + tproper_stacktrace.nim(73) bar + tproper_stacktrace.nim(7) raiseTestException + """ + + verifyStackTrace expectedStackTrace: + foo() + + block: + proc bar(x: int) = + raiseTestException() + + template foo(x: int) = + bar(x) + + const expectedStackTrace = """ + tproper_stacktrace.nim(103) tproper_stacktrace + tproper_stacktrace.nim(90) bar + tproper_stacktrace.nim(7) raiseTestException + """ + + verifyStackTrace expectedStackTrace: + var x: int + foo(x) + + block: #6803 + proc bar(x = 500) = + raiseTestException() + + proc foo() = + bar() + + const expectedStackTrace = """ + tproper_stacktrace.nim(120) tproper_stacktrace + tproper_stacktrace.nim(110) foo + tproper_stacktrace.nim(107) bar + tproper_stacktrace.nim(7) raiseTestException + """ + + verifyStackTrace expectedStackTrace: + foo() + + block: + proc bar() {.stackTrace: off.} = + proc baz() = # Stack trace should be enabled + raiseTestException() + baz() + + proc foo() = + bar() + + const expectedStackTrace = """ + tproper_stacktrace.nim(139) tproper_stacktrace + tproper_stacktrace.nim(129) foo + tproper_stacktrace.nim(125) baz + tproper_stacktrace.nim(7) raiseTestException + """ + + verifyStackTrace expectedStackTrace: + foo() + + echo "ok" diff --git a/tests/errmsgs/tproper_stacktrace2.nim b/tests/errmsgs/tproper_stacktrace2.nim index 5f312b870..44b208c87 100644 --- a/tests/errmsgs/tproper_stacktrace2.nim +++ b/tests/errmsgs/tproper_stacktrace2.nim @@ -3,7 +3,7 @@ discard """ exitcode: 1 """ -proc returnsNil(): string = return nil +proc returnsNil(): ref int = return nil iterator fields*(a, b: int): int = if a == b: @@ -17,6 +17,6 @@ proc main(): string = result = "" for i in fields(0, 1): let x = returnsNil() - result &= "string literal " & $x + result &= "string literal " & $x[] echo main() diff --git a/tests/errmsgs/tunknown_named_parameter.nim b/tests/errmsgs/tunknown_named_parameter.nim new file mode 100644 index 000000000..b6b855136 --- /dev/null +++ b/tests/errmsgs/tunknown_named_parameter.nim @@ -0,0 +1,24 @@ +discard """ +cmd: "nim check $file" +errormsg: "type mismatch: got <string, set[char], maxsplits: int literal(1)>" +nimout: ''' +proc rsplit(s: string; sep: string; maxsplit: int = -1): seq[string] + first type mismatch at position: 2 + required type: string + but expression '{':'}' is of type: set[char] +proc rsplit(s: string; sep: char; maxsplit: int = -1): seq[string] + first type mismatch at position: 2 + required type: char + but expression '{':'}' is of type: set[char] +proc rsplit(s: string; seps: set[char] = Whitespace; maxsplit: int = -1): seq[string] + first type mismatch at position: 3 + unknown named parameter: maxsplits + +expression: rsplit("abc:def", {':'}, maxsplits = 1) +''' +""" + +# bug #8043 + +import strutils +"abc:def".rsplit({':'}, maxsplits = 1) diff --git a/tests/errmsgs/twrong_at_operator.nim b/tests/errmsgs/twrong_at_operator.nim new file mode 100644 index 000000000..b6b3d101f --- /dev/null +++ b/tests/errmsgs/twrong_at_operator.nim @@ -0,0 +1,15 @@ +discard """ +errormsg: "type mismatch: got <array[0..0, type int]>" +line: 15 +nimout: ''' +twrong_at_operator.nim(15, 30) Error: type mismatch: got <array[0..0, type int]> +but expected one of: +proc `@`[T](a: openArray[T]): seq[T] +proc `@`[IDX, T](a: array[IDX, T]): seq[T] + +expression: @[int] +''' +""" + +# bug #7331 +var seqOfStrings: seq[int] = @[int] diff --git a/tests/exception/tcontinuexc.nim b/tests/exception/tcontinuexc.nim index fb9b523d7..2a05da9c0 100644 --- a/tests/exception/tcontinuexc.nim +++ b/tests/exception/tcontinuexc.nim @@ -4,8 +4,8 @@ discard """ exitcode: "1" """ type - ESomething = object of E_Base - ESomeOtherErr = object of E_Base + ESomething = object of Exception + ESomeOtherErr = object of Exception proc genErrors(s: string) = if s == "error!": diff --git a/tests/exception/tcpp_imported_exc.nim b/tests/exception/tcpp_imported_exc.nim new file mode 100644 index 000000000..c8349f7d5 --- /dev/null +++ b/tests/exception/tcpp_imported_exc.nim @@ -0,0 +1,134 @@ +discard """ +targets: "cpp" +output: '''caught as std::exception +expected +finally1 +finally2 +finally2 +2 +expected +finally 1 +finally 2 +expected +cpp exception caught +''' +""" + +type + std_exception* {.importcpp: "std::exception", header: "<exception>".} = object + std_runtime_error* {.importcpp: "std::runtime_error", header: "<stdexcept>".} = object + std_string* {.importcpp: "std::string", header: "<string>".} = object + +proc constructStdString(s: cstring): std_string {.importcpp: "std::string(@)", constructor, header: "<string>".} + +proc constructRuntimeError(s: stdstring): std_runtime_error {.importcpp: "std::runtime_error(@)", constructor.} + +proc what(ex: std_runtime_error): cstring {.importcpp: "((char *)#.what())".} + +proc myexception = + raise constructRuntimeError(constructStdString("cpp_exception")) + +try: + myexception() # raise std::runtime_error +except std_exception: + echo "caught as std::exception" + try: + raise constructStdString("x") + except std_exception: + echo "should not happen" + except: + echo "expected" + +doAssert(getCurrentException() == nil) + +proc earlyReturn = + try: + try: + myexception() + finally: + echo "finally1" + except: + return + finally: + echo "finally2" + +earlyReturn() +doAssert(getCurrentException() == nil) + + +try: + block blk1: + try: + raise newException(ValueError, "mmm") + except: + break blk1 +except: + echo "should not happen" +finally: + echo "finally2" + +doAssert(getCurrentException() == nil) + +#-------------------------------------- + +# raise by pointer and also generic type + +type + std_vector {.importcpp"std::vector", header"<vector>".} [T] = object + +proc newVector[T](len: int): ptr std_vector[T] {.importcpp: "new std::vector<'1>(@)".} +proc deleteVector[T](v: ptr std_vector[T]) {.importcpp: "delete @; @ = NIM_NIL;".} +proc len[T](v: std_vector[T]): uint {.importcpp: "size".} + +var v = newVector[int](2) +try: + try: + try: + raise v + except ptr std_vector[int] as ex: + echo len(ex[]) + raise newException(ValueError, "msg5") + except: + echo "should not happen" + finally: + deleteVector(v) +except: + echo "expected" + +doAssert(v == nil) +doAssert(getCurrentException() == nil) + +#-------------------------------------- + +# mix of Nim and imported exceptions +try: + try: + try: + raise newException(KeyError, "msg1") + except KeyError: + raise newException(ValueError, "msg2") + except: + echo "should not happen" + finally: + echo "finally 1" + except: + doAssert(getCurrentExceptionMsg() == "msg2") + raise constructStdString("std::string") + finally: + echo "finally 2" +except: + echo "expected" + + +doAssert(getCurrentException() == nil) + +try: + try: + myexception() + except std_runtime_error as ex: + echo "cpp exception caught" + raise newException(ValueError, "rewritten " & $ex.what()) +except: + doAssert(getCurrentExceptionMsg() == "rewritten cpp_exception") + +doAssert(getCurrentException() == nil) diff --git a/tests/exception/tdont_overwrite_typename.nim b/tests/exception/tdont_overwrite_typename.nim index 6e3ff816f..d6dca0990 100644 --- a/tests/exception/tdont_overwrite_typename.nim +++ b/tests/exception/tdont_overwrite_typename.nim @@ -7,10 +7,10 @@ Check passed''' # bug #5628 proc checkException(ex: ref Exception) = - doAssert(ex.name == "ValueError") + doAssert(ex.name == cstring"ValueError") doAssert(ex.msg == "SecondException") doAssert(ex.parent != nil) - doAssert(ex.parent.name == "KeyError") + doAssert(ex.parent.name == cstring"KeyError") doAssert(ex.parent.msg == "FirstException") echo "Check passed" diff --git a/tests/exception/texcas.nim b/tests/exception/texcas.nim index 298aee707..7108e334c 100644 --- a/tests/exception/texcas.nim +++ b/tests/exception/texcas.nim @@ -32,3 +32,11 @@ proc testTryAsExpr(i: int) = test[Exception]() test2() testTryAsExpr(5) + +# see bug #7115 +doAssert(not compiles( + try: + echo 1 + except [KeyError as ex1, ValueError as ex2]: + echo 2 +)) diff --git a/tests/exception/tfinally.nim b/tests/exception/tfinally.nim index e5315a318..7a218b444 100644 --- a/tests/exception/tfinally.nim +++ b/tests/exception/tfinally.nim @@ -7,6 +7,11 @@ msg1 msg2 finally2 finally1 +----------- +except1 +finally1 +except2 +finally2 ''' """ # Test return in try statement: @@ -39,4 +44,19 @@ proc nested_finally = finally: echo "finally1" -nested_finally() \ No newline at end of file +nested_finally() + +echo "-----------" +#bug 7414 +try: + try: + raise newException(Exception, "Hello") + except: + echo "except1" + raise + finally: + echo "finally1" +except: + echo "except2" +finally: + echo "finally2" \ No newline at end of file diff --git a/tests/exception/tunhandledexc.nim b/tests/exception/tunhandledexc.nim index b519fa3f6..c318aec81 100644 --- a/tests/exception/tunhandledexc.nim +++ b/tests/exception/tunhandledexc.nim @@ -2,7 +2,6 @@ discard """ file: "tunhandledexc.nim" outputsub: "Error: unhandled exception: bla [ESomeOtherErr]" exitcode: "1" - targets: "c cpp" """ type ESomething = object of Exception diff --git a/tests/exprs/tstmtexprs.nim b/tests/exprs/tstmtexprs.nim index 2a0ec2821..615c36024 100644 --- a/tests/exprs/tstmtexprs.nim +++ b/tests/exprs/tstmtexprs.nim @@ -33,7 +33,7 @@ when true: if (var yy = 0; yy != 0): echo yy else: - echo(try: parseInt("1244") except EINvalidValue: -1) + echo(try: parseInt("1244") except ValueError: -1) result = case x of 23: 3 of 64: @@ -81,13 +81,13 @@ semiProblem() # bug #844 import json -proc parseResponse(): PJsonNode = +proc parseResponse(): JsonNode = result = % { "key1": % { "key2": % "value" } } for key, val in result["key1"]: var excMsg = key & "(" if (var n=result["key2"]; n != nil): excMsg &= n.str - raise newException(ESynch, excMsg) + raise newException(CatchableError, excMsg) @@ -99,7 +99,7 @@ let b = (se[1] = 1; 1) # bug #1161 type - PFooBase = ref object of PObject + PFooBase = ref object of RootRef field: int PFoo[T] = ref object of PFooBase @@ -151,3 +151,13 @@ if true: fooBool() else: raise newException(ValueError, "argh") + +# bug #5374 + +proc test1(): int64 {.discardable.} = discard +proc test2(): int {.discardable.} = discard + +if true: + test1() +else: + test2() diff --git a/tests/fields/tfieldindex.nim b/tests/fields/tfieldindex.nim index d11c1a8b2..6de6d54bd 100644 --- a/tests/fields/tfieldindex.nim +++ b/tests/fields/tfieldindex.nim @@ -14,7 +14,7 @@ proc indexOf*(t: typedesc, name: string): int = for n, x in fieldPairs(d): if n == name: return i i.inc - raise newException(EInvalidValue, "No field " & name & " in type " & + raise newException(ValueError, "No field " & name & " in type " & astToStr(t)) echo TMyTuple.indexOf("b") diff --git a/tests/flags/tgenscript.nim b/tests/flags/tgenscript.nim new file mode 100644 index 000000000..6a037b5d8 --- /dev/null +++ b/tests/flags/tgenscript.nim @@ -0,0 +1,5 @@ +discard """ + file: "tgenscript.nim" +""" + +echo "--genscript" diff --git a/tests/float/tfloatrange.nim b/tests/float/tfloatrange.nim new file mode 100644 index 000000000..e8ea1912e --- /dev/null +++ b/tests/float/tfloatrange.nim @@ -0,0 +1,49 @@ +discard """ + cmd: "nim c -d:release --rangeChecks:on $file" + output: '''StrictPositiveRange +float +range fail expected +range fail expected +''' +""" +import math, fenv + +type + Positive = range[0.0..Inf] + StrictPositive = range[minimumPositiveValue(float)..Inf] + Negative32 = range[-maximumPositiveValue(float32) .. -1.0'f32] + +proc myoverload(x: float) = + echo "float" + +proc myoverload(x: Positive) = + echo "PositiveRange" + +proc myoverload(x: StrictPositive) = + echo "StrictPositiveRange" + +let x = 9.0.StrictPositive +myoverload(x) +myoverload(9.0) + +doAssert(sqrt(x) == 3.0) + +var z = -10.0 +try: + myoverload(StrictPositive(z)) +except: + echo "range fail expected" + + +proc strictOnlyProc(x: StrictPositive): bool = + if x > 1.0: true else: false + +let x2 = 5.0.Positive +doAssert(strictOnlyProc(x2)) + +try: + let x4 = 0.0.Positive + discard strictOnlyProc(x4) +except: + echo "range fail expected" + \ No newline at end of file diff --git a/tests/fragmentation/tfragment_alloc.nim b/tests/fragmentation/tfragment_alloc.nim index 5a44b7434..80341d2dc 100644 --- a/tests/fragmentation/tfragment_alloc.nim +++ b/tests/fragmentation/tfragment_alloc.nim @@ -16,9 +16,12 @@ proc main = dealloc p # c_fprintf(stdout, "iteration: %ld size: %ld\n", i, size) when defined(cpu64): - # bug #7120 - var x = alloc(((1 shl 29) - 4) * 8) - dealloc x + # see https://github.com/nim-lang/Nim/issues/8509 + # this often made appveyor (on windows) fail with out of memory + when defined(posix): + # bug #7120 + var x = alloc(((1 shl 29) - 4) * 8) + dealloc x main() diff --git a/tests/fragmentation/tfragment_gc.nim b/tests/fragmentation/tfragment_gc.nim index c930ec931..d387bbea2 100644 --- a/tests/fragmentation/tfragment_gc.nim +++ b/tests/fragmentation/tfragment_gc.nim @@ -1,6 +1,7 @@ discard """ output: '''occupied ok: true total ok: true''' + disabled: "windows" """ import strutils, data diff --git a/tests/gc/cyclecollector.nim b/tests/gc/cyclecollector.nim index 46fed6c45..7b47758f2 100644 --- a/tests/gc/cyclecollector.nim +++ b/tests/gc/cyclecollector.nim @@ -16,6 +16,5 @@ proc main = var leaf = "this is the leaf. it allocates" let x = createCycle(leaf) let y = createCycle(leaf) - echo "done ", getOccupiedMem() main() diff --git a/tests/gc/gcleak.nim b/tests/gc/gcleak.nim index 8852a8d91..24ac1036a 100644 --- a/tests/gc/gcleak.nim +++ b/tests/gc/gcleak.nim @@ -6,16 +6,16 @@ when defined(GC_setMaxPause): GC_setMaxPause 2_000 type - TTestObj = object of TObject + TTestObj = object of RootObj x: string -proc MakeObj(): TTestObj = +proc makeObj(): TTestObj = result.x = "Hello" for i in 1 .. 1_000_000: when defined(gcMarkAndSweep) or defined(boehmgc): GC_fullcollect() - var obj = MakeObj() + var obj = makeObj() if getOccupiedMem() > 300_000: quit("still a leak!") # echo GC_getstatistics() diff --git a/tests/gc/gcleak2.nim b/tests/gc/gcleak2.nim index facb8a008..fe1718aef 100644 --- a/tests/gc/gcleak2.nim +++ b/tests/gc/gcleak2.nim @@ -6,11 +6,11 @@ when defined(GC_setMaxPause): GC_setMaxPause 2_000 type - TTestObj = object of TObject + TTestObj = object of RootObj x: string s: seq[int] -proc MakeObj(): TTestObj = +proc makeObj(): TTestObj = result.x = "Hello" result.s = @[1,2,3] @@ -19,7 +19,7 @@ proc inProc() = when defined(gcMarkAndSweep) or defined(boehmgc): GC_fullcollect() var obj: TTestObj - obj = MakeObj() + obj = makeObj() if getOccupiedMem() > 300_000: quit("still a leak!") inProc() diff --git a/tests/gc/gctest.nim b/tests/gc/gctest.nim index 7f5260200..25d57ff0e 100644 --- a/tests/gc/gctest.nim +++ b/tests/gc/gctest.nim @@ -66,8 +66,7 @@ proc caseTree(lvl: int = 0): PCaseNode = proc finalizeNode(n: PNode) = assert(n != nil) write(stdout, "finalizing: ") - if isNil(n.data): writeLine(stdout, "nil!") - else: writeLine(stdout, "not nil") + writeLine(stdout, "not nil") var id: int = 1 diff --git a/tests/gc/growobjcrash.nim b/tests/gc/growobjcrash.nim index a16468c7e..07f92b8f4 100644 --- a/tests/gc/growobjcrash.nim +++ b/tests/gc/growobjcrash.nim @@ -14,7 +14,7 @@ proc handleRequest(query: string): StringTableRef = let x = foo result = x() -const Limit = when compileOption("gc", "markAndSweep"): 5*1024*1024 else: 700_000 +const Limit = 5*1024*1024 proc main = var counter = 0 diff --git a/tests/gc/thavlak.nim b/tests/gc/thavlak.nim index efab49e36..cc0095fbc 100644 --- a/tests/gc/thavlak.nim +++ b/tests/gc/thavlak.nim @@ -245,7 +245,7 @@ proc findLoops(self: var HavlakLoopFinder): int = # - the list of backedges (backPreds) or # - the list of non-backedges (nonBackPreds) # - for w in 0 .. <size: + for w in 0 ..< size: header[w] = 0 types[w] = BB_NONHEADER diff --git a/tests/generics/module_with_generics.nim b/tests/generics/module_with_generics.nim new file mode 100644 index 000000000..e801a4790 --- /dev/null +++ b/tests/generics/module_with_generics.nim @@ -0,0 +1,14 @@ +type + Base[T] = ref object {.inheritable.} + value*: T + + Derived[T] = ref object of Base[T] + derivedValue*: T + +proc makeDerived*[T](v: T): Derived[T] = + new result + result.value = v + +proc setBaseValue*[T](a: Base[T], value: T) = + a.value = value + diff --git a/tests/generics/t3977.nim b/tests/generics/t3977.nim new file mode 100644 index 000000000..eed1a7d63 --- /dev/null +++ b/tests/generics/t3977.nim @@ -0,0 +1,14 @@ +discard """ + output: "42\n42" +""" + +type + Foo[N: static[int]] = object + +proc foo[N](x: Foo[N]) = + let n = N + echo N + echo n + +var f1: Foo[42] +f1.foo diff --git a/tests/generics/t5602_inheritence.nim b/tests/generics/t5602_inheritence.nim index 6d48c796e..ee5ba89d5 100644 --- a/tests/generics/t5602_inheritence.nim +++ b/tests/generics/t5602_inheritence.nim @@ -1,10 +1,11 @@ discard """ output: "seq[float]\n0" + targets: "c cpp" """ # https://github.com/nim-lang/Nim/issues/5602 -import typetraits +import typetraits, module_with_generics type Foo[T] = object of RootObj @@ -16,3 +17,8 @@ proc p[T](f: Foo[T]): T = var s: Bar[float] echo p(s).len # the bug was: p(s) should return seq[float], but returns float instead +# Test overloading and code generation when +# downcasting is required for generic types: +var d = makeDerived(10) +setBaseValue(d, 20) + diff --git a/tests/generics/t5643.nim b/tests/generics/t5643.nim index 962d5cef5..f303bbc70 100644 --- a/tests/generics/t5643.nim +++ b/tests/generics/t5643.nim @@ -1,5 +1,5 @@ type - Matrix*[M, N: static[int], T: SomeReal] = object + Matrix*[M, N: static[int], T: SomeFloat] = object data: ref array[N * M, T] Matrix64*[M, N: static[int]] = Matrix[M, N, float64] diff --git a/tests/generics/t6137.nim b/tests/generics/t6137.nim new file mode 100644 index 000000000..639675f35 --- /dev/null +++ b/tests/generics/t6137.nim @@ -0,0 +1,29 @@ +discard """ + action: "reject" + line: 29 + errormsg: "\'vectFunc\' doesn't have a concrete type, due to unspecified generic parameters." +""" + +type + # simple vector of declared fixed length + vector[N : static[int]] = array[0..N-1, float] + +proc `*`[T](x: float, a: vector[T]): vector[T] = + # multiplication by scalar + for ii in 0..high(a): + result[ii] = a[ii]*x + +let + # define a vector of length 3 + x: vector[3] = [1.0, 3.0, 5.0] + +proc vectFunc[T](x: vector[T]): vector[T] {.procvar.} = + # Define a vector function + result = 2.0*x + +proc passVectFunction[T](g: proc(x: vector[T]): vector[T], x: vector[T]): vector[T] = + # pass a vector function as input in another procedure + result = g(x) + +let + xNew = passVectFunction(vectFunc,x) diff --git a/tests/generics/t7141.nim b/tests/generics/t7141.nim new file mode 100644 index 000000000..8a128d828 --- /dev/null +++ b/tests/generics/t7141.nim @@ -0,0 +1,10 @@ +discard """ + action: "reject" + line: 7 + errormsg: "cannot instantiate: \'T\'" +""" + +proc foo[T](x: T) = + discard + +var fun = if true: foo else: foo diff --git a/tests/generics/t7794.nim b/tests/generics/t7794.nim new file mode 100644 index 000000000..b295da865 --- /dev/null +++ b/tests/generics/t7794.nim @@ -0,0 +1,15 @@ +discard """ +output: ''' +10 +2.0 +''' +""" + +type + Data*[T:SomeNumber, U:SomeReal] = ref object + x*: T + value*: U + +var d = Data[int, float64](x:10.int, value:2'f64) +echo d.x +echo d.value diff --git a/tests/generics/t8270.nim b/tests/generics/t8270.nim new file mode 100644 index 000000000..707e981fa --- /dev/null +++ b/tests/generics/t8270.nim @@ -0,0 +1,7 @@ +discard """ + line: 6 + errormsg: "cannot instantiate: \'T\'" +""" + +proc m[T](x: T): int = discard +echo [m] diff --git a/tests/generics/t8403.nim b/tests/generics/t8403.nim new file mode 100644 index 000000000..47ce9c452 --- /dev/null +++ b/tests/generics/t8403.nim @@ -0,0 +1,11 @@ +discard """ + output: "6.0" +""" + +proc sum*[T](s: seq[T], R: typedesc): R = + var sum: R = 0 + for x in s: + sum += R(x) + return sum + +echo @[1, 2, 3].sum(float) diff --git a/tests/generics/t8439.nim b/tests/generics/t8439.nim new file mode 100644 index 000000000..69bd7cfcb --- /dev/null +++ b/tests/generics/t8439.nim @@ -0,0 +1,10 @@ +discard """ + output: "1" +""" + +type + Cardinal = enum + north, east, south, west + +proc foo[cardinal: static[Cardinal]](): int = 1 +echo(foo[north]()) diff --git a/tests/generics/tcan_inherit_generic.nim b/tests/generics/tcan_inherit_generic.nim index 331fcfd5c..69e06c4a5 100644 --- a/tests/generics/tcan_inherit_generic.nim +++ b/tests/generics/tcan_inherit_generic.nim @@ -4,7 +4,7 @@ ## Created by Eric Doughty-Papassideris on 2011-02-16. type - TGen[T] = object of TObject + TGen[T] = object of RootObj x, y: T TSpef[T] = object of TGen[T] diff --git a/tests/generics/tgeneric3.nim b/tests/generics/tgeneric3.nim index d014eb998..6897d9de2 100644 --- a/tests/generics/tgeneric3.nim +++ b/tests/generics/tgeneric3.nim @@ -103,7 +103,7 @@ proc DeleteItem[T,D] (n: PNode[T,D], x: int): PNode[T,D] {.inline.} = else : result = n.left - n.slots = nil + n.slots = @[] n.left = nil proc internalDelete[T,D] (ANode: PNode[T,D], key: T, Avalue: var D): PNode[T,D] = @@ -200,7 +200,7 @@ proc traceTree[T,D](root: PNode[T,D]) = traceln(space) write stdout, "left: " doTrace(n.left, level+1) - for i, el in n.slots : + for i, el in n.slots: if el != nil and not isClean(el): traceln(space) traceX(i) diff --git a/tests/generics/tgenericprop.nim b/tests/generics/tgenericprop.nim index 7cddf5617..21ffdf289 100644 --- a/tests/generics/tgenericprop.nim +++ b/tests/generics/tgenericprop.nim @@ -1,11 +1,11 @@ type - TProperty[T] = object of TObject + TProperty[T] = object of RootObj getProc: proc(property: TProperty[T]): T {.nimcall.} setProc: proc(property: TProperty[T], value: T) {.nimcall.} value: T -proc newProperty[T](value: TObject): TProperty[T] = +proc newProperty[T](value: RootObj): TProperty[T] = result.getProc = proc (property: TProperty[T]) = return property.value diff --git a/tests/generics/tgenerics_and_inheritance.nim b/tests/generics/tgenerics_and_inheritance.nim new file mode 100644 index 000000000..ea776b517 --- /dev/null +++ b/tests/generics/tgenerics_and_inheritance.nim @@ -0,0 +1,36 @@ + +# bug #7854 + +type + Stream* = ref StreamObj + StreamObj* = object of RootObj + + InhStream* = ref InhStreamObj + InhStreamObj* = object of Stream + f: string + +proc newInhStream*(f: string): InhStream = + new(result) + result.f = f + +var val: int +let str = newInhStream("input_file.json") + +block: + # works: + proc load[T](data: var T, s: Stream) = + discard + load(val, str) + +block: + # works + proc load[T](s: Stream, data: T) = + discard + load(str, val) + +block: + # broken + proc load[T](s: Stream, data: var T) = + discard + load(str, val) + diff --git a/tests/generics/tlateboundgenericparams.nim b/tests/generics/tlateboundgenericparams.nim new file mode 100644 index 000000000..9f0580fd2 --- /dev/null +++ b/tests/generics/tlateboundgenericparams.nim @@ -0,0 +1,145 @@ +discard """ + output: "1\n10\n1\n10" + nimout: ''' +bar instantiated with 1 +bar instantiated with 10 +''' +""" + +import typetraits + +type + Foo = object + +proc defaultFoo: Foo = discard +proc defaultInt: int = 1 +proc defaultTInt(T: type): int = 2 +proc defaultTFoo[T](x: typedesc[T]): Foo = discard +proc defaultTOldSchool[T](x: typedesc[T]): T = discard +proc defaultTModern(T: type): T = discard + +proc specializedDefault(T: type int): int = 10 +proc specializedDefault(T: type string): string = "default" + +converter intFromFoo(x: Foo): int = 3 + +proc consumeInt(x: int) = + discard + +const activeTests = {1..100} + +when true: + template test(n, body) = + when n in activeTests: + block: + body + + template reject(x) = + static: assert(not compiles(x)) + + test 1: + proc t[T](val: T = defaultInt()) = + consumeInt val + + t[int]() + reject t[string]() + + test 2: + proc t1[T](val: T = defaultFoo()) = + static: + assert type(val).name == "int" + assert T.name == "int" + + consumeInt val + + # here, the converter should kick in, but notice + # how `val` is still typed `int` inside the proc. + t1[int]() + + proc t2[T](val: T = defaultFoo()) = + discard + + reject t2[string]() + + test 3: + proc tInt[T](val = defaultInt()): string = + return type(val).name + + doAssert tInt[int]() == "int" + doAssert tInt[string]() == "int" + + proc tInt2[T](val = defaultTInt(T)): string = + return type(val).name + + doAssert tInt2[int]() == "int" + doAssert tInt2[string]() == "int" + + proc tDefTModern[T](val = defaultTModern(T)): string = + return type(val).name + + doAssert tDefTModern[int]() == "int" + doAssert tDefTModern[string]() == "string" + doAssert tDefTModern[Foo]() == "Foo" + + proc tDefTOld[T](val = defaultTOldSchool(T)): string = + return type(val).name + + doAssert tDefTOld[int]() == "int" + doAssert tDefTOld[string]() == "string" + doAssert tDefTOld[Foo]() == "Foo" + + test 4: + proc t[T](val: T = defaultTFoo(T)): string = + return type(val).name + + doAssert t[int]() == "int" + doAssert t[Foo]() == "Foo" + reject t[string]() + + test 5: + proc t1[T](a: T = specializedDefault(T)): T = + return a + + doAssert t1[int]() == 10 + doAssert t1[string]() == "default" + + proc t2[T](a: T, b = specializedDefault(T)): auto = + return $a & $b + + doAssert t2(5) == "510" + doAssert t2("string ") == "string default" + + proc t3[T](a: T, b = specializedDefault(type(a))): auto = + return $a & $b + + doAssert t3(100) == "10010" + doAssert t3("another ") == "another default" + + test 6: + # https://github.com/nim-lang/Nim/issues/5595 + type + Point[T] = object + x, y: T + + proc getOrigin[T](): Point[T] = Point[T](x: 0, y: 0) + + proc rotate[T](point: Point[T], radians: float, + origin = getOrigin[T]()): Point[T] = + discard + + var p = getOrigin[float]() + var rotated = p.rotate(2.1) + + test 7: + proc bar(x: static[int]) = + static: echo "bar instantiated with ", x + echo x + + proc foo(x: static[int] = 1) = + bar(x) + + foo() + foo(10) + foo(1) + foo(10) + diff --git a/tests/generics/tobjecttyperel.nim b/tests/generics/tobjecttyperel.nim index 8c8f90098..6c2184cc2 100644 --- a/tests/generics/tobjecttyperel.nim +++ b/tests/generics/tobjecttyperel.nim @@ -2,8 +2,8 @@ discard """ output: '''(peel: 0, color: 15) (color: 15) 17 -(width: 0.0, taste: nil, color: 13) -(width: 0.0, taste: nil, color: 15) +(width: 0.0, taste: "", color: 13) +(width: 0.0, taste: "", color: 15) cool''' """ @@ -11,16 +11,16 @@ cool''' type BaseFruit[T] = object of RootObj color: T - + MidLevel[T] = object of BaseFruit[T] - + Mango = object of MidLevel[int] peel: int - + Peach[X, T, Y] = object of T width: X taste: Y - + proc setColor[T](self: var BaseFruit[T]) = self.color = 15 diff --git a/tests/generics/tparam_binding.nim b/tests/generics/tparam_binding.nim index 55acb8f06..cd0d58e02 100644 --- a/tests/generics/tparam_binding.nim +++ b/tests/generics/tparam_binding.nim @@ -4,7 +4,7 @@ discard """ """ type - Matrix[M,N: static[int]; T: SomeReal] = distinct array[0..(M*N - 1), T] + Matrix[M,N: static[int]; T: SomeFloat] = distinct array[0..(M*N - 1), T] let a = new Matrix[2,2,float] let b = new Matrix[2,1,float] diff --git a/tests/global/t5958.nim b/tests/global/t5958.nim new file mode 100644 index 000000000..5abcad4a9 --- /dev/null +++ b/tests/global/t5958.nim @@ -0,0 +1,9 @@ +discard """ + line: 9 + errormsg: "undeclared identifier: 'a'" +""" + +static: + var a = 1 + +echo a diff --git a/tests/init/t8314.nim b/tests/init/t8314.nim new file mode 100644 index 000000000..59d46eb33 --- /dev/null +++ b/tests/init/t8314.nim @@ -0,0 +1,21 @@ +discard """ + nimout: ''' +t8314.nim(8, 7) Hint: BEGIN [User] +t8314.nim(19, 7) Hint: END [User] + ''' +""" + +{.hint: "BEGIN".} +proc foo(x: range[1..10]) = + block: + var (y,) = (x,) + echo y + block: + var (_,y) = (1,x) + echo y + block: + var (y,_,) = (x,1,) + echo y +{.hint: "END".} + +foo(1) diff --git a/tests/init/tuninit1.nim b/tests/init/tuninit1.nim index 886a1d766..891f1e96c 100644 --- a/tests/init/tuninit1.nim +++ b/tests/init/tuninit1.nim @@ -22,7 +22,7 @@ proc p = try: z = parseInt("1233") - except E_Base: + except Exception: case x of 34: z = 123 of 13: z = 34 diff --git a/tests/iter/t338.nim b/tests/iter/t338.nim new file mode 100644 index 000000000..dbced8b60 --- /dev/null +++ b/tests/iter/t338.nim @@ -0,0 +1,20 @@ +discard """ + output: '''0 +1 +2 +3 +4 +''' +""" + +proc moo(): iterator (): int = + iterator fooGen: int {.closure.} = + while true: + yield result + result.inc + return fooGen + +var foo = moo() + +for i in 0 .. 4: + echo foo() diff --git a/tests/iter/t8041.nim b/tests/iter/t8041.nim new file mode 100644 index 000000000..c87134cfc --- /dev/null +++ b/tests/iter/t8041.nim @@ -0,0 +1,7 @@ +iterator xy[T](a: T, b: set[T]): T = + if a in b: + yield a + +for a in xy(1'i8, {}): + for b in xy(a, {}): + echo a diff --git a/tests/iter/tobj_iter.nim b/tests/iter/tobj_iter.nim index eb0e37b23..a894755d7 100644 --- a/tests/iter/tobj_iter.nim +++ b/tests/iter/tobj_iter.nim @@ -4,8 +4,6 @@ discard """ # bug #2023 -{.deadCodeElim:on.} - type Obj = object iter: iterator (): int8 {.closure.} diff --git a/tests/iter/tyieldintry.nim b/tests/iter/tyieldintry.nim new file mode 100644 index 000000000..6d24417a6 --- /dev/null +++ b/tests/iter/tyieldintry.nim @@ -0,0 +1,443 @@ +discard """ +targets: "c cpp" +output: "ok" +""" +var closureIterResult = newSeq[int]() + +proc checkpoint(arg: int) = + closureIterResult.add(arg) + +type + TestException = object of Exception + AnotherException = object of Exception + +proc testClosureIterAux(it: iterator(): int, exceptionExpected: bool, expectedResults: varargs[int]) = + closureIterResult.setLen(0) + + var exceptionCaught = false + + try: + for i in it(): + closureIterResult.add(i) + except TestException: + exceptionCaught = true + + if closureIterResult != @expectedResults or exceptionCaught != exceptionExpected: + if closureIterResult != @expectedResults: + echo "Expected: ", @expectedResults + echo "Actual: ", closureIterResult + if exceptionCaught != exceptionExpected: + echo "Expected exception: ", exceptionExpected + echo "Got exception: ", exceptionCaught + doAssert(false) + +proc test(it: iterator(): int, expectedResults: varargs[int]) = + testClosureIterAux(it, false, expectedResults) + +proc testExc(it: iterator(): int, expectedResults: varargs[int]) = + testClosureIterAux(it, true, expectedResults) + +proc raiseException() = + raise newException(TestException, "Test exception!") + +block: + iterator it(): int {.closure.} = + var i = 5 + while i != 0: + yield i + if i == 3: + yield 123 + dec i + + test(it, 5, 4, 3, 123, 2, 1) + +block: + iterator it(): int {.closure.} = + yield 0 + try: + checkpoint(1) + raiseException() + except TestException: + checkpoint(2) + yield 3 + checkpoint(4) + finally: + checkpoint(5) + + checkpoint(6) + + test(it, 0, 1, 2, 3, 4, 5, 6) + +block: + iterator it(): int {.closure.} = + yield 0 + try: + yield 1 + checkpoint(2) + finally: + checkpoint(3) + yield 4 + checkpoint(5) + yield 6 + + test(it, 0, 1, 2, 3, 4, 5, 6) + +block: + iterator it(): int {.closure.} = + yield 0 + try: + yield 1 + raiseException() + yield 2 + finally: + checkpoint(3) + yield 4 + checkpoint(5) + yield 6 + + testExc(it, 0, 1, 3, 4, 5, 6) + +block: + iterator it(): int {.closure.} = + try: + try: + raiseException() + except AnotherException: + yield 123 + finally: + checkpoint(3) + finally: + checkpoint(4) + + testExc(it, 3, 4) + +block: + iterator it(): int {.closure.} = + try: + yield 1 + raiseException() + except AnotherException: + checkpoint(123) + finally: + checkpoint(2) + checkpoint(3) + + testExc(it, 1, 2) + +block: + iterator it(): int {.closure.} = + try: + yield 0 + try: + yield 1 + try: + yield 2 + raiseException() + except AnotherException: + yield 123 + finally: + yield 3 + except AnotherException: + yield 124 + finally: + yield 4 + checkpoint(1234) + except: + yield 5 + checkpoint(6) + finally: + checkpoint(7) + yield 8 + checkpoint(9) + + test(it, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + +block: + iterator it(): int {.closure.} = + try: + yield 0 + return 2 + finally: + checkpoint(1) + checkpoint(123) + + test(it, 0, 1) + +block: + iterator it(): int {.closure.} = + try: + try: + yield 0 + raiseException() + finally: + checkpoint(1) + except TestException: + yield 2 + return + finally: + yield 3 + + checkpoint(123) + + test(it, 0, 1, 2, 3) + +block: + iterator it(): int {.closure.} = + try: + try: + yield 0 + raiseException() + finally: + return # Return in finally should stop exception propagation + except AnotherException: + yield 2 + return + finally: + yield 3 + checkpoint(123) + + test(it, 0, 3) + +block: # Yield in yield + iterator it(): int {.closure.} = + template foo(): int = + yield 1 + 2 + + for i in 0 .. 2: + checkpoint(0) + yield foo() + + test(it, 0, 1, 2, 0, 1, 2, 0, 1, 2) + +block: + iterator it(): int {.closure.} = + let i = if true: + yield 0 + 1 + else: + 2 + yield i + + test(it, 0, 1) + +block: + iterator it(): int {.closure.} = + var foo = 123 + let i = try: + yield 0 + raiseException() + 1 + except TestException as e: + assert(e.msg == "Test exception!") + case foo + of 1: + yield 123 + 2 + of 123: + yield 5 + 6 + else: + 7 + yield i + + test(it, 0, 5, 6) + +block: + iterator it(): int {.closure.} = + proc voidFoo(i1, i2, i3: int) = + checkpoint(i1) + checkpoint(i2) + checkpoint(i3) + + proc foo(i1, i2, i3: int): int = + voidFoo(i1, i2, i3) + i3 + + proc bar(i1: int): int = + checkpoint(i1) + + template tryexcept: int = + try: + yield 1 + raiseException() + 123 + except TestException: + yield 2 + checkpoint(3) + 4 + + let e1 = true + + template ifelse1: int = + if e1: + yield 10 + 11 + else: + 12 + + template ifelse2: int = + if ifelse1() == 12: + yield 20 + 21 + else: + yield 22 + 23 + + let i = foo(bar(0), tryexcept, ifelse2) + discard foo(bar(0), tryexcept, ifelse2) + voidFoo(bar(0), tryexcept, ifelse2) + yield i + + test(it, + + # let i = foo(bar(0), tryexcept, ifelse2) + 0, # bar(0) + 1, 2, 3, # tryexcept + 10, # ifelse1 + 22, # ifelse22 + 0, 4, 23, # foo + + # discard foo(bar(0), tryexcept, ifelse2) + 0, # bar(0) + 1, 2, 3, # tryexcept + 10, # ifelse1 + 22, # ifelse22 + 0, 4, 23, # foo + + # voidFoo(bar(0), tryexcept, ifelse2) + 0, # bar(0) + 1, 2, 3, # tryexcept + 10, # ifelse1 + 22, # ifelse22 + 0, 4, 23, # foo + + 23 # i + ) + +block: + iterator it(): int {.closure.} = + checkpoint(0) + for i in 0 .. 1: + try: + yield 1 + raiseException() + except TestException as e: + doAssert(e.msg == "Test exception!") + yield 2 + except AnotherException: + yield 123 + except: + yield 1234 + finally: + yield 3 + checkpoint(4) + yield 5 + + test(it, 0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5) + +block: + iterator it(): int {.closure.} = + var i = 5 + template foo(): bool = + yield i + true + + while foo(): + dec i + if i == 0: + break + + test(it, 5, 4, 3, 2, 1) + +block: # Short cirquits + iterator it(): int {.closure.} = + template trueYield: bool = + yield 1 + true + + template falseYield: bool = + yield 0 + false + + if trueYield or falseYield: + discard falseYield and trueYield + + if falseYield and trueYield: + checkpoint(123) + + test(it, 1, 0, 0) + +block: #7969 + type + SomeObj = object + id: int + + iterator it(): int {.closure.} = + template yieldAndSomeObj: SomeObj = + var s: SomeObj + s.id = 2 + yield 1 + s + + checkpoint(yieldAndSomeObj().id) + + var i = 5 + case i + of 0: + checkpoint(123) + of 1, 2, 5: + checkpoint(3) + else: + checkpoint(123) + + test(it, 1, 2, 3) + +block: # yield in blockexpr + iterator it(): int {.closure.} = + yield(block: + checkpoint(1) + yield 2 + 3 + ) + + test(it, 1, 2, 3) + +block: #8851 + type + Foo = ref object of RootObj + template someFoo(): Foo = + var f: Foo + yield 1 + f + iterator it(): int {.closure.} = + var o: RootRef + o = someFoo() + + test(it, 1) + +block: # 8243 + iterator it(): int {.closure.} = + template yieldAndSeq: seq[int] = + yield 1 + @[123, 5, 123] + + checkpoint(yieldAndSeq[1]) + + test(it, 1, 5) + +block: + iterator it(): int {.closure.} = + template yieldAndSeq: seq[int] = + yield 1 + @[123, 5, 123] + + template yieldAndNum: int = + yield 2 + 1 + + checkpoint(yieldAndSeq[yieldAndNum]) + + test(it, 1, 2, 5) + +echo "ok" diff --git a/tests/js/t7224.nim b/tests/js/t7224.nim new file mode 100644 index 000000000..2d7ee1336 --- /dev/null +++ b/tests/js/t7224.nim @@ -0,0 +1,26 @@ +discard """ + cmd: "nim $target $options --stackTrace:on --lineTrace:on $file" + outputsub: ''' +t7224.aaa, line: 21 +t7224.bbb, line: 18 +t7224.ccc, line: 15 +t7224.ddd, line: 12 +''' +""" + +proc ddd() = + raise newException(IOError, "didn't do stuff") + +proc ccc() = + ddd() + +proc bbb() = + ccc() + +proc aaa() = + bbb() + +try: + aaa() +except IOError as e: + echo getStackTrace(e) diff --git a/tests/js/t7249.nim b/tests/js/t7249.nim new file mode 100644 index 000000000..52eee2f7c --- /dev/null +++ b/tests/js/t7249.nim @@ -0,0 +1,21 @@ +discard """ + output: ''' +a -> 2 +a <- 2 +''' +""" + +import jsffi + +var a = JsAssoc[cstring, int]{a: 2} + +for z, b in a: + echo z, " -> ", b + +proc f = + var a = JsAssoc[cstring, int]{a: 2} + + for z, b in a: + echo z, " <- ", b + +f() diff --git a/tests/js/t7534.nim b/tests/js/t7534.nim new file mode 100644 index 000000000..64aadb8d6 --- /dev/null +++ b/tests/js/t7534.nim @@ -0,0 +1,7 @@ +proc f(x: int): int = + result = case x + of 1: 2 + elif x == 2: 3 + else: 1 + +doAssert 2 == f(f(f(f(1)))) diff --git a/tests/js/t8914.nim b/tests/js/t8914.nim new file mode 100644 index 000000000..ff716b42a --- /dev/null +++ b/tests/js/t8914.nim @@ -0,0 +1,12 @@ +discard """ + output: ''' +@[42] +@[24, 42] +''' +""" + +var x = @[42,4242] +x.delete(1) +echo x +x.insert(24) +echo x diff --git a/tests/js/tasync_pragma.nim b/tests/js/tasync_pragma.nim new file mode 100644 index 000000000..916769fad --- /dev/null +++ b/tests/js/tasync_pragma.nim @@ -0,0 +1,27 @@ +discard """ + output: ''' +0 +t +''' +""" + +import asyncjs, macros + +macro f*(a: untyped): untyped = + assert a.kind == nnkProcDef + result = nnkProcDef.newTree(a.name, a[1], a[2], a.params, a.pragma, a[5], nnkStmtList.newTree()) + let call = quote: + echo 0 + result.body.add(call) + for child in a.body: + result.body.add(child) + #echo result.body.repr + +proc t* {.async, f.} = + echo "t" + +proc t0* {.async.} = + await t() + +discard t0() + diff --git a/tests/js/tclosures.nim b/tests/js/tclosures.nim index 67243c937..659c60092 100644 --- a/tests/js/tclosures.nim +++ b/tests/js/tclosures.nim @@ -48,4 +48,4 @@ for i in 1 .. 10: let results = runCallbacks() -doAssert(expected == results) +doAssert(expected == $results) diff --git a/tests/js/testobjs.nim b/tests/js/testobjs.nim index dd66825ec..78f0b4766 100644 --- a/tests/js/testobjs.nim +++ b/tests/js/testobjs.nim @@ -34,7 +34,7 @@ var recurse1 = Recurse[int](data: 1, next: recurse2) -doAssert test.name == "Jorden" +doAssert test.name == cstring"Jorden" doAssert knight.age == 19 doAssert knight.item.price == 50 doAssert recurse1.next.next.data == 3 diff --git a/tests/js/tjsffi.nim b/tests/js/tjsffi.nim index 325ab6366..213d05964 100644 --- a/tests/js/tjsffi.nim +++ b/tests/js/tjsffi.nim @@ -64,7 +64,7 @@ block: proc test(): bool = let obj = newJsObject() obj.`?!$` = proc(x, y, z: int, t: cstring): cstring = t & $(x + y + z) - obj.`?!$`(1, 2, 3, "Result is: ").to(cstring) == "Result is: 6" + obj.`?!$`(1, 2, 3, "Result is: ").to(cstring) == cstring"Result is: 6" echo test() # Test JsObject []() @@ -267,8 +267,8 @@ block: type TestObject = object a: int onWhatever: proc(e: int): int - proc handleWhatever(that: TestObject, e: int): int = - e + that.a + proc handleWhatever(this: TestObject, e: int): int = + e + this.a proc test(): bool = let obj = TestObject(a: 9, onWhatever: bindMethod(handleWhatever)) obj.onWhatever(1) == 10 diff --git a/tests/js/tmangle.nim b/tests/js/tmangle.nim index c4167ba39..c97bf7029 100644 --- a/tests/js/tmangle.nim +++ b/tests/js/tmangle.nim @@ -27,10 +27,10 @@ block: var global = T(a: 11, b: "foo") proc test(): bool = var obj = T(a: 11, b: "foo") - {. emit: [result, " = (", obj.addr[], "[0].a == 11);"] .} - {. emit: [result, " = ", result, " && (", obj.addr[], "[0].b == \"foo\");"] .} - {. emit: [result, " = ", result, " && (", global, "[0].a == 11);"] .} - {. emit: [result, " = ", result, " && (", global, "[0].b == \"foo\");"] .} + {. emit: [result, " = (", obj.addr[], ".a == 11);"] .} + {. emit: [result, " = ", result, " && (", obj.addr[], ".b == \"foo\");"] .} + {. emit: [result, " = ", result, " && (", global, ".a == 11);"] .} + {. emit: [result, " = ", result, " && (", global, ".b == \"foo\");"] .} echo test() # Test addr of field: diff --git a/tests/js/trepr.nim b/tests/js/trepr.nim index 3f07e6793..366d247c5 100644 --- a/tests/js/trepr.nim +++ b/tests/js/trepr.nim @@ -103,7 +103,7 @@ block strings: when defined js: # C prepends the pointer, JS does not. doAssert(repr(a) == "\"12345\"") doAssert(repr(b) == "\"hello,repr\"") - doAssert(repr(c) == "\"hi\nthere\"") + doAssert(repr(c) == "\"hi\\10there\"") block sets: let diff --git a/tests/js/tseqops.nim b/tests/js/tseqops.nim index d10e1ca6a..af664222c 100644 --- a/tests/js/tseqops.nim +++ b/tests/js/tseqops.nim @@ -1,8 +1,8 @@ discard """ output: '''(x: 0, y: 0) (x: 5, y: 0) -@[(x: 2, y: 4), (x: 4, y: 5), (x: 4, y: 5)] -@[(a: 3, b: 3), (a: 1, b: 1), (a: 2, b: 2)] +@[(x: "2", y: 4), (x: "4", y: 5), (x: "4", y: 5)] +@[(a: "3", b: 3), (a: "1", b: 1), (a: "2", b: 2)] ''' """ diff --git a/tests/js/tstring_assignment.nim b/tests/js/tstring_assignment.nim index bdd93e6b5..97ffa748f 100644 --- a/tests/js/tstring_assignment.nim +++ b/tests/js/tstring_assignment.nim @@ -1,5 +1,6 @@ discard """ - output: '''true''' + output: '''true +asdfasekjkler''' """ # bug #4471 @@ -9,3 +10,12 @@ when true: s2.setLen(0) # fails - s1.len == 0 echo s1.len == 3 + +# bug #4470 +proc main(s: cstring): string = + result = newString(0) + for i in 0..<s.len: + if s[i] >= 'a' and s[i] <= 'z': + result.add s[i] + +echo main("asdfasekjkleräöü") diff --git a/tests/js/tstringitems.nim b/tests/js/tstringitems.nim index 20aed6e8b..f09793dde 100644 --- a/tests/js/tstringitems.nim +++ b/tests/js/tstringitems.nim @@ -45,7 +45,7 @@ block: # Test compile-time binary data generation, invalid unicode block: # Test unicode strings const constStr = "Привет!" var jsStr : cstring - {.emit: """`jsStr`[0] = "Привет!";""".} + {.emit: """`jsStr` = "Привет!";""".} doAssert($jsStr == constStr) var runtimeStr = "При" @@ -76,3 +76,12 @@ block: # String case of case s of "Привет!": discard else: doAssert(false) + +block: # String cmp + var a, b: string + doAssert(cmp(a, b) == 0) + doAssert(cmp("foo", "foo") == 0) + doAssert(cmp("foobar", "foo") == 3) + doAssert(cmp("foo", "foobar") == -3) + doAssert(cmp("fooz", "foog") == 19) + doAssert(cmp("foog", "fooz") == -19) diff --git a/tests/js/tthismangle.nim b/tests/js/tthismangle.nim new file mode 100644 index 000000000..880abcc83 --- /dev/null +++ b/tests/js/tthismangle.nim @@ -0,0 +1,23 @@ +proc moo1(this: int) = + doAssert this == 42 + +proc moo2(x: int) = + var this = x + doAssert this == 42 + +proc moo3() = + for this in [1,1,1]: + doAssert this == 1 + +proc moo4() = + type + X = object + this: int + + var q = X(this: 42) + doAssert q.this == 42 + +moo1(42) +moo2(42) +moo3() +moo4() diff --git a/tests/js/ttimes.nim b/tests/js/ttimes.nim deleted file mode 100644 index 63a4bb04f..000000000 --- a/tests/js/ttimes.nim +++ /dev/null @@ -1,44 +0,0 @@ -discard """ - action: run -""" - -import times - -# $ date --date='@2147483647' -# Tue 19 Jan 03:14:07 GMT 2038 - -block yeardayTest: - doAssert fromUnix(2147483647).utc.yearday == 18 - -block localTime: - var local = now() - let utc = local.utc - doAssert local.toTime == utc.toTime - -let a = fromUnix(1_000_000_000) -let b = fromUnix(1_500_000_000) -doAssert b - a == 500_000_000 - -# Because we can't change the timezone JS uses, we define a simple static timezone for testing. - -proc staticZoneInfoFromUtc(time: Time): ZonedTime = - result.utcOffset = -7200 - result.isDst = false - result.adjTime = (time.toUnix + 7200).Time - -proc staticZoneInfoFromTz(adjTime: Time): ZonedTIme = - result.utcOffset = -7200 - result.isDst = false - result.adjTime = adjTime - -let utcPlus2 = Timezone(zoneInfoFromUtc: staticZoneInfoFromUtc, zoneInfoFromTz: staticZoneInfoFromTz, name: "") - -block timezoneTests: - let dt = initDateTime(01, mJan, 2017, 12, 00, 00, utcPlus2) - doAssert $dt == "2017-01-01T12:00:00+02:00" - doAssert $dt.utc == "2017-01-01T10:00:00+00:00" - doAssert $dt.utc.inZone(utcPlus2) == $dt - -doAssert $initDateTime(01, mJan, 1911, 12, 00, 00, utc()) == "1911-01-01T12:00:00+00:00" -# See #6752 -# doAssert $initDateTime(01, mJan, 1900, 12, 00, 00, utc()) == "0023-01-01T12:00:00+00:00" \ No newline at end of file diff --git a/tests/js/tvarargs.nim b/tests/js/tvarargs.nim index 2ddb421f8..b8c532767 100644 --- a/tests/js/tvarargs.nim +++ b/tests/js/tvarargs.nim @@ -1,3 +1,6 @@ +discard """ + output: "Hello, world" +""" # bug #3584 diff --git a/tests/lexer/thexlit.nim b/tests/lexer/thexlit.nim deleted file mode 100644 index 2b7f0a40e..000000000 --- a/tests/lexer/thexlit.nim +++ /dev/null @@ -1,12 +0,0 @@ -discard """ - file: "thexlit.nim" - output: "equal" -""" - -var t=0x950412DE - -if t==0x950412DE: - echo "equal" -else: - echo "not equal" - diff --git a/tests/lexer/thexrange.nim b/tests/lexer/thexrange.nim deleted file mode 100644 index 461e41dfd..000000000 --- a/tests/lexer/thexrange.nim +++ /dev/null @@ -1,8 +0,0 @@ - -type - TArray = array[0x0012..0x0013, int] - -var a: TArray - -echo a[0x0012] #OUT 0 - diff --git a/tests/lexer/tident.nim b/tests/lexer/tident.nim index 68cc01e70..3327344a5 100644 --- a/tests/lexer/tident.nim +++ b/tests/lexer/tident.nim @@ -1,6 +1,6 @@ type - TIdObj* = object of TObject + TIdObj* = object of RootObj id*: int # unique id; use this for comparisons and not the pointers PIdObj* = ref TIdObj diff --git a/tests/lexer/tintegerliterals.nim b/tests/lexer/tintegerliterals.nim new file mode 100644 index 000000000..7420db144 --- /dev/null +++ b/tests/lexer/tintegerliterals.nim @@ -0,0 +1,9 @@ +# test the valid literals +assert 0b10 == 2 +assert 0B10 == 2 +assert 0x10 == 16 +assert 0X10 == 16 +assert 0o10 == 8 +# the following is deprecated: +assert 0c10 == 8 +assert 0C10 == 8 diff --git a/tests/lexer/tinvalidintegerliteral1.nim b/tests/lexer/tinvalidintegerliteral1.nim new file mode 100644 index 000000000..08ab82a22 --- /dev/null +++ b/tests/lexer/tinvalidintegerliteral1.nim @@ -0,0 +1,7 @@ +discard """ + file: "tinvalidintegerliteral1.nim" + line: 7 + errormsg: "invalid number" +""" + +echo 0b diff --git a/tests/lexer/tinvalidintegerliteral2.nim b/tests/lexer/tinvalidintegerliteral2.nim new file mode 100644 index 000000000..bc8793e4e --- /dev/null +++ b/tests/lexer/tinvalidintegerliteral2.nim @@ -0,0 +1,7 @@ +discard """ + file: "tinvalidintegerliteral2.nim" + line: 7 + errormsg: "invalid number" +""" + +echo 0x diff --git a/tests/lexer/tinvalidintegerliteral3.nim b/tests/lexer/tinvalidintegerliteral3.nim new file mode 100644 index 000000000..9c2fe69df --- /dev/null +++ b/tests/lexer/tinvalidintegerliteral3.nim @@ -0,0 +1,7 @@ +discard """ + file: "tinvalidintegerliteral3.nim" + line: 7 + errormsg: "0O5 is an invalid int literal; For octal literals use the '0o' prefix." +""" + +echo 0O5 diff --git a/tests/lexer/tlexermisc.nim b/tests/lexer/tlexermisc.nim new file mode 100644 index 000000000..3e3993599 --- /dev/null +++ b/tests/lexer/tlexermisc.nim @@ -0,0 +1,27 @@ +discard """ + action: run + output: "equal" +""" + +var t=0x950412DE + +if t==0x950412DE: + echo "equal" +else: + echo "not equal" + +type + TArray = array[0x0012..0x0013, int] + +var a: TArray + +doAssert a[0x0012] == 0 + + +# #7884 + +type Obj = object + ö: int + +let o = Obj(ö: 1) +doAssert o.ö == 1 diff --git a/tests/lexer/tmissingnl.nim b/tests/lexer/tmissingnl.nim index 33b7debf1..095d9570e 100644 --- a/tests/lexer/tmissingnl.nim +++ b/tests/lexer/tmissingnl.nim @@ -4,7 +4,7 @@ discard """ errormsg: "invalid indentation" """ -import strutils var s: seq[int] = @[0, 1, 2, 3, 4, 5, 6] +import strutils let s: seq[int] = @[0, 1, 2, 3, 4, 5, 6] #s[1..3] = @[] diff --git a/tests/lexer/tunderscores.nim b/tests/lexer/tunderscores.nim index e718fb40a..f64f36977 100644 --- a/tests/lexer/tunderscores.nim +++ b/tests/lexer/tunderscores.nim @@ -1,7 +1,7 @@ discard """ file: "tunderscores.nim" line: 8 - errormsg: "invalid token: _" + errormsg: "invalid token: trailing underscore" """ # Bug #502670 diff --git a/tests/macros/t7454.nim b/tests/macros/t7454.nim new file mode 100644 index 000000000..e527de0c3 --- /dev/null +++ b/tests/macros/t7454.nim @@ -0,0 +1,8 @@ +discard """ +errormsg: "expression has no type:" +line: 8 +""" + +macro p(t: typedesc): typedesc = + discard +var a: p(int) diff --git a/tests/macros/t8706.nim b/tests/macros/t8706.nim new file mode 100644 index 000000000..b8640a80d --- /dev/null +++ b/tests/macros/t8706.nim @@ -0,0 +1,21 @@ +discard """ + output: '''0 +0 +''' +""" + +import macros + +macro varargsLen(args:varargs[untyped]): untyped = + doAssert args.kind == nnkArglist + doAssert args.len == 0 + result = newLit(args.len) + +template bar(a0:varargs[untyped]): untyped = + varargsLen(a0) + +template foo(x: int, a0:varargs[untyped]): untyped = + bar(a0) + +echo foo(42) +echo bar() diff --git a/tests/macros/tbindsym.nim b/tests/macros/tbindsym.nim index 6289d3eb2..2abcd98ce 100644 --- a/tests/macros/tbindsym.nim +++ b/tests/macros/tbindsym.nim @@ -1,4 +1,9 @@ discard """ + msg: '''initApple +deinitApple +Coral +enum + redCoral, blackCoral''' output: '''TFoo TBar''' """ @@ -23,3 +28,40 @@ macro test: untyped = bindSym("TBar")) test() + +# issue 7827, bindSym power up +{.experimental: "dynamicBindSym".} +type + Apple = ref object + name: string + color: int + weight: int + +proc initApple(name: string): Apple = + discard + +proc deinitApple(x: Apple) = + discard + +macro wrapObject(obj: typed, n: varargs[untyped]): untyped = + let m = n[0] + for x in m: + var z = bindSym x + echo z.repr + +wrapObject(Apple): + initApple + deinitApple + +type + Coral = enum + redCoral + blackCoral + +macro mixer(): untyped = + let m = "Co" & "ral" + let x = bindSym(m) + echo x.repr + echo getType(x).repr + +mixer() diff --git a/tests/macros/tdumpastgen.nim b/tests/macros/tdumpastgen.nim index faed77225..0a1836886 100644 --- a/tests/macros/tdumpastgen.nim +++ b/tests/macros/tdumpastgen.nim @@ -2,16 +2,33 @@ discard """ msg: '''nnkStmtList.newTree( nnkVarSection.newTree( nnkIdentDefs.newTree( - newIdentNode(!"x"), + newIdentNode("x"), newEmptyNode(), nnkCall.newTree( nnkDotExpr.newTree( - newIdentNode(!"foo"), - newIdentNode(!"create") + newIdentNode("baz"), + newIdentNode("create") ), newLit(56) ) ) + ), + nnkProcDef.newTree( + newIdentNode("foo"), + newEmptyNode(), + newEmptyNode(), + nnkFormalParams.newTree( + newEmptyNode() + ), + newEmptyNode(), + newEmptyNode(), + nnkStmtList.newTree( + newCommentStmtNode("This is a docstring"), + nnkCommand.newTree( + newIdentNode("echo"), + newLit("bar") + ) + ) ) )''' """ @@ -21,5 +38,8 @@ msg: '''nnkStmtList.newTree( import macros dumpAstGen: - var x = foo.create(56) + var x = baz.create(56) + proc foo() = + ## This is a docstring + echo "bar" diff --git a/tests/macros/tforloop_macro1.nim b/tests/macros/tforloop_macro1.nim new file mode 100644 index 000000000..49918563d --- /dev/null +++ b/tests/macros/tforloop_macro1.nim @@ -0,0 +1,44 @@ +discard """ + output: '''0 1 +1 2 +2 3 +0 1 +1 2 +2 3 +0 1 +1 2 +2 3 +3 5''' +""" + +import macros +{.experimental: "forLoopMacros".} +macro mymacro(): untyped = + result = newLit([1, 2, 3]) + +for a, b in mymacro(): + echo a, " ", b + +macro enumerate(x: ForLoopStmt): untyped = + expectKind x, nnkForStmt + # we strip off the first for loop variable and use + # it as an integer counter: + result = newStmtList() + result.add newVarStmt(x[0], newLit(0)) + var body = x[^1] + if body.kind != nnkStmtList: + body = newTree(nnkStmtList, body) + body.add newCall(bindSym"inc", x[0]) + var newFor = newTree(nnkForStmt) + for i in 1..x.len-3: + newFor.add x[i] + # transform enumerate(X) to 'X' + newFor.add x[^2][1] + newFor.add body + result.add newFor + +for a, b in enumerate(items([1, 2, 3])): + echo a, " ", b + +for a2, b2 in enumerate([1, 2, 3, 5]): + echo a2, " ", b2 diff --git a/tests/macros/tgensym.nim b/tests/macros/tgensym.nim index 955168939..1237b8bf7 100644 --- a/tests/macros/tgensym.nim +++ b/tests/macros/tgensym.nim @@ -28,6 +28,7 @@ macro async2(prc: untyped): untyped = # -> iterator nameIter(): FutureBase {.closure.} = <proc_body> # Changing this line to: newIdentNode($prc[0].ident & "Iter") # will make it work. var iteratorNameSym = genSym(nskIterator, $prc[0].ident & "Iter") + assert iteratorNameSym.symKind == nskIterator #var iteratorNameSym = newIdentNode($prc[0].ident & "Iter") var procBody = prc[6].convertReturns(retFutureSym) @@ -42,6 +43,7 @@ macro async2(prc: untyped): untyped = var varNameIter = newVarStmt(varNameIterSym, iteratorNameSym) outerProcBody.add varNameIter var varFirstSym = genSym(nskVar, "first") + assert varFirstSym.symKind == nskVar var varFirst = newVarStmt(varFirstSym, newCall(varNameIterSym)) outerProcBody.add varFirst diff --git a/tests/macros/tgetimpl.nim b/tests/macros/tgetimpl.nim index d38492934..715c969f3 100644 --- a/tests/macros/tgetimpl.nim +++ b/tests/macros/tgetimpl.nim @@ -1,6 +1,7 @@ discard """ msg: '''"muhaha" proc poo(x, y: int) = + let y = x echo ["poo"]''' """ @@ -10,11 +11,39 @@ const foo = "muhaha" proc poo(x, y: int) = + let y = x echo "poo" macro m(x: typed): untyped = - echo repr x.symbol.getImpl + echo repr x.getImpl result = x discard m foo discard m poo + +#------------ + +macro checkOwner(x: typed, check_id: static[int]): untyped = + let sym = case check_id: + of 0: x + of 1: x.getImpl.body[0][0][0] + of 2: x.getImpl.body[0][0][^1] + of 3: x.getImpl.body[1][0] + else: x + result = newStrLitNode($sym.owner.symKind) + +macro isSameOwner(x, y: typed): untyped = + result = + if x.owner == y.owner: bindSym"true" + else: bindSym"false" + + +static: + doAssert checkOwner(foo, 0) == "nskModule" + doAssert checkOwner(poo, 0) == "nskModule" + doAssert checkOwner(poo, 1) == "nskProc" + doAssert checkOwner(poo, 2) == "nskProc" + doAssert checkOwner(poo, 3) == "nskModule" + doAssert isSameOwner(foo, poo) + doAssert isSameOwner(foo, echo) == false + doAssert isSameOwner(poo, len) == false diff --git a/tests/macros/tlineinfo.nim b/tests/macros/tlineinfo.nim new file mode 100644 index 000000000..2ab0e1ee8 --- /dev/null +++ b/tests/macros/tlineinfo.nim @@ -0,0 +1,14 @@ +# issue #5617, feature request +# Ability to set a NimNode's lineinfo +import macros + +type + Test = object + +macro mixer(n: typed): untyped = + let x = newIdentNode("echo") + x.copyLineInfo(n) + result = newLit(x.lineInfo == n.lineInfo) + +var z = mixer(Test) +doAssert z diff --git a/tests/macros/tmacro1.nim b/tests/macros/tmacro1.nim index ac6bd02a5..ac2bf9094 100644 --- a/tests/macros/tmacro1.nim +++ b/tests/macros/tmacro1.nim @@ -18,5 +18,64 @@ macro test*(a: untyped): untyped = t.b = true t.z = 4.5 + test: "hi" + +import strutils + +template assertNot(arg: untyped): untyped = + assert(not(arg)) + + +proc foo(arg: int): void = + discard + +proc foo(arg: float): void = + discard + +static: + ## test eqIdent + let a = "abc_def" + let b = "abcDef" + let c = "AbcDef" + let d = nnkBracketExpr.newTree() # not an identifier at all + + assert eqIdent( a , b ) + assert eqIdent(newIdentNode(a), b ) + assert eqIdent( a , newIdentNode(b)) + assert eqIdent(newIdentNode(a), newIdentNode(b)) + + assert eqIdent( a , b ) + assert eqIdent(genSym(nskLet, a), b ) + assert eqIdent( a , genSym(nskLet, b)) + assert eqIdent(genSym(nskLet, a), genSym(nskLet, b)) + + assert eqIdent(newIdentNode( a), newIdentNode( b)) + assert eqIdent(genSym(nskLet, a), newIdentNode( b)) + assert eqIdent(newIdentNode( a), genSym(nskLet, b)) + assert eqIdent(genSym(nskLet, a), genSym(nskLet, b)) + + assertNot eqIdent( c , b ) + assertNot eqIdent(newIdentNode(c), b ) + assertNot eqIdent( c , newIdentNode(b)) + assertNot eqIdent(newIdentNode(c), newIdentNode(b)) + + assertNot eqIdent( c , b ) + assertNot eqIdent(genSym(nskLet, c), b ) + assertNot eqIdent( c , genSym(nskLet, b)) + assertNot eqIdent(genSym(nskLet, c), genSym(nskLet, b)) + + assertNot eqIdent(newIdentNode( c), newIdentNode( b)) + assertNot eqIdent(genSym(nskLet, c), newIdentNode( b)) + assertNot eqIdent(newIdentNode( c), genSym(nskLet, b)) + assertNot eqIdent(genSym(nskLet, c), genSym(nskLet, b)) + + # eqIdent on non identifier at all + assertNot eqIdent(a,d) + + # eqIdent on sym choice + let fooSym = bindSym"foo" + assert fooSym.kind in {nnkOpenSymChoice, nnkClosedSymChoice} + assert fooSym.eqIdent("fOO") + assertNot fooSym.eqIdent("bar") diff --git a/tests/macros/tmacros1.nim b/tests/macros/tmacros1.nim index 9e3ab028b..80afb6641 100644 --- a/tests/macros/tmacros1.nim +++ b/tests/macros/tmacros1.nim @@ -12,12 +12,12 @@ macro outterMacro*(n, blck: untyped): untyped = echo "Using arg ! " & n.repr result = "Got: '" & $n.kind & "' " & $j var callNode = n[0] - expectKind(n, TNimrodNodeKind.nnkCall) - if n.len != 3 or n[1].kind != TNimrodNodeKind.nnkIdent: + expectKind(n, NimNodeKind.nnkCall) + if n.len != 3 or n[1].kind != NimNodeKind.nnkIdent: error("Macro " & callNode.repr & " requires the ident passed as parameter (eg: " & callNode.repr & "(the_name_you_want)): statements.") - result = newNimNode(TNimrodNodeKind.nnkStmtList) + result = newNimNode(NimNodeKind.nnkStmtList) var ass : NimNode = newNimNode(nnkAsgn) ass.add(newIdentNode(n[1].ident)) ass.add(newStrLitNode(innerProc(4))) diff --git a/tests/macros/tmacrostmt.nim b/tests/macros/tmacrostmt.nim index 6f648958f..c1f26e626 100644 --- a/tests/macros/tmacrostmt.nim +++ b/tests/macros/tmacrostmt.nim @@ -1,5 +1,5 @@ import macros -macro case_token(n: untyped): untyped {.immediate.} = +macro case_token(n: varargs[untyped]): untyped = # creates a lexical analyzer from regular expressions # ... (implementation is an exercise for the reader :-) nil @@ -21,6 +21,102 @@ case_token: inc i macro foo: typed = var exp = newCall("whatwhat", newIntLitNode(1)) if compiles(getAst(exp)): return exp - else: echo "Does not compute!" + else: echo "Does not compute! (test OK)" foo() + +#------------------------------------ +# bug #8287 +type MyString = distinct string + +proc `$` (c: MyString): string {.borrow.} + +proc `!!` (c: cstring): int = + c.len + +proc f(name: MyString): int = + !! $ name + +macro repr_and_parse(fn: typed): typed = + let fn_impl = fn.getImpl + fn_impl.name = genSym(nskProc, $fn_impl.name) + echo fn_impl.repr + result = parseStmt(fn_impl.repr) + +macro repr_to_string(fn: typed): string = + let fn_impl = fn.getImpl + result = newStrLitNode(fn_impl.repr) + +repr_and_parse(f) + + +#------------------------------------ +# bugs #8343 and #8344 +proc one_if_proc(x, y : int): int = + if x < y: result = x + else: result = y + +proc test_block(x, y : int): int = + block label: + result = x + result = y + +#------------------------------------ +# bugs #8348 + +template `>`(x, y: untyped): untyped = + ## "is greater" operator. This is the same as ``y < x``. + y < x + +proc test_cond_stmtlist(x, y: int): int = + result = x + if x > y: + result = x + + +#------------------------------------ +# bug #8762 +proc t2(a, b: int): int = + `+`(a, b) + + +#------------------------------------ +# bug #8761 + +proc fn1(x, y: int):int = + 2 * (x + y) + +proc fn2(x, y: float): float = + (y + 2 * x) / (x - y) + +proc fn3(x, y: int): bool = + (((x and 3) div 4) or (x mod (y xor -1))) == 0 or y notin [1,2] + +static: + let fn1s = "proc fn1(x, y: int): int =\n result = 2 * (x + y)\n" + let fn2s = "proc fn2(x, y: float): float =\n result = (y + 2.0 * x) / (x - y)\n" + let fn3s = "proc fn3(x, y: int): bool =\n result = ((x and 3) div 4 or x mod (y xor -1)) == 0 or not contains([1, 2], y)\n" + doAssert fn1.repr_to_string == fn1s + doAssert fn2.repr_to_string == fn2s + doAssert fn3.repr_to_string == fn3s + +#------------------------------------ +# bug #8763 + +type + A {.pure.} = enum + X, Y + B {.pure.} = enum + X, Y + +proc test_pure_enums(a: B) = + case a + of B.X: echo B.X + of B.Y: echo B.Y + +repr_and_parse(one_if_proc) +repr_and_parse(test_block) +repr_and_parse(test_cond_stmtlist) +repr_and_parse(t2) +repr_and_parse(test_pure_enums) + diff --git a/tests/macros/tmacrotypes.nim b/tests/macros/tmacrotypes.nim index e8a68c34d..734503e6b 100644 --- a/tests/macros/tmacrotypes.nim +++ b/tests/macros/tmacrotypes.nim @@ -1,16 +1,25 @@ discard """ - nimout: '''void -int''' + nimout: '''intProc; ntyProc; proc[int, int, float]; proc (a: int; b: float): int +void; ntyVoid; void; void +int; ntyInt; int; int +proc (); ntyProc; proc[void]; proc () +voidProc; ntyProc; proc[void]; proc ()''' """ import macros macro checkType(ex: typed; expected: string): untyped = - var t = ex.getType() - echo t + echo ex.getTypeInst.repr, "; ", ex.typeKind, "; ", ex.getType.repr, "; ", ex.getTypeImpl.repr + +macro checkProcType(fn: typed): untyped = + let fn_sym = if fn.kind == nnkProcDef: fn[0] else: fn + echo fn_sym, "; ", fn_sym.typeKind, "; ", fn_sym.getType.repr, "; ", fn_sym.getTypeImpl.repr + proc voidProc = echo "hello" -proc intProc(a: int, b: float): int = 10 +proc intProc(a: int, b: float): int {.checkProcType.} = 10 checkType(voidProc(), "void") checkType(intProc(10, 20.0), "int") +checkType(voidProc, "procTy") +checkProcType(voidProc) diff --git a/tests/macros/tmsginfo.nim b/tests/macros/tmsginfo.nim new file mode 100644 index 000000000..bf6c9d537 --- /dev/null +++ b/tests/macros/tmsginfo.nim @@ -0,0 +1,24 @@ +discard """ + nimout: '''tmsginfo.nim(21, 1) Warning: foo1 [User] +tmsginfo.nim(22, 11) template/generic instantiation from here +tmsginfo.nim(15, 10) Warning: foo2 [User] +tmsginfo.nim(23, 1) Hint: foo3 [User] +tmsginfo.nim(19, 7) Hint: foo4 [User] +''' +""" + +import macros + +macro foo1(y: untyped): untyped = + warning("foo1", y) +macro foo2(y: untyped): untyped = + warning("foo2") +macro foo3(y: untyped): untyped = + hint("foo3", y) +macro foo4(y: untyped): untyped = + hint("foo4") + +proc x1() {.foo1.} = discard +proc x2() {.foo2.} = discard +proc x3() {.foo3.} = discard +proc x4() {.foo4.} = discard diff --git a/tests/macros/tprintf.nim b/tests/macros/tprintf.nim deleted file mode 100644 index 94cbfbc2b..000000000 --- a/tests/macros/tprintf.nim +++ /dev/null @@ -1,16 +0,0 @@ -discard """ - file: "tprintf.nim" - output: "Andreas Rumpf" -""" -# Test a printf proc - -proc printf(file: TFile, args: openarray[string]) = - var i = 0 - while i < args.len: - write(file, args[i]) - inc(i) - -printf(stdout, ["Andreas ", "Rumpf\n"]) -#OUT Andreas Rumpf - - diff --git a/tests/macros/tstructuredlogging.nim b/tests/macros/tstructuredlogging.nim new file mode 100644 index 000000000..05bb52a40 --- /dev/null +++ b/tests/macros/tstructuredlogging.nim @@ -0,0 +1,154 @@ +discard """ +output: ''' +main started: a=10, b=inner-b, c=10, d=some-d, x=16, z=20 +exiting: a=12, b=overriden-b, c=100, msg=bye bye, x=16 +''' +""" + +import macros, tables + +template scopeHolder = + 0 # scope revision number + +type + BindingsSet = Table[string, NimNode] + +proc actualBody(n: NimNode): NimNode = + # skip over the double StmtList node introduced in `mergeScopes` + result = n.body + if result.kind == nnkStmtList and result[0].kind == nnkStmtList: + result = result[0] + +iterator bindings(n: NimNode, skip = 0): (string, NimNode) = + for i in skip ..< n.len: + let child = n[i] + if child.kind in {nnkAsgn, nnkExprEqExpr}: + let name = $child[0] + let value = child[1] + yield (name, value) + +proc scopeRevision(scopeHolder: NimNode): int = + # get the revision number from a scopeHolder sym + assert scopeHolder.kind == nnkSym + var revisionNode = scopeHolder.getImpl.actualBody[0] + result = int(revisionNode.intVal) + +proc lastScopeHolder(scopeHolders: NimNode): NimNode = + # get the most recent scopeHolder from a symChoice node + if scopeHolders.kind in {nnkClosedSymChoice, nnkOpenSymChoice}: + var bestScopeRev = 0 + assert scopeHolders.len > 0 + for scope in scopeHolders: + let rev = scope.scopeRevision + if result == nil or rev > bestScopeRev: + result = scope + bestScopeRev = rev + else: + result = scopeHolders + + assert result.kind == nnkSym + +macro mergeScopes(scopeHolders: typed, newBindings: untyped): untyped = + var + bestScope = scopeHolders.lastScopeHolder + bestScopeRev = bestScope.scopeRevision + + var finalBindings = initTable[string, NimNode]() + for k, v in bindings(bestScope.getImpl.actualBody, skip = 1): + finalBindings[k] = v + + for k, v in bindings(newBindings): + finalBindings[k] = v + + var newScopeDefinition = newStmtList(newLit(bestScopeRev + 1)) + + for k, v in finalBindings: + newScopeDefinition.add newAssignment(newIdentNode(k), v) + + result = quote: + template scopeHolder = `newScopeDefinition` + +template scope(newBindings: untyped) {.dirty.} = + mergeScopes(bindSym"scopeHolder", newBindings) + +type + TextLogRecord = object + line: string + + StdoutLogRecord = object + +template setProperty(r: var TextLogRecord, key: string, val: string, isFirst: bool) = + if not first: r.line.add ", " + r.line.add key + r.line.add "=" + r.line.add val + +template setEventName(r: var StdoutLogRecord, name: string) = + stdout.write(name & ": ") + +template setProperty(r: var StdoutLogRecord, key: string, val: auto, isFirst: bool) = + when not isFirst: stdout.write ", " + stdout.write key + stdout.write "=" + stdout.write $val + +template flushRecord(r: var StdoutLogRecord) = + stdout.write "\n" + stdout.flushFile + +macro logImpl(scopeHolders: typed, + logStmtProps: varargs[untyped]): untyped = + let lexicalScope = scopeHolders.lastScopeHolder.getImpl.actualBody + var finalBindings = initOrderedTable[string, NimNode]() + + for k, v in bindings(lexicalScope, skip = 1): + finalBindings[k] = v + + for k, v in bindings(logStmtProps, skip = 1): + finalBindings[k] = v + + finalBindings.sort(system.cmp) + + let eventName = logStmtProps[0] + assert eventName.kind in {nnkStrLit} + let record = genSym(nskVar, "record") + + result = quote: + var `record`: StdoutLogRecord + setEventName(`record`, `eventName`) + + var isFirst = true + for k, v in finalBindings: + result.add newCall(newIdentNode"setProperty", + record, newLit(k), v, newLit(isFirst)) + isFirst = false + + result.add newCall(newIdentNode"flushRecord", record) + +template log(props: varargs[untyped]) {.dirty.} = + logImpl(bindSym"scopeHolder", props) + +scope: + a = 12 + b = "original-b" + +scope: + x = 16 + b = "overriden-b" + +scope: + c = 100 + +proc main = + scope: + c = 10 + + scope: + z = 20 + + log("main started", a = 10, b = "inner-b", d = "some-d") + +main() + +log("exiting", msg = "bye bye") + diff --git a/tests/macros/ttemplatesymbols.nim b/tests/macros/ttemplatesymbols.nim new file mode 100644 index 000000000..8d9c9ec02 --- /dev/null +++ b/tests/macros/ttemplatesymbols.nim @@ -0,0 +1,173 @@ +import + macros, algorithm, strutils + +proc normalProc(x: int) = + echo x + +template templateWithtouParams = + echo 10 + +proc overloadedProc(x: int) = + echo x + +proc overloadedProc(x: string) = + echo x + +proc overloadedProc[T](x: T) = + echo x + +template normalTemplate(x: int) = + echo x + +template overloadedTemplate(x: int) = + echo x + +template overloadedTemplate(x: string) = + echo x + +macro normalMacro(x: int): untyped = + discard + +macro macroWithoutParams: untyped = + discard + +macro inspectSymbol(sym: typed, expected: static[string]): untyped = + if sym.kind == nnkSym: + echo "Symbol node:" + let res = sym.getImpl.repr & "\n" + echo res + # echo "|", res, "|" + # echo "|", expected, "|" + if expected.len > 0: assert res == expected + elif sym.kind in {nnkClosedSymChoice, nnkOpenSymChoice}: + echo "Begin sym choice:" + var results = newSeq[string](0) + for innerSym in sym: + results.add innerSym.getImpl.repr + sort(results, cmp[string]) + let res = results.join("\n") & "\n" + echo res + if expected.len > 0: assert res == expected + echo "End symchoice." + else: + echo "Non-symbol node: ", sym.kind + if expected.len > 0: assert $sym.kind == expected + +macro inspectUntyped(sym: untyped, expected: static[string]): untyped = + let res = sym.repr + echo "Untyped node: ", res + assert res == expected + +inspectSymbol templateWithtouParams, "nnkCommand" + # this template is expanded, because bindSym was not used + # the end result is the template body (nnkCommand) + +inspectSymbol bindSym("templateWithtouParams"), """ +template templateWithtouParams() = + echo 10 + +""" + +inspectSymbol macroWithoutParams, "nnkEmpty" + # Just like the template above, the macro was expanded + +inspectSymbol bindSym("macroWithoutParams"), """ +macro macroWithoutParams(): untyped = + discard + +""" + +inspectSymbol normalMacro, """ +macro normalMacro(x: int): untyped = + discard + +""" + # Since the normalMacro has params, it's automatically + # treated as a symbol here (no need for `bindSym`) + +inspectSymbol bindSym("normalMacro"), """ +macro normalMacro(x: int): untyped = + discard + +""" + +inspectSymbol normalTemplate, """ +template normalTemplate(x: int) = + echo x + +""" + +inspectSymbol bindSym("normalTemplate"), """ +template normalTemplate(x: int) = + echo x + +""" + +inspectSymbol overloadedTemplate, """ +template overloadedTemplate(x: int) = + echo x + +template overloadedTemplate(x: string) = + echo x + +""" + +inspectSymbol bindSym("overloadedTemplate"), """ +template overloadedTemplate(x: int) = + echo x + +template overloadedTemplate(x: string) = + echo x + +""" + +inspectUntyped bindSym("overloadedTemplate"), """bindSym("overloadedTemplate")""" + # binSym is active only in the presense of `typed` params. + # `untyped` params still get the raw AST + +inspectSymbol normalProc, """ +proc normalProc(x: int) = + echo [x] + +""" + +inspectSymbol bindSym("normalProc"), """ +proc normalProc(x: int) = + echo [x] + +""" + +inspectSymbol overloadedProc, """ +proc overloadedProc(x: int) = + echo [x] + +proc overloadedProc(x: string) = + echo [x] + +proc overloadedProc[T](x: T) = + echo x + +""" + # XXX: There seems to be a repr rendering problem above. + # Notice that `echo [x]` + +inspectSymbol overloadedProc[float], """ +proc overloadedProc(x: T) = + echo [x] + +""" + # As expected, when we select a specific generic, the + # AST is no longer a symChoice + +inspectSymbol bindSym("overloadedProc"), """ +proc overloadedProc(x: int) = + echo [x] + +proc overloadedProc(x: string) = + echo [x] + +proc overloadedProc[T](x: T) = + echo x + +""" + diff --git a/tests/macros/typesafeprintf.nim b/tests/macros/typesafeprintf.nim index 2f4622f3e..daf213bd3 100644 --- a/tests/macros/typesafeprintf.nim +++ b/tests/macros/typesafeprintf.nim @@ -9,7 +9,7 @@ proc printfImpl(formatstr: cstring) {.importc: "printf", varargs.} iterator tokenize(format: string): char = var i = 0 - while true: + while i < format.len: case format[i] of '%': case format[i+1] @@ -42,7 +42,8 @@ macro printf(formatString: string{lit}, args: varargs[typed]): untyped = $expectedType & ", actual type: " & $actualType # keep the original callsite, but use cprintf instead - result = callsite() - result[0] = bindSym"printfImpl" + result = newCall(bindSym"printfImpl") + result.add formatString + for a in args: result.add a printf("test %d\n", 10) diff --git a/tests/magics/t8693.nim b/tests/magics/t8693.nim new file mode 100644 index 000000000..554244de4 --- /dev/null +++ b/tests/magics/t8693.nim @@ -0,0 +1,29 @@ +discard """ + output: '''true +false +true +false +false +true +true +false +true +true +''' +""" + +type Foo = int | float + +proc bar(t1, t2: typedesc): bool = + echo (t1 is t2) + (t2 is t1) + +proc bar[T](x: T, t2: typedesc): bool = + echo (T is t2) + (t2 is T) + +echo bar(int, Foo) +echo bar(4, Foo) +echo bar(any, int) +echo bar(int, any) +echo bar(Foo, Foo) diff --git a/tests/manyloc/argument_parser/argument_parser.nim b/tests/manyloc/argument_parser/argument_parser.nim index 14352066d..d42842f93 100644 --- a/tests/manyloc/argument_parser/argument_parser.nim +++ b/tests/manyloc/argument_parser/argument_parser.nim @@ -74,14 +74,14 @@ type ## nothing prevents you from accessing directly the type of field you want ## if you expect only one kind. case kind*: Tparam_kind - of PK_EMPTY: nil + of PK_EMPTY: discard of PK_INT: int_val*: int of PK_BIGGEST_INT: big_int_val*: BiggestInt of PK_FLOAT: float_val*: float of PK_BIGGEST_FLOAT: big_float_val*: BiggestFloat of PK_STRING: str_val*: string of PK_BOOL: bool_val*: bool - of PK_HELP: nil + of PK_HELP: discard Tcommandline_results* = object of RootObj ## \ ## Contains the results of the parsing. @@ -209,7 +209,7 @@ proc `$`*(data: Tcommandline_results): string = # - Parse code -template raise_or_quit(exception, message: expr): stmt {.immediate.} = +template raise_or_quit(exception, message: untyped) = ## Avoids repeating if check based on the default quit_on_failure variable. ## ## As a special case, if message has a zero length the call to quit won't @@ -230,15 +230,15 @@ template run_custom_proc(parsed_parameter: Tparsed_parameter, ## ## Pass in the string of the parameter triggering the call. If the if not custom_validator.isNil: + try: + let message = custom_validator(parameter, parsed_parameter) + if message.len > 0: + raise_or_quit(ValueError, ("Failed to validate value for " & + "parameter $1:\n$2" % [escape(parameter), message])) except: raise_or_quit(ValueError, ("Couldn't run custom proc for " & "parameter $1:\n$2" % [escape(parameter), getCurrentExceptionMsg()])) - let message = custom_validator(parameter, parsed_parameter) - if not message.isNil and message.len > 0: - raise_or_quit(ValueError, ("Failed to validate value for " & - "parameter $1:\n$2" % [escape(parameter), message])) - proc parse_parameter(quit_on_failure: bool, param, value: string, param_kind: Tparam_kind): Tparsed_parameter = @@ -319,7 +319,7 @@ proc echo_help*(expected: seq[Tparameter_specification] = @[], proc parse*(expected: seq[Tparameter_specification] = @[], - type_of_positional_parameters = PK_STRING, args: seq[TaintedString] = nil, + type_of_positional_parameters = PK_STRING, args: seq[TaintedString] = @[], bad_prefixes = @["-", "--"], end_of_options = "--", quit_on_failure = true): Tcommandline_results = ## Parses parameters and returns results. @@ -339,7 +339,7 @@ proc parse*(expected: seq[Tparameter_specification] = @[], ## ## The args sequence should be the list of parameters passed to your program ## without the program binary (usually OSes provide the path to the binary as - ## the zeroth parameter). If args is nil, the list will be retrieved from the + ## the zeroth parameter). If args is empty, the list will be retrieved from the ## OS. ## ## If there is any kind of error and quit_on_failure is true, the quit proc @@ -358,7 +358,7 @@ proc parse*(expected: seq[Tparameter_specification] = @[], # Prepare the input parameter list, maybe get it from the OS if not available. var args = args - if args == nil: + if args.len == 0: let total_params = paramCount() #echo "Got no explicit args, retrieving from OS. Count: ", total_params newSeq(args, total_params) diff --git a/tests/manyloc/keineschweine/dependencies/chipmunk/chipmunk.nim b/tests/manyloc/keineschweine/dependencies/chipmunk/chipmunk.nim index 56d3edec4..ac425c7a0 100644 --- a/tests/manyloc/keineschweine/dependencies/chipmunk/chipmunk.nim +++ b/tests/manyloc/keineschweine/dependencies/chipmunk/chipmunk.nim @@ -23,7 +23,6 @@ const Lib = "libchipmunk.so.6.1.1" when defined(MoreNim): {.hint: "MoreNim defined; some Chipmunk functions replaced in Nim".} -{.deadCodeElim: on.} from math import sqrt, sin, cos, arctan2 when defined(CpUseFloat): {.hint: "CpUseFloat defined; using float32 as float".} @@ -389,13 +388,13 @@ type cdecl.} ##cp property emulators -template defGetter(otype: typedesc, memberType: typedesc, memberName: expr, procName: expr): stmt {.immediate.} = +template defGetter(otype: typedesc, memberType: typedesc, memberName, procName: untyped) = proc `get procName`*(obj: otype): memberType {.cdecl.} = return obj.memberName -template defSetter(otype: typedesc, memberType: typedesc, memberName: expr, procName: expr): stmt {.immediate.} = +template defSetter(otype: typedesc, memberType: typedesc, memberName, procName: untyped) = proc `set procName`*(obj: otype, value: memberType) {.cdecl.} = obj.memberName = value -template defProp(otype: typedesc, memberType: typedesc, memberName: expr, procName: expr): stmt {.immediate.} = +template defProp(otype: typedesc, memberType: typedesc, memberName, procName: untyped) = defGetter(otype, memberType, memberName, procName) defSetter(otype, memberType, memberName, procName) @@ -909,7 +908,7 @@ proc getShapes*(arb: PArbiter, a, b: var PShape) {.inline.} = #/ A macro shortcut for defining and retrieving the shapes from an arbiter. #define CP_ARBITER_GET_SHAPES(arb, a, b) cpShape *a, *b; cpArbiterGetShapes(arb, &a, &b); -template getShapes*(arb: PArbiter, name1, name2: expr): stmt {.immediate.} = +template getShapes*(arb: PArbiter, name1, name2: untyped) = var name1, name2: PShape getShapes(arb, name1, name2) @@ -924,7 +923,7 @@ template getShapes*(arb: PArbiter, name1, name2: expr): stmt {.immediate.} = #/ A macro shortcut for defining and retrieving the bodies from an arbiter. #define CP_ARBITER_GET_BODIES(arb, a, b) cpBody *a, *b; cpArbiterGetBodies(arb, &a, &b); -template getBodies*(arb: PArbiter, name1, name2: expr): stmt {.immediate.} = +template getBodies*(arb: PArbiter, name1, name2: untyped) = var name1, name2: PBOdy getBodies(arb, name1, name2) @@ -948,11 +947,11 @@ proc getDepth*(arb: PArbiter; i: cint): CpFloat {. cdecl, importc: "cpArbiterGetDepth", dynlib: Lib.} ##Shapes -template defShapeSetter(memberType: typedesc, memberName: expr, procName: expr, activates: bool): stmt {.immediate.} = +template defShapeSetter(memberType: typedesc, memberName: untyped, procName: untyped, activates: bool) = proc `set procName`*(obj: PShape, value: memberType) {.cdecl.} = if activates and obj.body != nil: obj.body.activate() obj.memberName = value -template defShapeProp(memberType: typedesc, memberName: expr, procName: expr, activates: bool): stmt {.immediate.} = +template defShapeProp(memberType: typedesc, memberName: untyped, procName: untyped, activates: bool) = defGetter(PShape, memberType, memberName, procName) defShapeSetter(memberType, memberName, procName, activates) @@ -1273,11 +1272,11 @@ proc activateBodies(constraint: PConstraint) {.inline.} = # cpConstraintActivateBodies(constraint); \ # constraint->member = value; \ # } -template defConstraintSetter(memberType: typedesc, member: expr, name: expr): stmt {.immediate.} = +template defConstraintSetter(memberType: typedesc, member, name: untyped) = proc `set name`*(constraint: PConstraint, value: memberType) {.cdecl.} = activateBodies(constraint) constraint.member = value -template defConstraintProp(memberType: typedesc, member: expr, name: expr): stmt {.immediate.} = +template defConstraintProp(memberType: typedesc, member, name: untyped) = defGetter(PConstraint, memberType, member, name) defConstraintSetter(memberType, member, name) # CP_DefineConstraintStructGetter(cpSpace*, CP_PRIVATE(space), Space) @@ -1307,18 +1306,18 @@ proc getImpulse*(constraint: PConstraint): CpFloat {.inline.} = # cpConstraintActivateBodies(constraint); \ # ((struct *)constraint)->member = value; \ # } -template constraintCheckCast(constraint: PConstraint, ctype: expr): stmt {.immediate.} = +template constraintCheckCast(constraint: PConstraint, ctype: untyped) = assert(constraint.klass == `ctype getClass`(), "Constraint is the wrong class") -template defCGetter(ctype: expr, memberType: typedesc, member: expr, name: expr): stmt {.immediate.} = +template defCGetter(ctype: untyped, memberType: typedesc, member, name: untyped) = proc `get ctype name`*(constraint: PConstraint): memberType {.cdecl.} = constraintCheckCast(constraint, ctype) result = cast[`P ctype`](constraint).member -template defCSetter(ctype: expr, memberType: typedesc, member: expr, name: expr): stmt {.immediate.} = +template defCSetter(ctype: untyped, memberType: typedesc, member, name: untyped) = proc `set ctype name`*(constraint: PConstraint, value: memberType) {.cdecl.} = constraintCheckCast(constraint, ctype) activateBodies(constraint) cast[`P ctype`](constraint).member = value -template defCProp(ctype: expr, memberType: typedesc, member: expr, name: expr): stmt {.immediate.} = +template defCProp(ctype: untyped, memberType: typedesc, member, name: untyped) = defCGetter(ctype, memberType, member, name) defCSetter(ctype, memberType, member, name) diff --git a/tests/manyloc/keineschweine/dependencies/enet/enet.nim b/tests/manyloc/keineschweine/dependencies/enet/enet.nim index 3c4ce2017..07079f2ea 100644 --- a/tests/manyloc/keineschweine/dependencies/enet/enet.nim +++ b/tests/manyloc/keineschweine/dependencies/enet/enet.nim @@ -20,12 +20,11 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. const Lib = "libenet.so.1(|.0.3)" -{.deadCodeElim: on.} const ENET_VERSION_MAJOR* = 1 ENET_VERSION_MINOR* = 3 ENET_VERSION_PATCH* = 3 -template ENET_VERSION_CREATE(major, minor, patch: expr): expr = +template ENET_VERSION_CREATE(major, minor, patch: untyped): untyped = (((major) shl 16) or ((minor) shl 8) or (patch)) const @@ -278,22 +277,22 @@ when defined(Linux) or true: dataLength*: csize TENetSocketSet* = Tfd_set ## see if these are different on win32, if not then get rid of these - template ENET_HOST_TO_NET_16*(value: expr): expr = + template ENET_HOST_TO_NET_16*(value: untyped): untyped = (htons(value)) - template ENET_HOST_TO_NET_32*(value: expr): expr = + template ENET_HOST_TO_NET_32*(value: untyped): untyped = (htonl(value)) - template ENET_NET_TO_HOST_16*(value: expr): expr = + template ENET_NET_TO_HOST_16*(value: untyped): untyped = (ntohs(value)) - template ENET_NET_TO_HOST_32*(value: expr): expr = + template ENET_NET_TO_HOST_32*(value: untyped): untyped = (ntohl(value)) - template ENET_SOCKETSET_EMPTY*(sockset: expr): expr = + template ENET_SOCKETSET_EMPTY*(sockset: untyped): untyped = FD_ZERO(addr((sockset))) - template ENET_SOCKETSET_ADD*(sockset, socket: expr): expr = + template ENET_SOCKETSET_ADD*(sockset, socket: untyped): untyped = FD_SET(socket, addr((sockset))) - template ENET_SOCKETSET_REMOVE*(sockset, socket: expr): expr = + template ENET_SOCKETSET_REMOVE*(sockset, socket: untyped): untyped = FD_CLEAR(socket, addr((sockset))) - template ENET_SOCKETSET_CHECK*(sockset, socket: expr): expr = + template ENET_SOCKETSET_CHECK*(sockset, socket: untyped): untyped = FD_ISSET(socket, addr((sockset))) when defined(Windows): @@ -607,7 +606,7 @@ proc protocolCommandSize*(commandNumber: cuchar): csize{. {.pop.} -from hashes import `!$`, `!&`, THash, hash -proc hash*(x: TAddress): THash {.nimcall, noSideEffect.} = +from hashes import `!$`, `!&`, Hash, hash +proc hash*(x: TAddress): Hash {.nimcall, noSideEffect.} = result = !$(hash(x.host.int32) !& hash(x.port.int16)) diff --git a/tests/manyloc/keineschweine/dependencies/genpacket/genpacket_enet.nim b/tests/manyloc/keineschweine/dependencies/genpacket/genpacket_enet.nim index 142b190ab..3fb4dc7d9 100644 --- a/tests/manyloc/keineschweine/dependencies/genpacket/genpacket_enet.nim +++ b/tests/manyloc/keineschweine/dependencies/genpacket/genpacket_enet.nim @@ -1,15 +1,15 @@ import macros, macro_dsl, estreams from strutils import format -template newLenName(): stmt {.immediate.} = +template newLenName() = let lenName {.inject.} = ^("len"& $lenNames) inc(lenNames) -template defPacketImports*(): stmt {.immediate, dirty.} = +template defPacketImports*() {.dirty.} = import macros, macro_dsl, estreams from strutils import format -macro defPacket*(typeNameN: expr, typeFields: expr): stmt {.immediate.} = +macro defPacket*(typeNameN: untyped, typeFields: untyped): untyped = result = newNimNode(nnkStmtList) let typeName = quoted2ident(typeNameN) @@ -60,7 +60,7 @@ macro defPacket*(typeNameN: expr, typeFields: expr): stmt {.immediate.} = let name = typeFields[i][0] dotName = packetID.dot(name) - resName = newIdentNode(!"result").dot(name) + resName = newIdentNode("result").dot(name) case typeFields[i][1].kind of nnkBracketExpr: #ex: paddedstring[32, '\0'], array[range, type] case $typeFields[i][1][0].ident @@ -141,7 +141,7 @@ macro defPacket*(typeNameN: expr, typeFields: expr): stmt {.immediate.} = const emptyFields = {nnkEmpty, nnkNilLit} var objFields = newNimNode(nnkRecList) - for i in 0.. < len(typeFields): + for i in 0 ..< len(typeFields): let fname = typeFields[i][0] constructorParams.add(newNimNode(nnkIdentDefs).und( fname, @@ -200,7 +200,7 @@ proc iddefs*(a: string; b: NimNode): NimNode {.compileTime.} = proc varTy*(a: NimNode): NimNode {.compileTime.} = result = newNimNode(nnkVarTy).und(a) -macro forwardPacket*(typeName: expr, underlyingType: expr): stmt {.immediate.} = +macro forwardPacket*(typeName: untyped, underlyingType: untyped): untyped = var packetID = ^"p" streamID = ^"s" @@ -234,7 +234,7 @@ macro forwardPacket*(typeName: expr, underlyingType: expr): stmt {.immediate.} = echo "unknown type:", repr(underlyingtype) echo(repr(result)) -template forwardPacketT*(typeName: expr; underlyingType: expr): stmt {.dirty, immediate.} = +template forwardPacketT*(typeName: untyped; underlyingType: untyped) {.dirty.} = proc `read typeName`*(buffer: PBuffer): typeName = #discard readData(s, addr result, sizeof(result)) var res: underlyingType diff --git a/tests/manyloc/keineschweine/dependencies/genpacket/macro_dsl.nim b/tests/manyloc/keineschweine/dependencies/genpacket/macro_dsl.nim index d3a0c701d..33d2a7177 100644 --- a/tests/manyloc/keineschweine/dependencies/genpacket/macro_dsl.nim +++ b/tests/manyloc/keineschweine/dependencies/genpacket/macro_dsl.nim @@ -1,5 +1,5 @@ import macros -{.deadCodeElim: on.} + #Inline macro.add() to allow for easier nesting proc und*(a: NimNode; b: NimNode): NimNode {.compileTime.} = a.add(b) @@ -10,7 +10,7 @@ proc und*(a: NimNode; b: varargs[NimNode]): NimNode {.compileTime.} = proc `^`*(a: string): NimNode {.compileTime.} = ## new ident node - result = newIdentNode(!a) + result = newIdentNode(a) proc `[]`*(a, b: NimNode): NimNode {.compileTime.} = ## new bracket expression: node[node] not to be confused with node[indx] result = newNimNode(nnkBracketExpr).und(a, b) @@ -34,7 +34,7 @@ proc emptyNode*(): NimNode {.compileTime.} = proc dot*(left, right: NimNode): NimNode {.compileTime.} = result = newNimNode(nnkDotExpr).und(left, right) proc prefix*(a: string, b: NimNode): NimNode {.compileTime.} = - result = newNimNode(nnkPrefix).und(newIdentNode(!a), b) + result = newNimNode(nnkPrefix).und(newIdentNode(a), b) proc quoted2ident*(a: NimNode): NimNode {.compileTime.} = if a.kind != nnkAccQuoted: @@ -45,13 +45,13 @@ proc quoted2ident*(a: NimNode): NimNode {.compileTime.} = result = ^pname -macro `?`(a: expr): expr = +macro `?`(a: untyped): untyped = ## Character literal ?A #=> 'A' result = ($a[1].ident)[0].lit ## echo(?F,?a,?t,?t,?y) when isMainModule: - macro foo(x: stmt): stmt = + macro foo(x: untyped) = result = newNimNode(nnkStmtList) result.add(newNimNode(nnkCall).und(!!"echo", "Hello thar".lit)) result.add(newCall("echo", lit("3 * 45 = "), (3.lit.infix("*", 45.lit)))) diff --git a/tests/manyloc/keineschweine/dependencies/nake/nakefile.nim b/tests/manyloc/keineschweine/dependencies/nake/nakefile.nim index bdf2139c9..5490211de 100644 --- a/tests/manyloc/keineschweine/dependencies/nake/nakefile.nim +++ b/tests/manyloc/keineschweine/dependencies/nake/nakefile.nim @@ -7,7 +7,7 @@ task "install", "compile and install nake binary": for index, dir in pairs(path): echo " ", index, ". ", dir echo "Where to install nake binary? (quit with ^C or quit or exit)" - let ans = stdin.readLine().toLower + let ans = stdin.readLine().toLowerAscii var index = 0 case ans of "q", "quit", "x", "exit": diff --git a/tests/manyloc/keineschweine/dependencies/sfml/sfml.nim b/tests/manyloc/keineschweine/dependencies/sfml/sfml.nim index 1524f0eb4..0060bf12b 100644 --- a/tests/manyloc/keineschweine/dependencies/sfml/sfml.nim +++ b/tests/manyloc/keineschweine/dependencies/sfml/sfml.nim @@ -12,7 +12,7 @@ else: LibS = "libcsfml-system.so.2.0" LibW = "libcsfml-window.so.2.0" #{.error: "Platform unsupported".} -{.deadCodeElim: on.} + {.pragma: pf, pure, final.} type PClock* = ptr TClock diff --git a/tests/manyloc/keineschweine/dependencies/sfml/sfml_colors.nim b/tests/manyloc/keineschweine/dependencies/sfml/sfml_colors.nim index 95a760e1f..b4eb1b8f0 100644 --- a/tests/manyloc/keineschweine/dependencies/sfml/sfml_colors.nim +++ b/tests/manyloc/keineschweine/dependencies/sfml/sfml_colors.nim @@ -1,5 +1,5 @@ import sfml -{.deadCodeElim: on.} + let Black*: TColor = color(0, 0, 0) White*: TColor = color(255, 255, 255) diff --git a/tests/manyloc/keineschweine/dependencies/sfml/sfml_vector.nim b/tests/manyloc/keineschweine/dependencies/sfml/sfml_vector.nim index 474d249aa..94c5308a9 100644 --- a/tests/manyloc/keineschweine/dependencies/sfml/sfml_vector.nim +++ b/tests/manyloc/keineschweine/dependencies/sfml/sfml_vector.nim @@ -1,2 +1 @@ import sfml, math, strutils -{.deadCodeElim: on.} diff --git a/tests/manyloc/keineschweine/keineschweine.nim b/tests/manyloc/keineschweine/keineschweine.nim index c0f1bc031..59347ee46 100644 --- a/tests/manyloc/keineschweine/keineschweine.nim +++ b/tests/manyloc/keineschweine/keineschweine.nim @@ -5,7 +5,7 @@ import sg_gui, sg_assets, sound_buffer, enet_client when defined(profiler): import nimprof -{.deadCodeElim: on.} + type PPlayer* = ref TPlayer TPlayer* = object diff --git a/tests/manyloc/keineschweine/keineschweine.nim.cfg b/tests/manyloc/keineschweine/keineschweine.nim.cfg index ca6c75f6e..f335a0e0e 100644 --- a/tests/manyloc/keineschweine/keineschweine.nim.cfg +++ b/tests/manyloc/keineschweine/keineschweine.nim.cfg @@ -7,3 +7,4 @@ path = "dependencies/genpacket" path = "enet_server" debugger = off warning[SmallLshouldNotBeUsed] = off +nilseqs = on diff --git a/tests/manyloc/keineschweine/lib/estreams.nim b/tests/manyloc/keineschweine/lib/estreams.nim index bdf9b2bf0..00a55c626 100644 --- a/tests/manyloc/keineschweine/lib/estreams.nim +++ b/tests/manyloc/keineschweine/lib/estreams.nim @@ -78,7 +78,7 @@ proc write*(buffer: PBuffer; val: var string) = setLen buffer.data, buffer.pos + length.int copyMem(addr buffer.data[buffer.pos], addr val[0], length.int) inc buffer.pos, length.int -proc write*[T: TNumber|bool|char|byte](buffer: PBuffer; val: T) = +proc write*[T: SomeNumber|bool|char|byte](buffer: PBuffer; val: T) = var v: T shallowCopy v, val writeBE buffer, v diff --git a/tests/manyloc/keineschweine/lib/input_helpers.nim b/tests/manyloc/keineschweine/lib/input_helpers.nim index 1953cb58c..ff1286c8d 100644 --- a/tests/manyloc/keineschweine/lib/input_helpers.nim +++ b/tests/manyloc/keineschweine/lib/input_helpers.nim @@ -5,8 +5,8 @@ type TInputFinishedProc* = proc() TKeyCallback = proc() PKeyClient* = ref object - onKeyDown: TTable[int32, TKeyCallback] - onKeyUp: TTable[int32, TKeyCallback] + onKeyDown: Table[int32, TKeyCallback] + onKeyUp: Table[int32, TKeyCallback] name: string PTextInput* = ref object text*: string @@ -134,5 +134,5 @@ iterator pollEvents*(window: PRenderWindow): PEvent = of EvtMouseButtonReleased: addButtonEvent(event.mouseButton.button, up) of EvtTextEntered: recordText(activeInput, event.text) of EvtMouseMoved: setMousePos(event.mouseMove.x, event.mouseMove.y) - else: nil + else: discard yield(addr event) diff --git a/tests/manyloc/keineschweine/lib/map_filter.nim b/tests/manyloc/keineschweine/lib/map_filter.nim index 42ef74ceb..f3f1df190 100644 --- a/tests/manyloc/keineschweine/lib/map_filter.nim +++ b/tests/manyloc/keineschweine/lib/map_filter.nim @@ -1,4 +1,4 @@ -template filterIt2*(seq, pred: expr, body: stmt): stmt {.immediate, dirty.} = +template filterIt2*(seq, pred: untyped, body: untyped) = ## sequtils defines a filterIt() that returns a new seq, but this one is called ## with a statement body to iterate directly over it for it in items(seq): @@ -13,8 +13,8 @@ proc mapInPlace*[A](x: var seq[A], fun: proc(y: A): A {.closure.}) = for i in 0..x.len-1: x[i] = fun(x[i]) -template unless*(condition: expr; body: stmt): stmt {.dirty.} = - if not(condition): +template unless*(condition: untyped; body: untyped) {.dirty.} = + if not condition: body when isMainModule: diff --git a/tests/manyloc/keineschweine/lib/sg_assets.nim b/tests/manyloc/keineschweine/lib/sg_assets.nim index fbc3c9ab8..90f0a54e9 100644 --- a/tests/manyloc/keineschweine/lib/sg_assets.nim +++ b/tests/manyloc/keineschweine/lib/sg_assets.nim @@ -59,7 +59,7 @@ type of Projectile: bullet*: PBulletRecord else: - nil + discard PBulletRecord* = ref TBulletRecord TBulletRecord* = object id*: int16 @@ -115,10 +115,10 @@ var cfg: PZoneSettings SpriteSheets* = initTable[string, PSpriteSheet](64) SoundCache * = initTable[string, PSoundRecord](64) - nameToVehID*: TTable[string, int] - nameToItemID*: TTable[string, int] - nameToObjID*: TTable[string, int] - nameToBulletID*: TTable[string, int] + nameToVehID*: Table[string, int] + nameToItemID*: Table[string, int] + nameToObjID*: Table[string, int] + nameToBulletID*: Table[string, int] activeState = Lobby proc newSprite*(filename: string; errors: var seq[string]): PSpriteSheet @@ -126,7 +126,7 @@ proc load*(ss: PSpriteSheet): bool {.discardable.} proc newSound*(filename: string; errors: var seq[string]): PSoundRecord proc load*(s: PSoundRecord): bool {.discardable.} -proc validateSettings*(settings: PJsonNode; errors: var seq[string]): bool +proc validateSettings*(settings: JsonNode; errors: var seq[string]): bool proc loadSettings*(rawJson: string, errors: var seq[string]): bool proc loadSettingsFromFile*(filename: string, errors: var seq[string]): bool @@ -135,17 +135,17 @@ proc fetchItm*(itm: string): PItemRecord proc fetchObj*(name: string): PObjectRecord proc fetchBullet(name: string): PBulletRecord -proc importLevel(data: PJsonNode; errors: var seq[string]): PLevelSettings -proc importVeh(data: PJsonNode; errors: var seq[string]): PVehicleRecord -proc importObject(data: PJsonNode; errors: var seq[string]): PObjectRecord -proc importItem(data: PJsonNode; errors: var seq[string]): PItemRecord -proc importPhys(data: PJsonNode): TPhysicsRecord -proc importAnim(data: PJsonNode; errors: var seq[string]): PAnimationRecord -proc importHandling(data: PJsonNode): THandlingRecord -proc importBullet(data: PJsonNode; errors: var seq[string]): PBulletRecord -proc importSoul(data: PJsonNode): TSoulRecord -proc importExplosion(data: PJsonNode; errors: var seq[string]): TExplosionRecord -proc importSound*(data: PJsonNode; errors: var seq[string]; fieldName: string = nil): PSoundRecord +proc importLevel(data: JsonNode; errors: var seq[string]): PLevelSettings +proc importVeh(data: JsonNode; errors: var seq[string]): PVehicleRecord +proc importObject(data: JsonNode; errors: var seq[string]): PObjectRecord +proc importItem(data: JsonNode; errors: var seq[string]): PItemRecord +proc importPhys(data: JsonNode): TPhysicsRecord +proc importAnim(data: JsonNode; errors: var seq[string]): PAnimationRecord +proc importHandling(data: JsonNode): THandlingRecord +proc importBullet(data: JsonNode; errors: var seq[string]): PBulletRecord +proc importSoul(data: JsonNode): TSoulRecord +proc importExplosion(data: JsonNode; errors: var seq[string]): TExplosionRecord +proc importSound*(data: JsonNode; errors: var seq[string]; fieldName: string = ""): PSoundRecord ## this is the only pipe between lobby and main.nim proc getActiveState*(): TGameState = @@ -203,7 +203,7 @@ iterator playableVehicles*(): PVehicleRecord = if v.playable: yield v -template allAssets*(body: stmt) {.dirty.}= +template allAssets*(body: untyped) {.dirty.}= block: var assetType = FGraphics for file, asset in pairs(SpriteSheets): @@ -212,7 +212,7 @@ template allAssets*(body: stmt) {.dirty.}= for file, asset in pairs(SoundCache): body -template cacheImpl(procName, cacheName, resultType: expr; body: stmt) {.dirty, immediate.} = +template cacheImpl(procName, cacheName, resultType, body: untyped) {.dirty.} = proc procName*(filename: string; errors: var seq[string]): resulttype = if hasKey(cacheName, filename): return cacheName[filename] @@ -220,7 +220,7 @@ template cacheImpl(procName, cacheName, resultType: expr; body: stmt) {.dirty, i body cacheName[filename] = result -template checkFile(path: expr): stmt {.dirty, immediate.} = +template checkFile(path: untyped) {.dirty.} = if not existsFile(path): errors.add("File missing: "& path) @@ -243,7 +243,7 @@ proc expandPath*(assetType: TAssetType; fileName: string): string = case assetType of FGraphics: result.add "gfx/" of FSound: result.add "sfx/" - else: nil + else: discard result.add fileName proc expandPath*(fc: ScFileChallenge): string {.inline.} = result = expandPath(fc.assetType, fc.file) @@ -280,10 +280,10 @@ else: if not s.soundBuf.isNil: result = true -template addError(e: expr): stmt {.immediate.} = +template addError(e: untyped) = errors.add(e) result = false -proc validateSettings*(settings: PJsonNode, errors: var seq[string]): bool = +proc validateSettings*(settings: JsonNode, errors: var seq[string]): bool = result = true if settings.kind != JObject: addError("Settings root must be an object") @@ -328,10 +328,10 @@ proc loadSettingsFromFile*(filename: string, errors: var seq[string]): bool = result = loadSettings(readFile(filename), errors) proc loadSettings*(rawJson: string, errors: var seq[string]): bool = - var settings: PJsonNode + var settings: JsonNode try: settings = parseJson(rawJson) - except EJsonParsingError: + except JsonParsingError: errors.add("JSON parsing error: "& getCurrentExceptionMsg()) return except: @@ -407,21 +407,21 @@ proc fetchObj*(name: string): PObjectRecord = proc fetchBullet(name: string): PBulletRecord = return cfg.bullets[nameToBulletID[name]] -proc getField(node: PJsonNode, field: string, target: var float) = +proc getField(node: JsonNode, field: string, target: var float) = if not node.hasKey(field): return if node[field].kind == JFloat: target = node[field].fnum elif node[field].kind == JInt: target = node[field].num.float -proc getField(node: PJsonNode, field: string, target: var int) = +proc getField(node: JsonNode, field: string, target: var int) = if not node.hasKey(field): return if node[field].kind == JInt: target = node[field].num.int elif node[field].kind == JFloat: target = node[field].fnum.int -proc getField(node: PJsonNode; field: string; target: var bool) = +proc getField(node: JsonNode; field: string; target: var bool) = if not node.hasKey(field): return case node[field].kind @@ -431,19 +431,19 @@ proc getField(node: PJsonNode; field: string; target: var bool) = target = (node[field].num != 0) of JFloat: target = (node[field].fnum != 0.0) - else: nil + else: discard -template checkKey(node: expr; key: string): stmt = +template checkKey(node: untyped; key: string) = if not hasKey(node, key): return -proc importTrail(data: PJsonNode; errors: var seq[string]): TTrailRecord = +proc importTrail(data: JsonNode; errors: var seq[string]): TTrailRecord = checkKey(data, "trail") result.anim = importAnim(data["trail"], errors) result.timer = 1000.0 getField(data["trail"], "timer", result.timer) result.timer /= 1000.0 -proc importLevel(data: PJsonNode; errors: var seq[string]): PLevelSettings = +proc importLevel(data: JsonNode; errors: var seq[string]): PLevelSettings = new(result) result.size = vec2i(5000, 5000) result.starfield = @[] @@ -456,7 +456,7 @@ proc importLevel(data: PJsonNode; errors: var seq[string]): PLevelSettings = if level.hasKey("starfield"): for star in level["starfield"].items: result.starfield.add(newSprite(star.str, errors)) -proc importPhys(data: PJsonNode): TPhysicsRecord = +proc importPhys(data: JsonNode): TPhysicsRecord = result.radius = 20.0 result.mass = 10.0 @@ -466,7 +466,7 @@ proc importPhys(data: PJsonNode): TPhysicsRecord = phys.getField("mass", result.mass) when not defined(NoChipmunk): result.moment = momentForCircle(result.mass, 0.0, result.radius, VectorZero) * MomentMult -proc importHandling(data: PJsonNode): THandlingRecord = +proc importHandling(data: JsonNode): THandlingRecord = result.thrust = 45.0 result.topSpeed = 100.0 #unused result.reverse = 30.0 @@ -483,7 +483,7 @@ proc importHandling(data: PJsonNode): THandlingRecord = hand.getField("reverse", result.reverse) hand.getField("strafe", result.strafe) hand.getField("rotation", result.rotation) -proc importAnim(data: PJsonNode, errors: var seq[string]): PAnimationRecord = +proc importAnim(data: JsonNode, errors: var seq[string]): PAnimationRecord = new(result) result.angle = 0.0 result.delay = 1000.0 @@ -502,26 +502,26 @@ proc importAnim(data: PJsonNode, errors: var seq[string]): PAnimationRecord = result.angle = radians(result.angle) ## comes in as degrees result.delay /= 1000 ## delay comes in as milliseconds -proc importSoul(data: PJsonNode): TSoulRecord = +proc importSoul(data: JsonNode): TSoulRecord = result.energy = 10000 result.health = 1 checkKey(data, "soul") let soul = data["soul"] soul.getField("energy", result.energy) soul.getField("health", result.health) -proc importExplosion(data: PJsonNode; errors: var seq[string]): TExplosionRecord = +proc importExplosion(data: JsonNode; errors: var seq[string]): TExplosionRecord = checkKey(data, "explode") let expl = data["explode"] result.anim = importAnim(expl, errors) result.sound = importSound(expl, errors, "sound") -proc importSound*(data: PJsonNode; errors: var seq[string]; fieldName: string = nil): PSoundRecord = +proc importSound*(data: JsonNode; errors: var seq[string]; fieldName: string = ""): PSoundRecord = if data.kind == JObject: checkKey(data, fieldName) result = newSound(data[fieldName].str, errors) elif data.kind == JString: result = newSound(data.str, errors) -proc importVeh(data: PJsonNode; errors: var seq[string]): PVehicleRecord = +proc importVeh(data: JsonNode; errors: var seq[string]): PVehicleRecord = new(result) result.playable = false if data.kind != JArray or data.len != 2 or @@ -538,7 +538,7 @@ proc importVeh(data: PJsonNode; errors: var seq[string]): PVehicleRecord = vehdata.getField("playable", result.playable) if result.anim.spriteSheet.isNil and result.playable: result.playable = false -proc importObject(data: PJsonNode; errors: var seq[string]): PObjectRecord = +proc importObject(data: JsonNode; errors: var seq[string]): PObjectRecord = new(result) if data.kind != JArray or data.len != 2: result.name = "(broken)" @@ -546,7 +546,7 @@ proc importObject(data: PJsonNode; errors: var seq[string]): PObjectRecord = result.name = data[0].str result.anim = importAnim(data[1], errors) result.physics = importPhys(data[1]) -proc importItem(data: PJsonNode; errors: var seq[string]): PItemRecord = +proc importItem(data: JsonNode; errors: var seq[string]): PItemRecord = new(result) if data.kind != JArray or data.len != 3: result.name = "(broken)" @@ -562,7 +562,7 @@ proc importItem(data: PJsonNode; errors: var seq[string]): PItemRecord = result.useSound = importSound(data[2], errors, "useSound") - case data[1].str.toLower + case data[1].str.toLowerAscii of "projectile": result.kind = Projectile if data[2]["bullet"].kind == JString: @@ -576,15 +576,15 @@ proc importItem(data: PJsonNode; errors: var seq[string]): PItemRecord = of "ammo": result.kind = Ammo of "utility": - nil + discard else: errors.add "Invalid item type \""&data[1].str&"\" for item "&result.name -proc importBullet(data: PJsonNode; errors: var seq[string]): PBulletRecord = +proc importBullet(data: JsonNode; errors: var seq[string]): PBulletRecord = new(result) result.id = -1 - var bdata: PJsonNode + var bdata: JsonNode if data.kind == JArray: result.name = data[0].str bdata = data[1] diff --git a/tests/manyloc/keineschweine/lib/sg_gui.nim b/tests/manyloc/keineschweine/lib/sg_gui.nim index ffc4e8215..074e0604c 100644 --- a/tests/manyloc/keineschweine/lib/sg_gui.nim +++ b/tests/manyloc/keineschweine/lib/sg_gui.nim @@ -2,16 +2,16 @@ import sfml, sfml_colors, input_helpers, sg_packets from strutils import countlines -{.deadCodeElim: on.} + type PGuiContainer* = ref TGuiContainer - TGuiContainer* = object of TObject + TGuiContainer* = object of RootObj position: TVector2f activeEntry: PTextEntry widgets: seq[PGuiObject] buttons: seq[PButton] PGuiObject* = ref TGuiObject - TGuiObject* = object of TObject + TGuiObject* = object of RootObj PButton* = ref TButton TButton* = object of TGuiObject enabled: bool @@ -92,8 +92,8 @@ proc newGuiContainer*(pos: TVector2f): PGuiContainer = result = newGuiContainer() result.setPosition pos proc free*(container: PGuiContainer) = - container.widgets = nil - container.buttons = nil + container.widgets = @[] + container.buttons = @[] proc add*(container: PGuiContainer; widget: PGuiObject) = container.widgets.add(widget) proc add*(container: PGuiContainer; button: PButton) = diff --git a/tests/manyloc/keineschweine/lib/sg_packets.nim b/tests/manyloc/keineschweine/lib/sg_packets.nim index d84bf72fc..f3a0e8925 100644 --- a/tests/manyloc/keineschweine/lib/sg_packets.nim +++ b/tests/manyloc/keineschweine/lib/sg_packets.nim @@ -4,14 +4,14 @@ defPacketImports() type PacketID* = char -template idpacket(pktName, id, s2c, c2s: expr): stmt {.immediate, dirty.} = +template idpacket(pktName, id, s2c, c2s: untyped) {.dirty.} = let `H pktName`* {.inject.} = id defPacket(`Sc pktName`, s2c) defPacket(`Cs pktName`, c2s) forwardPacketT(uint8, int8) forwardPacketT(uint16, int16) -forwardPacketT(TPort, int16) +forwardPacketT(Port, int16) idPacket(Login, 'a', tuple[id: int32; alias: string; sessionKey: string], @@ -22,7 +22,7 @@ defPacket(CsZoneJoinReq, tuple[session: ScLogin]) defPacket(ScZoneRecord, tuple[ name: string = "", desc: string = "", - ip: string = "", port: TPort = 0.Tport]) + ip: string = "", port: Port = 0.Port]) idPacket(ZoneList, 'z', tuple[network: string = "", zones: seq[ScZoneRecord]], tuple[time: string]) diff --git a/tests/manyloc/keineschweine/server/nim.cfg b/tests/manyloc/keineschweine/server/nim.cfg index fdc45a8e1..211ad3003 100644 --- a/tests/manyloc/keineschweine/server/nim.cfg +++ b/tests/manyloc/keineschweine/server/nim.cfg @@ -1,5 +1,4 @@ debugger = off -deadCodeElim = on path = ".." path = "../genpacket" path = "../helpers" diff --git a/tests/manyloc/nake/nake.nim b/tests/manyloc/nake/nake.nim index 1e88fa73b..ff3c10a17 100644 --- a/tests/manyloc/nake/nake.nim +++ b/tests/manyloc/nake/nake.nim @@ -22,10 +22,10 @@ proc runTask*(name: string) {.inline.} proc shell*(cmd: varargs[string, `$`]): int {.discardable.} proc cd*(dir: string) {.inline.} -template nakeImports*(): stmt {.immediate.} = +template nakeImports*() = import tables, parseopt, strutils, os -template task*(name: string; description: string; body: stmt): stmt {.dirty, immediate.} = +template task*(name: string; description: string; body: untyped) {.dirty.} = block: var t = newTask(description, proc() {.closure.} = body) @@ -40,7 +40,7 @@ proc runTask*(name: string) = tasks[name].action() proc shell*(cmd: varargs[string, `$`]): int = result = execShellCmd(cmd.join(" ")) proc cd*(dir: string) = setCurrentDir(dir) -template withDir*(dir: string; body: stmt): stmt = +template withDir*(dir: string; body: untyped) = ## temporary cd ## withDir "foo": ## # inside foo @@ -67,7 +67,7 @@ else: for kind, key, val in getOpt(): case kind of cmdLongOption, cmdShortOption: - case key.tolower + case key.tolowerAscii of "tasks", "t": printTaskList = true else: @@ -75,7 +75,7 @@ else: of cmdArgument: task = key else: discard - if printTaskList or task.isNil or not(tasks.hasKey(task)): + if printTaskList or task.len == 0 or not(tasks.hasKey(task)): echo "Available tasks:" for name, task in pairs(tasks): echo name, " - ", task.desc diff --git a/tests/manyloc/nake/nakefile.nim b/tests/manyloc/nake/nakefile.nim index 2055d7834..3e8609169 100644 --- a/tests/manyloc/nake/nakefile.nim +++ b/tests/manyloc/nake/nakefile.nim @@ -10,7 +10,7 @@ const ExeName = "keineschweine" ServerDefines = "-d:NoSFML -d:NoChipmunk" TestBuildDefines = "-d:escapeMenuTest -d:debugWeps -d:showFPS -d:moreNim -d:debugKeys -d:foo -d:recordMode --forceBuild" - ReleaseDefines = "-d:release --deadCodeElim:on" + ReleaseDefines = "-d:release" ReleaseTestDefines = "-d:debugWeps -d:debugKeys --forceBuild" task "testprofile", "..": @@ -88,7 +88,7 @@ task "download", "download game assets": if existsFile(path): echo "The file already exists\n", "[R]emove [M]ove [Q]uit [S]kip Source: ", GameAssets - case stdin.readLine.toLower + case stdin.readLine.toLowerAscii of "r": removeFile path of "m": @@ -120,7 +120,7 @@ task "download", "download game assets": echo "Download binary libs? Only libs for linux are available currently, enjoy the irony.\n", "[Y]es [N]o Source: ", BinLibs - case stdin.readline.toLower + case stdin.readline.toLowerAscii of "y", "yes": discard ## o_O else: diff --git a/tests/manyloc/standalone/barebone.nim.cfg b/tests/manyloc/standalone/barebone.nim.cfg index bb350ff55..b956bef8e 100644 --- a/tests/manyloc/standalone/barebone.nim.cfg +++ b/tests/manyloc/standalone/barebone.nim.cfg @@ -1,3 +1,2 @@ --os:standalone ---deadCodeElim:on --gc:none diff --git a/tests/manyloc/standalone/panicoverride.nim b/tests/manyloc/standalone/panicoverride.nim index d9b3f4388..9d0d070c7 100644 --- a/tests/manyloc/standalone/panicoverride.nim +++ b/tests/manyloc/standalone/panicoverride.nim @@ -2,6 +2,10 @@ proc printf(frmt: cstring) {.varargs, importc, header: "<stdio.h>", cdecl.} proc exit(code: int) {.importc, header: "<stdlib.h>", cdecl.} +proc nimToCStringConv(s: NimString): cstring {.compilerProc, inline.} = + if s == nil or s.len == 0: result = cstring"" + else: result = cstring(addr s.data) + {.push stack_trace: off, profiler:off.} proc rawoutput(s: string) = diff --git a/tests/metatype/tautotypetrait.nim b/tests/metatype/tautotypetrait.nim new file mode 100644 index 000000000..84cd7b9f3 --- /dev/null +++ b/tests/metatype/tautotypetrait.nim @@ -0,0 +1,14 @@ +discard """ + output: '''(Field0: "string", Field1: "string")''' +""" + +# 7528 +import macros +import typetraits + +macro bar*(n: untyped): typed = + result = newNimNode(nnkStmtList, n) + result.add(newCall("write", newIdentNode("stdout"), n)) + +proc foo0[T](): auto = return (T.name, T.name) +bar foo0[string]() diff --git a/tests/metatype/tbindtypedesc.nim b/tests/metatype/tbindtypedesc.nim index b287aad01..039acfbe9 100644 --- a/tests/metatype/tbindtypedesc.nim +++ b/tests/metatype/tbindtypedesc.nim @@ -46,7 +46,7 @@ type type1 = typedesc type2 = typedesc -proc typePairs(A, B: type1; C, D: type2) = nil +proc typePairs(A, B: type1; C, D: type2) = discard accept typePairs(int, int, TFoo, TFOO) accept typePairs(TBAR, TBar, TBAR, TBAR) @@ -55,7 +55,7 @@ accept typePairs(int, int, string, string) reject typePairs(TBAR, TBar, TBar, TFoo) reject typePairs(string, int, TBAR, TBAR) -proc typePairs2[T: typedesc, U: typedesc](A, B: T; C, D: U) = nil +proc typePairs2[T: typedesc, U: typedesc](A, B: T; C, D: U) = discard accept typePairs2(int, int, TFoo, TFOO) accept typePairs2(TBAR, TBar, TBAR, TBAR) @@ -71,12 +71,12 @@ proc dontBind(a: typedesc, b: typedesc) = accept dontBind(int, float) accept dontBind(TFoo, TFoo) -proc dontBind2(a, b: typedesc) = nil +proc dontBind2(a, b: typedesc) = discard accept dontBind2(int, float) accept dontBind2(TBar, int) -proc bindArg(T: typedesc, U: typedesc, a, b: T, c, d: U) = nil +proc bindArg(T: typedesc, U: typedesc, a, b: T, c, d: U) = discard accept bindArg(int, string, 10, 20, "test", "nest") accept bindArg(int, int, 10, 20, 30, 40) diff --git a/tests/metatype/tcompositetypeclasses.nim b/tests/metatype/tcompositetypeclasses.nim index 1cb86e4d7..d125b119b 100644 --- a/tests/metatype/tcompositetypeclasses.nim +++ b/tests/metatype/tcompositetypeclasses.nim @@ -19,7 +19,7 @@ var vfoo: TFoo[int, string] vbar: TFoo[string, string] vbaz: TFoo[int, int] - vnotbaz: TFoo[TObject, TObject] + vnotbaz: TFoo[RootObj, RootObj] proc foo(x: TFoo) = echo "foo" proc bar(x: TBar) = echo "bar" diff --git a/tests/metatype/tstaticparammacro.nim b/tests/metatype/tstaticparammacro.nim index bd3295874..02021185f 100644 --- a/tests/metatype/tstaticparammacro.nim +++ b/tests/metatype/tstaticparammacro.nim @@ -14,7 +14,6 @@ AST b 20Test 20 ''' - disabled: true """ import macros @@ -26,7 +25,7 @@ type const data: Tconfig = (@["aa", "bb"], @[11, 22]) -macro mymacro(data: static[TConfig]) = +macro mymacro(data: static[TConfig]): untyped = echo "letters" for s in items(data.letters): echo s @@ -44,10 +43,10 @@ const a : Ta = @[(11, 22), (33, 44)] b : Tb = (@[55,66], @[77, 88]) -macro mA(data: static[Ta]) = +macro mA(data: static[Ta]): untyped = echo "AST a \n", repr(data) -macro mB(data: static[Tb]) = +macro mB(data: static[Tb]): untyped = echo "AST b \n", repr(data) echo data.e[0] @@ -57,13 +56,15 @@ mB(b) type Foo[N: static[int], Z: static[string]] = object -macro staticIntMacro(f: static[int]) = echo f +macro staticIntMacro(f: static[int]): untyped = + echo f + staticIntMacro 10 var x: Foo[20, "Test"] -macro genericMacro[N; Z: static[string]](f: Foo[N, Z], ll = 3, zz = 12) = +macro genericMacro[N; Z: static[string]](f: Foo[N, Z], ll = 3, zz = 12): untyped = echo N, Z genericMacro x diff --git a/tests/metatype/ttypedesc1.nim b/tests/metatype/ttypedesc1.nim index e9eee581f..837c8eccc 100644 --- a/tests/metatype/ttypedesc1.nim +++ b/tests/metatype/ttypedesc1.nim @@ -5,15 +5,16 @@ type x: T y: U -proc getTypeName(t: typedesc): string = t.name +proc getTypeName1(t: typedesc): string = t.name +proc getTypeName2(t: type): string = t.name -proc foo(T: typedesc[float], a: auto): string = +proc foo(T: type float, a: auto): string = result = "float " & $(a.len > 5) proc foo(T: typedesc[TFoo], a: int): string = result = "TFoo " & $(a) -proc foo(T: typedesc[int or bool]): string = +proc foo(T: type[int or bool]): string = var a: T a = 10 result = "int or bool " & ($a) @@ -23,8 +24,8 @@ template foo(T: typedesc[seq]): string = "seq" test "types can be used as proc params": # XXX: `check` needs to know that TFoo[int, float] is a type and # cannot be assigned for a local variable for later inspection - check ((string.getTypeName == "string")) - check ((getTypeName(int) == "int")) + check ((string.getTypeName1 == "string")) + check ((getTypeName2(int) == "int")) check ((foo(TFoo[int, float], 1000) == "TFoo 1000")) @@ -37,6 +38,25 @@ test "types can be used as proc params": check ((foo(seq[int]) == "seq")) check ((foo(seq[TFoo[bool, string]]) == "seq")) -when false: - proc foo(T: typedesc[seq], s: T) = nil +template accept(x) = + static: assert(compiles(x)) + +template reject(x) = + static: assert(not compiles(x)) + +var + si: seq[int] + ss: seq[string] + +proc foo(T: typedesc[seq], s: T) = + discard + +accept: + foo seq[int], si + +reject: + foo seq[string], si + +reject: + foo seq[int], ss diff --git a/tests/metatype/ttypedesc2.nim b/tests/metatype/ttypedesc2.nim index 4b6cfe6bc..94a7367e7 100644 --- a/tests/metatype/ttypedesc2.nim +++ b/tests/metatype/ttypedesc2.nim @@ -34,3 +34,17 @@ when true: type Point[T] = tuple[x, y: T] proc origin(T: typedesc): Point[T] = discard discard origin(int) + +# https://github.com/nim-lang/Nim/issues/7516 +import typetraits + +proc hasDefault1(T: type = int): auto = return T.name +doAssert hasDefault1(int) == "int" +doAssert hasDefault1(string) == "string" +doAssert hasDefault1() == "int" + +proc hasDefault2(T = string): auto = return T.name +doAssert hasDefault2(int) == "int" +doAssert hasDefault2(string) == "string" +doAssert hasDefault2() == "string" + diff --git a/tests/metatype/ttypedesc3.nim b/tests/metatype/ttypedesc3.nim index 9f19bd6e3..3d1cf2ec9 100644 --- a/tests/metatype/ttypedesc3.nim +++ b/tests/metatype/ttypedesc3.nim @@ -4,9 +4,9 @@ type Base = object of RootObj Child = object of Base -proc pr(T: typedesc[Base]) = echo "proc " & T.name -method me(T: typedesc[Base]) = echo "method " & T.name -iterator it(T: typedesc[Base]): auto = yield "yield " & T.name +proc pr(T: type[Base]) = echo "proc " & T.name +method me(T: type[Base]) = echo "method " & T.name +iterator it(T: type[Base]): auto = yield "yield " & T.name Base.pr Child.pr diff --git a/tests/metatype/ttypeselectors.nim b/tests/metatype/ttypeselectors.nim index c29fd15ce..eb857271d 100644 --- a/tests/metatype/ttypeselectors.nim +++ b/tests/metatype/ttypeselectors.nim @@ -1,15 +1,23 @@ -import macros +discard """ +output: "8\n8\n4" +""" -template selectType(x: int): typeDesc = +import + macros, typetraits + +template selectType(x: int): type = when x < 10: int else: string -template simpleTypeTempl: typeDesc = +template simpleTypeTempl: type = string -macro typeFromMacro: typedesc = string +macro typeFromMacro: type = string + +# The tests below check that the result variable of the +# selected type matches the literal types in the code: proc t1*(x: int): simpleTypeTempl() = result = "test" @@ -37,3 +45,69 @@ proc t6*(x: type(t3(0))): type(t1(0)) = proc t7*(x: int): type($x) = result = "test" +# This is a more compicated example involving a type +# selection through a macro: +# https://github.com/nim-lang/Nim/issues/7230 + +macro getBase*(bits: static[int]): untyped = + if bits >= 128: + result = newTree(nnkBracketExpr, ident("MpUintBase"), ident("uint64")) + else: + result = newTree(nnkBracketExpr, ident("MpUintBase"), ident("uint32")) + +type + BaseUint* = SomeUnsignedInt or MpUintBase + + MpUintBase*[T] = object + lo*, hi*: T + + ## This gets type mismatch + MpUint*[bits: static[int]] = getBase(bits) + +var m1: MpUint[128] +var m2: MpUint[64] +var m3: getBase(32) + +static: + # assert m1.type.name == "MpUintBase[uint64]" + assert m1.lo.type.name == "uint64" + assert m2.lo.type.name == "uint32" + assert m3.type.name == "MpUintBase[system.uint32]" + +# https://github.com/nim-lang/Nim/issues/7379 + +import macros, typetraits + +macro works(): untyped = + result = getType(int64) + +macro fails(bits: static[int]): untyped = + if bits > 64: + result = getType(int64) + else: + result = getType(int32) + +type + Foo*[bits: static[int]] = works() + Bar*[bits: static[int]] = fails(bits) + +var a: Foo[16] +var b: Bar[256] +var c: Bar[32] + +echo sizeof(a) +echo sizeof(b) +echo sizeof(c) + +# This is the same example but using a proc instead of a macro +# Instead of type mismatch for macro, proc just failed with internal error: getTypeDescAux(tyNone) +# https://github.com/nim-lang/Nim/issues/7231 + +proc getBase2*(bits: static[int]): typedesc = + if bits == 128: + result = newTree(nnkBracketExpr, ident("MpUintBase"), ident("uint64")) + else: + result = newTree(nnkBracketExpr, ident("MpUintBase"), ident("uint32")) + +type + MpUint2*[bits: static[int]] = getbase2(bits) diff --git a/tests/metatype/typeclassinference.nim b/tests/metatype/typeclassinference.nim index 8b99eb501..c845e04f7 100644 --- a/tests/metatype/typeclassinference.nim +++ b/tests/metatype/typeclassinference.nim @@ -1,7 +1,6 @@ discard """ errormsg: "type mismatch: got <string> but expected 'ptr'" line: 20 - disabled: true """ import typetraits @@ -12,7 +11,7 @@ type var x = Vec([1, 2, 3]) static: - assert x.type.name == "Vec[static[int](3), int]" + assert x.type.name == "Vec[3, system.int]" var str1: string = "hello, world!" var ptr1: ptr = addr(str1) diff --git a/tests/method/tmultim4.nim b/tests/method/tmultim4.nim index eabf8d126..a1d65d570 100644 --- a/tests/method/tmultim4.nim +++ b/tests/method/tmultim4.nim @@ -3,15 +3,15 @@ discard """ output: "hello" """ type - Test = object of TObject + Test = object of RootObj -method doMethod(a: ref TObject) {.base, raises: [EIO].} = +method doMethod(a: ref RootObj) {.base, raises: [IoError].} = quit "override" method doMethod(a: ref Test) = echo "hello" if a == nil: - raise newException(EIO, "arg") + raise newException(IoError, "arg") proc doProc(a: ref Test) = echo "hello" diff --git a/tests/method/tsimmeth.nim b/tests/method/tsimmeth.nim index 11ff2674f..a057c35b7 100644 --- a/tests/method/tsimmeth.nim +++ b/tests/method/tsimmeth.nim @@ -6,7 +6,7 @@ discard """ import strutils -var x = "hello world!".toLower.toUpper +var x = "hello world!".toLowerAscii.toUpperAscii x.echo() #OUT HELLO WORLD! diff --git a/tests/misc/tidentconcatenations.nim b/tests/misc/tidentconcatenations.nim new file mode 100644 index 000000000..302c51d87 --- /dev/null +++ b/tests/misc/tidentconcatenations.nim @@ -0,0 +1,32 @@ +type + Hash*[bits: static[int]] = object + data*: array[bits div 8, uint8] + +{.emit: """ + +void sha_256(void* input, int input_len, void* output, int output_len) {} +void sha_512(void* input, int input_len, void* output, int output_len) {} + +void keccak_256(void* input, int input_len, void* output, int output_len) {} +void keccak_512(void* input, int input_len, void* output, int output_len) {} + +""".} + +template defineKeccak(bits: untyped) = + proc `extKeccak bits`(output: pointer, outSize: csize, input: pointer, inputSize: csize) {.nodecl, importc: "keccak_" & astToStr(bits).} + +template defineSha(bits: static[int]) = + proc `extSha bits`(output: pointer, outSize: csize, input: pointer, inputSize: csize) {.nodecl, importc: "sha_" & astToStr(bits).} + +template defineHashProcs(bits) = + defineSha(bits) + defineKeccak(bits) + +defineHashProcs(256) +defineHashProcs(512) + +extSha256(nil, 0, nil, 0) +extSha512(nil, 0, nil, 0) +extKeccak256(nil, 0, nil, 0) +extKeccak512(nil, 0, nil, 0) + diff --git a/tests/misc/tmemoization.nim b/tests/misc/tmemoization.nim index 180acd89b..840eb3b0d 100644 --- a/tests/misc/tmemoization.nim +++ b/tests/misc/tmemoization.nim @@ -8,7 +8,7 @@ import strutils proc foo(s: static[string]): string = static: echo s - const R = s.toUpper + const R = s.toUpperAscii return R echo foo("test 1") diff --git a/tests/misc/tparamsindefault.nim b/tests/misc/tparamsindefault.nim new file mode 100644 index 000000000..c678dcc60 --- /dev/null +++ b/tests/misc/tparamsindefault.nim @@ -0,0 +1,114 @@ +discard """ +output: ''' +@[1, 2, 3]@[1, 2, 3] +a +a +1 +3 is an int +2 is an int +miau is a string +f1 1 1 1 +f1 2 3 3 +f1 10 20 30 +f2 100 100 100 +f2 200 300 300 +f2 300 400 400 +f3 10 10 20 +f3 10 15 25 +true true +false true +world +''' +""" + +template reject(x) = + assert(not compiles(x)) + +block: + # https://github.com/nim-lang/Nim/issues/7756 + proc foo[T](x: seq[T], y: seq[T] = x) = + echo x, y + + let a = @[1, 2, 3] + foo(a) + +block: + # https://github.com/nim-lang/Nim/issues/1201 + proc issue1201(x: char|int = 'a') = echo x + + issue1201() + issue1201('a') + issue1201(1) + + # https://github.com/nim-lang/Nim/issues/7000 + proc test(a: int|string = 2) = + when a is int: + echo a, " is an int" + elif a is string: + echo a, " is a string" + + test(3) # works + test() # works + test("miau") + +block: + # https://github.com/nim-lang/Nim/issues/3002 and similar + proc f1(a: int, b = a, c = b) = + echo "f1 ", a, " ", b, " ", c + + proc f2(a: int, b = a, c: int = b) = + echo "f2 ", a, " ", b, " ", c + + proc f3(a: int, b = a, c = a + b) = + echo "f3 ", a, " ", b, " ", c + + f1 1 + f1(2, 3) + f1 10, 20, 30 + 100.f2 + 200.f2 300 + 300.f2(400) + + 10.f3() + 10.f3(15) + + reject: + # This is a type mismatch error: + proc f4(a: int, b = a, c: float = b) = discard + + reject: + # undeclared identifier + proc f5(a: int, b = c, c = 10) = discard + + reject: + # undeclared identifier + proc f6(a: int, b = b) = discard + + reject: + # undeclared identifier + proc f7(a = a) = discard + +block: + proc f(a: var int, b: ptr int, c = addr(a)) = + echo addr(a) == b, " ", b == c + + var x = 10 + f(x, addr(x)) + f(x, nil, nil) + +block: + # https://github.com/nim-lang/Nim/issues/1046 + proc pySubstr(s: string, start: int, endd = s.len()): string = + var + revStart = start + revEnd = endd + + if start < 0: + revStart = s.len() + start + if endd < 0: + revEnd = s.len() + endd + + return s[revStart .. revEnd-1] + + echo pySubstr("Hello world", -5) + diff --git a/tests/misc/tparseopt.nim b/tests/misc/tparseopt.nim index 1f8375dfd..badbc59ad 100644 --- a/tests/misc/tparseopt.nim +++ b/tests/misc/tparseopt.nim @@ -9,6 +9,17 @@ kind: cmdLongOption key:val -- left: kind: cmdLongOption key:val -- debug:3 kind: cmdShortOption key:val -- l:4 kind: cmdShortOption key:val -- r:2 +parseoptNoVal +kind: cmdLongOption key:val -- left: +kind: cmdLongOption key:val -- debug:3 +kind: cmdShortOption key:val -- l: +kind: cmdShortOption key:val -- r:2 +kind: cmdLongOption key:val -- debug:2 +kind: cmdLongOption key:val -- debug:1 +kind: cmdShortOption key:val -- r:1 +kind: cmdShortOption key:val -- r:0 +kind: cmdShortOption key:val -- l: +kind: cmdShortOption key:val -- r:4 parseopt2 first round kind: cmdLongOption key:val -- left: @@ -40,6 +51,15 @@ block: echo "kind: ", kind, "\tkey:val -- ", key, ":", val block: + echo "parseoptNoVal" + # test NoVal mode with custom cmdline arguments + var argv = "--left --debug:3 -l -r:2 --debug 2 --debug=1 -r1 -r=0 -lr4" + var p = parseopt.initOptParser(argv, + shortNoVal = {'l'}, longNoVal = @["left"]) + for kind, key, val in parseopt.getopt(p): + echo "kind: ", kind, "\tkey:val -- ", key, ":", val + +block: echo "parseopt2" for kind, key, val in parseopt2.getopt(): echo "kind: ", kind, "\tkey:val -- ", key, ":", val diff --git a/tests/misc/tsemfold.nim b/tests/misc/tsemfold.nim new file mode 100644 index 000000000..2101a8b50 --- /dev/null +++ b/tests/misc/tsemfold.nim @@ -0,0 +1,27 @@ +discard """ + action: run +""" + +doAssertRaises(OverflowError): discard low(int8) - 1'i8 +doAssertRaises(OverflowError): discard high(int8) + 1'i8 +doAssertRaises(OverflowError): discard abs(low(int8)) +doAssertRaises(DivByZeroError): discard 1 mod 0 +doAssertRaises(DivByZeroError): discard 1 div 0 +doAssertRaises(OverflowError): discard low(int8) div -1'i8 + +doAssertRaises(OverflowError): discard -low(int64) +doAssertRaises(OverflowError): discard low(int64) - 1'i64 +doAssertRaises(OverflowError): discard high(int64) + 1'i64 + +type E = enum eA, eB +doAssertRaises(OverflowError): discard eA.pred +doAssertRaises(OverflowError): discard eB.succ + +doAssertRaises(OverflowError): discard low(int8) * -1 +doAssertRaises(OverflowError): discard low(int64) * -1 +doAssertRaises(OverflowError): discard high(int8) * 2 +doAssertRaises(OverflowError): discard high(int64) * 2 + +doAssert abs(-1) == 1 +doAssert 2 div 2 == 1 +doAssert 2 * 3 == 6 \ No newline at end of file diff --git a/tests/misc/tsimplesort.nim b/tests/misc/tsimplesort.nim index 3115863d5..e4a8e0b37 100644 --- a/tests/misc/tsimplesort.nim +++ b/tests/misc/tsimplesort.nim @@ -40,11 +40,11 @@ proc mustRehash(length, counter: int): bool {.inline.} = assert(length > counter) result = (length * 2 < counter * 3) or (length - counter < 4) -proc nextTry(h, maxHash: THash): THash {.inline.} = +proc nextTry(h, maxHash: Hash): Hash {.inline.} = result = ((5 * h) + 1) and maxHash template rawGetImpl() = - var h: THash = hash(key) and high(t.data) # start with real hash value + var h: Hash = hash(key) and high(t.data) # start with real hash value while t.data[h].slot != seEmpty: if t.data[h].key == key and t.data[h].slot == seFilled: return h @@ -52,7 +52,7 @@ template rawGetImpl() = result = -1 template rawInsertImpl() = - var h: THash = hash(key) and high(data) + var h: Hash = hash(key) and high(data) while data[h].slot == seFilled: h = nextTry(h, high(data)) data[h].key = key @@ -162,7 +162,7 @@ iterator values*[A](t: TCountTable[A]): int = if t.data[h].val != 0: yield t.data[h].val proc RawGet[A](t: TCountTable[A], key: A): int = - var h: THash = hash(key) and high(t.data) # start with real hash value + var h: Hash = hash(key) and high(t.data) # start with real hash value while t.data[h].val != 0: if t.data[h].key == key: return h h = nextTry(h, high(t.data)) @@ -181,7 +181,7 @@ proc hasKey*[A](t: TCountTable[A], key: A): bool = proc rawInsert[A](t: TCountTable[A], data: var seq[tuple[key: A, val: int]], key: A, val: int) = - var h: THash = hash(key) and high(data) + var h: Hash = hash(key) and high(data) while data[h].val != 0: h = nextTry(h, high(data)) data[h].key = key data[h].val = val diff --git a/tests/misc/tsizeof.nim b/tests/misc/tsizeof.nim index 4afd48472..0597535f9 100644 --- a/tests/misc/tsizeof.nim +++ b/tests/misc/tsizeof.nim @@ -1,5 +1,7 @@ -# Test the sizeof proc - +discard """ + file: "tsize.nim" + output: "40 3 12 32" +""" type TMyRecord {.final.} = object x, y: int @@ -7,4 +9,20 @@ type r: float s: string + TMyEnum = enum + tmOne, tmTwo, tmThree, tmFour + + TMyArray1 = array[3, uint8] + TMyArray2 = array[1..3, int32] + TMyArray3 = array[TMyEnum, float64] + +const + mysize1 = sizeof(TMyArray1) + mysize2 = sizeof(TMyArray2) + mysize3 = sizeof(TMyArray3) + write(stdout, sizeof(TMyRecord)) +echo ' ', mysize1, ' ', mysize2, ' ',mysize3 + + + diff --git a/tests/misc/tunsignedmisc.nim b/tests/misc/tunsignedmisc.nim index 4b8157ddd..b2a3849cf 100644 --- a/tests/misc/tunsignedmisc.nim +++ b/tests/misc/tunsignedmisc.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "number 0x123'u8 out of valid range" + errormsg: "number out of range: '0x123'u8'" """ # Bug #1179 @@ -9,7 +9,7 @@ discard """ # 8 bit let ref1 = 128'u8 shr 7 let hex1 = 0x80'u8 shr 7 -let oct1 = 0c200'u8 shr 7 +let oct1 = 0o200'u8 shr 7 let dig1 = 0b10000000'u8 shr 7 doAssert(ref1 == 1) @@ -20,7 +20,7 @@ doAssert(ref1 == dig1) # 16 bit let ref2 = 32768'u16 shr 15 let hex2 = 0x8000'u16 shr 15 -let oct2 = 0c100000'u16 shr 15 +let oct2 = 0o100000'u16 shr 15 let dig2 = 0b1000000000000000'u16 shr 15 doAssert(ref2 == 1) @@ -31,7 +31,7 @@ doAssert(ref2 == dig2) # 32 bit let ref3 = 2147483648'u32 shr 31 let hex3 = 0x80000000'u32 shr 31 -let oct3 = 0c20000000000'u32 shr 31 +let oct3 = 0o20000000000'u32 shr 31 let dig3 = 0b10000000000000000000000000000000'u32 shr 31 doAssert(ref3 == 1) diff --git a/tests/misc/åäö.nim b/tests/misc/åäö.nim new file mode 100644 index 000000000..b3caa9861 --- /dev/null +++ b/tests/misc/åäö.nim @@ -0,0 +1,11 @@ +discard """ + action: run +""" + +# Tests that module names can contain multi byte characters + +let a = 1 +doAssert åäö.a == 1 + +proc inlined() {.inline.} = discard +inlined() \ No newline at end of file diff --git a/tests/modules/definitions.nim b/tests/modules/definitions.nim new file mode 100644 index 000000000..edc6eaa6d --- /dev/null +++ b/tests/modules/definitions.nim @@ -0,0 +1,4 @@ +var v*: int +proc p* = echo "proc p called" +template t* = echo "template t expanded" + diff --git a/tests/modules/proxy_module.nim b/tests/modules/proxy_module.nim new file mode 100644 index 000000000..c244688cd --- /dev/null +++ b/tests/modules/proxy_module.nim @@ -0,0 +1,3 @@ +import definitions +export definitions except p + diff --git a/tests/modules/t8665.nim b/tests/modules/t8665.nim new file mode 100644 index 000000000..51538df79 --- /dev/null +++ b/tests/modules/t8665.nim @@ -0,0 +1 @@ +import treorder diff --git a/tests/modules/tcanimport.nim b/tests/modules/tcanimport.nim new file mode 100644 index 000000000..bc4e2e53f --- /dev/null +++ b/tests/modules/tcanimport.nim @@ -0,0 +1,19 @@ +discard """ + output: '''ABC +nope''' +""" + +template canImport(x): bool = + compiles: + import x + +when canImport(strutils): + import strutils + echo "abc".toUpperAscii +else: + echo "meh" + +when canImport(none): + echo "what" +else: + echo "nope" diff --git a/tests/modules/timportas.nim b/tests/modules/timportas.nim new file mode 100644 index 000000000..a92162117 --- /dev/null +++ b/tests/modules/timportas.nim @@ -0,0 +1,16 @@ +discard """ + action: run +""" + +import .. / modules / [definitions as foo] +import .. / modules / definitions as foo +import std / times as bar +from times as bar2 import nil +import times as bar3 except convert +import definitions as baz + +discard foo.v +discard bar.now +discard bar2.now +discard bar3.now +discard baz.v \ No newline at end of file diff --git a/tests/newconfig/tfoo.nims b/tests/newconfig/tfoo.nims index 8d0ed2c42..3be42c38a 100644 --- a/tests/newconfig/tfoo.nims +++ b/tests/newconfig/tfoo.nims @@ -23,4 +23,58 @@ task default, "default target": setCommand "c" # bug #6327 -discard existsEnv("dummy") +doAssert(existsEnv("dummy") == false) + +# issue #7283 +putEnv("dummy", "myval") +doAssert(existsEnv("dummy") == true) +doAssert(getEnv("dummy") == "myval") + +# issue #7393 +let wd = getCurrentDir() +cd("..") +assert wd != getCurrentDir() +cd(wd) +assert wd == getCurrentDir() + +assert findExe("nim") != "" + +# general tests +mode = ScriptMode.Verbose + +assert getCommand() == "c" +setCommand("cpp") +assert getCommand() == "cpp" +setCommand("c") + +assert cmpic("HeLLO", "hello") == 0 + +assert fileExists("tests/newconfig/tfoo.nims") == true +assert dirExists("tests") == true + +assert existsFile("tests/newconfig/tfoo.nims") == true +assert existsDir("tests") == true + +discard selfExe() + +when defined(windows): + assert toExe("nim") == "nim.exe" + assert toDll("nim") == "nim.dll" +else: + assert toExe("nim") == "nim" + assert toDll("nim") == "libnim.so" + +rmDir("tempXYZ") +assert dirExists("tempXYZ") == false +mkDir("tempXYZ") +assert dirExists("tempXYZ") == true +assert fileExists("tempXYZ/koch.nim") == false +cpFile("koch.nim", "tempXYZ/koch.nim") +assert fileExists("tempXYZ/koch.nim") == true +cpDir("nimsuggest", "tempXYZ/.") +assert dirExists("tempXYZ/tests") == true +assert fileExists("tempXYZ/nimsuggest.nim") == true +rmFile("tempXYZ/koch.nim") +assert fileExists("tempXYZ/koch.nim") == false +rmDir("tempXYZ") +assert dirExists("tempXYZ") == false diff --git a/tests/niminaction/Chapter1/various1.nim b/tests/niminaction/Chapter1/various1.nim new file mode 100644 index 000000000..688180fd2 --- /dev/null +++ b/tests/niminaction/Chapter1/various1.nim @@ -0,0 +1,45 @@ +discard """ + exitCode: 0 + outputsub: "Woof!" +""" + +import strutils +echo("hello".to_upper()) +echo("world".toUpper()) + +type + Dog = object #<1> + age: int #<2> + +let dog = Dog(age: 3) #<3> + +proc showNumber(num: int | float) = + echo(num) + +showNumber(3.14) +showNumber(42) + +for i in 0 .. <10: + echo(i) + +block: # Block added due to clash. + type + Dog = object + + proc bark(self: Dog) = #<1> + echo("Woof!") + + let dog = Dog() + dog.bark() #<2> + +import sequtils, future, strutils +let list = @["Dominik Picheta", "Andreas Rumpf", "Desmond Hume"] +list.map( + (x: string) -> (string, string) => (x.split[0], x.split[1]) +).echo + +import strutils +let list1 = @["Dominik Picheta", "Andreas Rumpf", "Desmond Hume"] +for name in list1: + echo((name.split[0], name.split[1])) + diff --git a/tests/niminaction/Chapter2/explicit_discard.nim b/tests/niminaction/Chapter2/explicit_discard.nim new file mode 100644 index 000000000..3e94c335b --- /dev/null +++ b/tests/niminaction/Chapter2/explicit_discard.nim @@ -0,0 +1,7 @@ +discard """ + line: 7 + errormsg: "has to be discarded" +""" + +proc myProc(name: string): string = "Hello " & name +myProc("Dominik") \ No newline at end of file diff --git a/tests/niminaction/Chapter2/no_def_eq.nim b/tests/niminaction/Chapter2/no_def_eq.nim new file mode 100644 index 000000000..77f0a7dd8 --- /dev/null +++ b/tests/niminaction/Chapter2/no_def_eq.nim @@ -0,0 +1,16 @@ +discard """ + line: 16 + errormsg: "type mismatch" +""" + +type + Dog = object + name: string + + Cat = object + name: string + +let dog: Dog = Dog(name: "Fluffy") +let cat: Cat = Cat(name: "Fluffy") + +echo(dog == cat) \ No newline at end of file diff --git a/tests/niminaction/Chapter2/no_iterator.nim b/tests/niminaction/Chapter2/no_iterator.nim new file mode 100644 index 000000000..331d69480 --- /dev/null +++ b/tests/niminaction/Chapter2/no_iterator.nim @@ -0,0 +1,7 @@ +discard """ + line: 6 + errormsg: "type mismatch" +""" + +for i in 5: + echo i \ No newline at end of file diff --git a/tests/niminaction/Chapter2/no_seq_type.nim b/tests/niminaction/Chapter2/no_seq_type.nim new file mode 100644 index 000000000..493be270a --- /dev/null +++ b/tests/niminaction/Chapter2/no_seq_type.nim @@ -0,0 +1,6 @@ +discard """ + line: 6 + errormsg: "cannot infer the type of the sequence" +""" + +var list = @[] \ No newline at end of file diff --git a/tests/niminaction/Chapter2/resultaccept.nim b/tests/niminaction/Chapter2/resultaccept.nim new file mode 100644 index 000000000..7dd976b40 --- /dev/null +++ b/tests/niminaction/Chapter2/resultaccept.nim @@ -0,0 +1,28 @@ +discard """ + output: "" +""" + +# Page 35. + +proc implicit: string = + "I will be returned" + +proc discarded: string = + discard "I will not be returned" + +proc explicit: string = + return "I will be returned" + +proc resultVar: string = + result = "I will be returned" + +proc resultVar2: string = + result = "" + result.add("I will be ") + result.add("returned") + +doAssert implicit() == "I will be returned" +doAssert discarded() == nil +doAssert explicit() == "I will be returned" +doAssert resultVar() == "I will be returned" +doAssert resultVar2() == "I will be returned" \ No newline at end of file diff --git a/tests/niminaction/Chapter2/resultreject.nim b/tests/niminaction/Chapter2/resultreject.nim new file mode 100644 index 000000000..de59af7d9 --- /dev/null +++ b/tests/niminaction/Chapter2/resultreject.nim @@ -0,0 +1,33 @@ +discard """ + line: 27 + errormsg: "has to be discarded" +""" + +# Page 35. + +proc implicit: string = + "I will be returned" + +proc discarded: string = + discard "I will not be returned" + +proc explicit: string = + return "I will be returned" + +proc resultVar: string = + result = "I will be returned" + +proc resultVar2: string = + result = "" + result.add("I will be ") + result.add("returned") + +proc resultVar3: string = + result = "I am the result" + "I will cause an error" + +doAssert implicit() == "I will be returned" +doAssert discarded() == nil +doAssert explicit() == "I will be returned" +doAssert resultVar() == "I will be returned" +doAssert resultVar2() == "I will be returned" \ No newline at end of file diff --git a/tests/niminaction/Chapter2/various2.nim b/tests/niminaction/Chapter2/various2.nim new file mode 100644 index 000000000..3f6a3f453 --- /dev/null +++ b/tests/niminaction/Chapter2/various2.nim @@ -0,0 +1,369 @@ +discard """ + exitCode: 0 + outputsub: '''42 is greater than 0''' +""" + +if 42 >= 0: + echo "42 is greater than 0" + + +echo("Output: ", + 5) +echo(5 + + 5) +# --- Removed code that is supposed to fail here. Not going to test those. --- + +# Single-line comment +#[ +Multiline comment +]# +when false: + echo("Commented-out code") + +let decimal = 42 +let hex = 0x42 +let octal = 0o42 +let binary = 0b101010 + +let a: int16 = 42 +let b = 42'i8 + +let c = 1'f32 # --- Changed names here to avoid clashes --- +let d = 1.0e19 + +let e = false +let f = true + +let g = 'A' +let h = '\109' +let i = '\x79' + +let text = "The book title is \"Nim in Action\"" + +let filepath = "C:\\Program Files\\Nim" + +# --- Changed name here to avoid clashes --- +let filepath1 = r"C:\Program Files\Nim" + +let multiLine = """foo + bar + baz +""" +echo multiLine + +import strutils +# --- Changed name here to avoid clashes --- +let multiLine1 = """foo + bar + baz +""" +echo multiLine1.unindent +doAssert multiLine1.unindent == "foo\nbar\nbaz\n" + +proc fillString(): string = + result = "" + echo("Generating string") + for i in 0 .. 4: + result.add($i) #<1> + +const count = fillString() + +var + text1 = "hello" + number: int = 10 + isTrue = false + +var 火 = "Fire" +let ogień = true + +var `var` = "Hello" +echo(`var`) + +proc myProc(name: string): string = "Hello " & name +discard myProc("Dominik") + +proc bar(): int #<1> + +proc foo(): float = bar().float +proc bar(): int = foo().int + +proc noReturn() = echo("Hello") +proc noReturn2(): void = echo("Hello") + +proc noReturn3 = echo("Hello") + +proc message(recipient: string): auto = + "Hello " & recipient + +doAssert message("Dom") == "Hello Dom" + +proc max(a: int, b: int): int = + if a > b: a else: b + +doAssert max(5, 10) == 10 + +proc max2(a, b: int): int = + if a > b: a else: b + +proc genHello(name: string, surname = "Doe"): string = + "Hello " & name & " " & surname + +# -- Leaving these as asserts as that is in the original code, just in case +# -- somehow in the future `assert` is removed :) +assert genHello("Peter") == "Hello Peter Doe" +assert genHello("Peter", "Smith") == "Hello Peter Smith" + +proc genHello2(names: varargs[string]): string = + result = "" + for name in names: + result.add("Hello " & name & "\n") + +doAssert genHello2("John", "Bob") == "Hello John\nHello Bob\n" + +proc getUserCity(firstName, lastName: string): string = + case firstName + of "Damien": return "Tokyo" + of "Alex": return "New York" + else: return "Unknown" + +proc getUserCity(userID: int): string = + case userID + of 1: return "Tokyo" + of 2: return "New York" + else: return "Unknown" + +doAssert getUserCity("Damien", "Lundi") == "Tokyo" +doAssert getUserCity(2) == "New York" # -- Errata here: missing closing " + +import sequtils +let numbers = @[1, 2, 3, 4, 5, 6] +let odd = filter(numbers, proc (x: int): bool = x mod 2 != 0) +doAssert odd == @[1, 3, 5] + +import sequtils, future +let numbers1 = @[1, 2, 3, 4, 5, 6] +let odd1 = filter(numbers1, (x: int) -> bool => x mod 2 != 0) +assert odd1 == @[1, 3, 5] + +proc isValid(x: int, validator: proc (x: int): bool) = + if validator(x): echo(x, " is valid") + else: echo(x, " is NOT valid") + +import future +proc isValid2(x: int, validator: (x: int) -> bool) = + if validator(x): echo(x, " is valid") + else: echo(x, " is NOT valid") + +var list: array[3, int] +list[0] = 1 +list[1] = 42 +assert list[0] == 1 +assert list[1] == 42 +assert list[2] == 0 #<1> + +echo list.repr #<2> + +# echo list[500] + +var list2: array[-10 .. -9, int] +list2[-10] = 1 +list2[-9] = 2 + +var list3 = ["Hi", "There"] + +var list4 = ["My", "name", "is", "Dominik"] +for item in list4: + echo(item) + +for i in list4.low .. list4.high: + echo(list4[i]) + +var list5: seq[int] = @[] +doAssertRaises(IndexError): + list5[0] = 1 + +list5.add(1) + +assert list5[0] == 1 +doAssertRaises(IndexError): + echo list5[42] + +# -- Errata: var list: seq[int]; echo(list[0]). This now creates an exception, +# -- not a SIGSEGV. + +block: + var list = newSeq[string](3) + assert list[0] == nil + list[0] = "Foo" + list[1] = "Bar" + list[2] = "Baz" + + list.add("Lorem") + +block: + let list = @[4, 8, 15, 16, 23, 42] + for i in 0 .. <list.len: + stdout.write($list[i] & " ") + +var collection: set[int16] +doAssert collection == {} + +block: + let collection = {'a', 'x', 'r'} + doAssert 'a' in collection + +block: + let collection = {'a', 'T', 'z'} + let isAllLowerCase = {'A' .. 'Z'} * collection == {} + doAssert(not isAllLowerCase) + +let age = 10 +if age > 0 and age <= 10: + echo("You're still a child") +elif age > 10 and age < 18: + echo("You're a teenager") +else: + echo("You're an adult") + +let variable = "Arthur" +case variable +of "Arthur", "Zaphod", "Ford": + echo("Male") +of "Marvin": + echo("Robot") +of "Trillian": + echo("Female") +else: + echo("Unknown") + +let ageDesc = if age < 18: "Non-Adult" else: "Adult" + +block: + var i = 0 + while i < 3: + echo(i) + i.inc + +block label: + var i = 0 + while true: + while i < 5: + if i > 3: break label + i.inc + +iterator values(): int = + var i = 0 + while i < 5: + yield i + i.inc + +for value in values(): + echo(value) + +import os +for filename in walkFiles("*.nim"): + echo(filename) + +for item in @[1, 2, 3]: + echo(item) + +for i, value in @[1, 2, 3]: echo("Value at ", i, ": ", value) + +doAssertRaises(IOError): + proc second() = + raise newException(IOError, "Somebody set us up the bomb") + + proc first() = + second() + + first() + +block: + proc second() = + raise newException(IOError, "Somebody set us up the bomb") + + proc first() = + try: + second() + except: + echo("Cannot perform second action because: " & + getCurrentExceptionMsg()) + + first() + +block: + type + Person = object + name: string + age: int + + var person: Person + var person1 = Person(name: "Neo", age: 28) + +block: + type + PersonObj = object + name: string + age: int + PersonRef = ref PersonObj + + # proc setName(person: PersonObj) = + # person.name = "George" + + proc setName(person: PersonRef) = + person.name = "George" + +block: + type + Dog = object + name: string + + Cat = object + name: string + + let dog: Dog = Dog(name: "Fluffy") + let cat: Cat = Cat(name: "Fluffy") + +block: + type + Dog = tuple + name: string + + Cat = tuple + name: string + + let dog: Dog = (name: "Fluffy") + let cat: Cat = (name: "Fluffy") + + echo(dog == cat) + +block: + type + Point = tuple[x, y: int] + Point2 = (int, int) + + let pos: Point = (x: 100, y: 50) + doAssert pos == (100, 50) + + let pos1: Point = (x: 100, y: 50) + let (x, y) = pos1 #<1> + let (left, _) = pos1 + doAssert x == pos1[0] + doAssert y == pos1[1] + doAssert left == x + +block: + type + Color = enum + colRed, + colGreen, + colBlue + + let color: Color = colRed + +block: + type + Color {.pure.} = enum + red, green, blue + + let color = Color.red \ No newline at end of file diff --git a/tests/niminaction/Chapter3/various3.nim b/tests/niminaction/Chapter3/various3.nim new file mode 100644 index 000000000..478229b00 --- /dev/null +++ b/tests/niminaction/Chapter3/various3.nim @@ -0,0 +1,93 @@ +import threadpool +proc foo: string = "Dog" +var x: FlowVar[string] = spawn foo() +assert(^x == "Dog") + +block: + type + Box = object + case empty: bool + of false: + contents: string + else: + discard + + var obj = Box(empty: false, contents: "Hello") + assert obj.contents == "Hello" + + var obj2 = Box(empty: true) + doAssertRaises(FieldError): + echo(obj2.contents) + +import json +assert parseJson("null").kind == JNull +assert parseJson("true").kind == JBool +assert parseJson("42").kind == JInt +assert parseJson("3.14").kind == JFloat +assert parseJson("\"Hi\"").kind == JString +assert parseJson("""{ "key": "value" }""").kind == JObject +assert parseJson("[1, 2, 3, 4]").kind == JArray + +import json +let data = """ + {"username": "Dominik"} +""" + +let obj = parseJson(data) +assert obj.kind == JObject +assert obj["username"].kind == JString +assert obj["username"].str == "Dominik" + +block: + proc count10(): int = + for i in 0 .. <10: + result.inc + assert count10() == 10 + +type + Point = tuple[x, y: int] + +var point = (5, 10) +var point2 = (x: 5, y: 10) + +type + Human = object + name: string + age: int + +var jeff = Human(name: "Jeff", age: 23) +var amy = Human(name: "Amy", age: 20) + +import asyncdispatch + +var future = newFuture[int]() +doAssert(not future.finished) + +future.callback = + proc (future: Future[int]) = + echo("Future is no longer empty, ", future.read) + +future.complete(42) + +import asyncdispatch, asyncfile + +when false: + var file = openAsync("") + let dataFut = file.readAll() + dataFut.callback = + proc (future: Future[string]) = + echo(future.read()) + + asyncdispatch.runForever() + +import asyncdispatch, asyncfile, os + +proc readFiles() {.async.} = + # --- Changed to getTempDir here. + var file = openAsync(getTempDir() / "test.txt", fmReadWrite) + let data = await file.readAll() + echo(data) + await file.write("Hello!\n") + +waitFor readFiles() + diff --git a/tests/niminaction/Chapter3/various3.nim.cfg b/tests/niminaction/Chapter3/various3.nim.cfg new file mode 100644 index 000000000..6c1ded992 --- /dev/null +++ b/tests/niminaction/Chapter3/various3.nim.cfg @@ -0,0 +1 @@ +threads:on \ No newline at end of file diff --git a/tests/notnil/tmust_compile.nim b/tests/notnil/tmust_compile.nim index 10da154f0..d09dda057 100644 --- a/tests/notnil/tmust_compile.nim +++ b/tests/notnil/tmust_compile.nim @@ -3,6 +3,7 @@ discard """ """ # bug #6682 +{.experimental: "notnil".} type Fields = enum @@ -43,16 +44,16 @@ import json type foo = object - thing: string not nil + thing: ptr int not nil CTS = ref object subs_by_sid: Table[int, foo] proc parse(cts: CTS, jn: JsonNode) = - + var y = jn.getInt(4523) let ces = foo( - thing: jn.getStr("thing") + thing: addr y ) cts.subs_by_sid[0] = ces @@ -62,17 +63,3 @@ proc parse(cts: CTS, jn: JsonNode) = proc p(x: proc(){.closure.} not nil) = discard p(proc(){.closure.} = discard) - -# bug #3993 - -type - List[T] = seq[T] not nil - -proc `^^`[T](v: T, lst: List[T]): List[T] = - result = @[v] - result.add(lst) - -proc Nil[T](): List[T] = @[] - -when isMainModule: - let lst = 1 ^^ 2 ^^ Nil[int]() diff --git a/tests/notnil/tnotnil.nim b/tests/notnil/tnotnil.nim index f65634ed6..aff3f8959 100644 --- a/tests/notnil/tnotnil.nim +++ b/tests/notnil/tnotnil.nim @@ -1,23 +1,13 @@ discard """ - line: 22 + line: 13 errormsg: "type mismatch" """ - +{.experimental: "notnil".} type PObj = ref TObj not nil TObj = object x: int - MyString = string not nil - -#var x: PObj = nil - -proc p(x: string not nil): int = - result = 45 - -proc q(x: MyString) = nil -proc q2(x: string) = nil +proc q2(x: string) = discard q2(nil) -q(nil) - diff --git a/tests/notnil/tnotnil1.nim b/tests/notnil/tnotnil1.nim index 73472752c..60666d64d 100644 --- a/tests/notnil/tnotnil1.nim +++ b/tests/notnil/tnotnil1.nim @@ -1,30 +1,17 @@ discard """ errormsg: "'y' is provably nil" - line:38 + line:25 """ import strutils - +{.experimental: "notnil".} type TObj = object x, y: int -type - superstring = string not nil - - -proc q(s: superstring) = - echo s - -proc p2() = - var a: string = "I am not nil" - q(a) # but this should and does not - -p2() - proc q(x: pointer not nil) = - nil + discard proc p() = var x: pointer diff --git a/tests/notnil/tnotnil2.nim b/tests/notnil/tnotnil2.nim index bd6b8b675..6cd08de73 100644 --- a/tests/notnil/tnotnil2.nim +++ b/tests/notnil/tnotnil2.nim @@ -4,14 +4,14 @@ discard """ """ import strutils - +{.experimental: "notnil".} type TObj = object x, y: int proc q(x: pointer not nil) = - nil + discard proc p() = var x: pointer diff --git a/tests/notnil/tnotnil3.nim b/tests/notnil/tnotnil3.nim index b7c7a811d..31a4efef7 100644 --- a/tests/notnil/tnotnil3.nim +++ b/tests/notnil/tnotnil3.nim @@ -5,7 +5,7 @@ discard """ # bug #584 # Testprogram for 'not nil' check - +{.experimental: "notnil".} const testWithResult = true type diff --git a/tests/notnil/tnotnil4.nim b/tests/notnil/tnotnil4.nim index 2fa888357..4fd169827 100644 --- a/tests/notnil/tnotnil4.nim +++ b/tests/notnil/tnotnil4.nim @@ -2,6 +2,8 @@ discard "" type TObj = ref object +{.experimental: "notnil".} + proc check(a: TObj not nil) = echo repr(a) diff --git a/tests/notnil/tnotnil_in_generic.nim b/tests/notnil/tnotnil_in_generic.nim index 357ab2c7c..89d20f182 100644 --- a/tests/notnil/tnotnil_in_generic.nim +++ b/tests/notnil/tnotnil_in_generic.nim @@ -3,6 +3,7 @@ discard """ """ # bug #2216 +{.experimental: "notnil".} type A[T] = ref object diff --git a/tests/notnil/tnotnil_in_objconstr.nim b/tests/notnil/tnotnil_in_objconstr.nim index 7dce98c29..f0d5c1ae2 100644 --- a/tests/notnil/tnotnil_in_objconstr.nim +++ b/tests/notnil/tnotnil_in_objconstr.nim @@ -2,13 +2,13 @@ discard """ errormsg: "fields not initialized: bar" line: "13" """ - +{.experimental: "notnil".} # bug #2355 type Foo = object - foo: string not nil - bar: string not nil - + foo: ref int + bar: ref int not nil +var x: ref int = new(int) # Create instance without initializaing the `bar` field -var f = Foo(foo: "foo") +var f = Foo(foo: x) echo f.bar.isNil # true diff --git a/tests/objects/tinherentedvalues.nim b/tests/objects/tinherentedvalues.nim index c96a0fd6d..7d4d7d23e 100644 --- a/tests/objects/tinherentedvalues.nim +++ b/tests/objects/tinherentedvalues.nim @@ -1,14 +1,12 @@ discard """ - output: '''tbObj of TC false -false + output: '''tbObj of TC true true -5 -false''' +5''' """ # bug #1053 type - TA = object of TObject + TA = object of RootObj a: int TB = object of TA @@ -20,10 +18,10 @@ type proc test(p: TA) = #echo "p of TB ", p of TB if p of TB: - var tbObj = TB(p) + #var tbObj = TB(p) # tbObj is actually no longer compatible with TC: - echo "tbObj of TC ", tbObj of TC + echo "tbObj of TC ", p of TC var v = TC() v.a = 1 @@ -34,13 +32,13 @@ test(v) # bug #924 type - MyObject = object of TObject + MyObject = object of RootObj x: int var asd: MyObject -proc isMyObject(obj: TObject) = +proc isMyObject(obj: RootObj) = echo obj of MyObject if obj of MyObject: let a = MyObject(obj) @@ -48,8 +46,8 @@ proc isMyObject(obj: TObject) = asd.x = 5 -var asdCopy = TObject(asd) -echo asdCopy of MyObject +#var asdCopy = RootObj(asd) +#echo asdCopy of MyObject isMyObject(asd) -isMyObject(asdCopy) +#isMyObject(asdCopy) diff --git a/tests/objects/tobj_asgn_dont_slice.nim b/tests/objects/tobj_asgn_dont_slice.nim new file mode 100644 index 000000000..866b5aecc --- /dev/null +++ b/tests/objects/tobj_asgn_dont_slice.nim @@ -0,0 +1,24 @@ +discard """ + outputsub: '''ObjectAssignmentError''' + exitcode: "1" +""" + +# bug #7637 +type + Fruit = object of RootObj + name*: string + Apple = object of Fruit + Pear = object of Fruit + +method eat(f: Fruit) {.base.} = + raise newException(Exception, "PURE VIRTUAL CALL") + +method eat(f: Apple) = + echo "fruity" + +method eat(f: Pear) = + echo "juicy" + +let basket = [Apple(name:"a"), Pear(name:"b")] + +eat(basket[0]) diff --git a/tests/objects/tobjconstr.nim b/tests/objects/tobjconstr.nim index d1f3c8bdb..7238d10c7 100644 --- a/tests/objects/tobjconstr.nim +++ b/tests/objects/tobjconstr.nim @@ -9,8 +9,8 @@ discard """ (k: kindA, a: (x: "abc", z: [1, 8, 3]), method: ()) (k: kindA, a: (x: "abc", z: [1, 9, 3]), method: ()) (k: kindA, a: (x: "abc", z: [1, 10, 3]), method: ()) -(x: 123) -(x: 123) +(y: 0, x: 123) +(y: 678, x: 123) (z: 89, y: 0, x: 128) (y: 678, x: 123) (y: 678, x: 123) @@ -33,7 +33,6 @@ type `method`: TEmpty # bug #1791 proc `$`[T](s: seq[T]): string = - # XXX why is that not in the stdlib? result = "[" for i, x in s: if i > 0: result.add(", ") @@ -59,7 +58,7 @@ type # inherited fields are ignored, so no fields are set when true: var - o: A + o: B o = B(x: 123) echo o o = B(y: 678, x: 123) diff --git a/tests/objects/tobjcov.nim b/tests/objects/tobjcov.nim index cf2e5becf..c766adde0 100644 --- a/tests/objects/tobjcov.nim +++ b/tests/objects/tobjcov.nim @@ -1,7 +1,7 @@ # Covariance is not type safe: type - TA = object of TObject + TA = object of RootObj a: int TB = object of TA b: array[0..5000_000, int] diff --git a/tests/objects/tobjects.nim b/tests/objects/tobjects.nim index 2f46b46b5..66a38960e 100644 --- a/tests/objects/tobjects.nim +++ b/tests/objects/tobjects.nim @@ -1,5 +1,5 @@ type - TBase = object of TObject + TBase = object of RootObj x, y: int TSubclassKind = enum ka, kb, kc, kd, ke, kf @@ -13,7 +13,7 @@ type n: bool type - TMyObject = object of TObject + TMyObject = object of RootObj case disp: range[0..4] of 0: arg: char of 1: s: string diff --git a/tests/objects/toop.nim b/tests/objects/toop.nim index ebc59f637..4bd3998f3 100644 --- a/tests/objects/toop.nim +++ b/tests/objects/toop.nim @@ -3,7 +3,7 @@ discard """ """ type - TA = object of TObject + TA = object of RootObj x, y: int TB = object of TA diff --git a/tests/objvariant/tcheckedfield1.nim b/tests/objvariant/tcheckedfield1.nim index 56d784a2b..a7f232c5b 100644 --- a/tests/objvariant/tcheckedfield1.nim +++ b/tests/objvariant/tcheckedfield1.nim @@ -6,7 +6,7 @@ discard """ import strutils {.warning[ProveField]: on.} - +{.experimental: "notnil".} type TNodeKind = enum nkBinary, nkTernary, nkStr diff --git a/tests/objvariant/temptycaseobj.nim b/tests/objvariant/temptycaseobj.nim index 5c012746e..53171e054 100644 --- a/tests/objvariant/temptycaseobj.nim +++ b/tests/objvariant/temptycaseobj.nim @@ -1,6 +1,6 @@ discard """ line: 11 - errormsg: "identifier expected, but found 'keyword of'" + errormsg: "identifier expected, but got 'keyword of'" """ type diff --git a/tests/objvariant/tvariantstack.nim b/tests/objvariant/tvariantstack.nim index d81f6e001..0cdde5a20 100644 --- a/tests/objvariant/tvariantstack.nim +++ b/tests/objvariant/tvariantstack.nim @@ -49,4 +49,34 @@ var t = stack.pop() echo "came here" +# another regression: +type + LexerToken* = enum + ltYamlDirective, ltYamlVersion, ltTagDirective, ltTagShorthand, + ltTagUri, ltUnknownDirective, ltUnknownDirectiveParams, ltEmptyLine, + ltDirectivesEnd, ltDocumentEnd, ltStreamEnd, ltIndentation, ltQuotedScalar, + ltScalarPart, ltBlockScalarHeader, ltBlockScalar, ltSeqItemInd, ltMapKeyInd, + ltMapValInd, ltBraceOpen, ltBraceClose, ltBracketOpen, ltBracketClose, + ltComma, ltLiteralTag, ltTagHandle, ltAnchor, ltAlias + +const tokensWithValue = + {ltScalarPart, ltQuotedScalar, ltYamlVersion, ltTagShorthand, ltTagUri, + ltUnknownDirective, ltUnknownDirectiveParams, ltLiteralTag, ltAnchor, + ltAlias, ltBlockScalar} + +type + TokenWithValue = object + case kind: LexerToken + of tokensWithValue: + value: string + of ltIndentation: + indentation: int + of ltTagHandle: + handle, suffix: string + else: discard + +proc sp(v: string): TokenWithValue = + # test.nim(27, 17) Error: a case selecting discriminator 'kind' with value 'ltScalarPart' appears in the object construction, but the field(s) 'value' are in conflict with this value. + TokenWithValue(kind: ltScalarPart, value: v) +let a = sp("test") diff --git a/tests/osproc/tclose.nim b/tests/osproc/tclose.nim new file mode 100644 index 000000000..d466b466a --- /dev/null +++ b/tests/osproc/tclose.nim @@ -0,0 +1,24 @@ +discard """ + exitcode: 0 +""" + +when defined(linux): + import osproc, os + + proc countFds(): int = + result = 0 + for i in walkDir("/proc/self/fd"): + result += 1 + + let initCount = countFds() + + let p = osproc.startProcess("echo", options={poUsePath}) + assert countFds() == initCount + 3 + p.close + assert countFds() == initCount + + let p1 = osproc.startProcess("echo", options={poUsePath}) + discard p1.inputStream + assert countFds() == initCount + 3 + p.close + assert countFds() == initCount diff --git a/tests/osproc/texitsignal.nim b/tests/osproc/texitsignal.nim index c0bed70ee..fbf5068ea 100644 --- a/tests/osproc/texitsignal.nim +++ b/tests/osproc/texitsignal.nim @@ -28,9 +28,12 @@ if paramCount() == 0: # windows kill happens using TerminateProcess(h, 0), so we should get a # 0 here echo p.waitForExit() == 0 + elif defined(haiku): + # on Haiku, the program main thread receive SIGKILLTHR + echo p.waitForExit() == 128 + SIGKILLTHR else: # on posix (non-windows), kill sends SIGKILL echo p.waitForExit() == 128 + SIGKILL else: - sleep(5000) # should get killed before this \ No newline at end of file + sleep(5000) # should get killed before this diff --git a/tests/osproc/tstderr.nim b/tests/osproc/tstderr.nim new file mode 100644 index 000000000..7a39522a3 --- /dev/null +++ b/tests/osproc/tstderr.nim @@ -0,0 +1,32 @@ +discard """ + output: '''-------------------------------------- +to stderr +to stderr +-------------------------------------- +''' +""" +import osproc, os, streams + +const filename = "ta_out".addFileExt(ExeExt) + +doAssert fileExists(getCurrentDir() / "tests" / "osproc" / filename) + +var p = startProcess(filename, getCurrentDir() / "tests" / "osproc", + options={}) + +try: + let stdoutStream = p.outputStream + let stderrStream = p.errorStream + var x = newStringOfCap(120) + var output = "" + while stderrStream.readLine(x.TaintedString): + output.add(x & "\n") + + echo "--------------------------------------" + stdout.flushFile() + stderr.write output + stderr.flushFile() + echo "--------------------------------------" + stdout.flushFile() +finally: + p.close() diff --git a/tests/osproc/tstdin.nim b/tests/osproc/tstdin.nim index d94c34192..9b49ed786 100644 --- a/tests/osproc/tstdin.nim +++ b/tests/osproc/tstdin.nim @@ -11,9 +11,8 @@ doAssert fileExists(getCurrentDir() / "tests" / "osproc" / filename) var p = startProcess(filename, getCurrentDir() / "tests" / "osproc") p.inputStream.write("5\n") p.inputStream.flush() -while true: - let line = p.outputStream.readLine() - if line != "": - echo line - else: - break + +var line = "" + +while p.outputStream.readLine(line.TaintedString): + echo line diff --git a/tests/osproc/tworkingdir.nim b/tests/osproc/tworkingdir.nim index eeed9240d..7d58d5ae6 100644 --- a/tests/osproc/tworkingdir.nim +++ b/tests/osproc/tworkingdir.nim @@ -12,6 +12,8 @@ else: var process: Process when defined(android): process = startProcess("/system/bin/env", "/system/bin", ["true"]) + elif defined(haiku): + process = startProcess("/bin/env", "/bin", ["true"]) else: process = startProcess("/usr/bin/env", "/usr/bin", ["true"]) let dir2 = getCurrentDir() diff --git a/tests/overflw/toverflw.nim b/tests/overflw/toverflw.nim index 771a43303..20bc56a53 100644 --- a/tests/overflw/toverflw.nim +++ b/tests/overflw/toverflw.nim @@ -1,21 +1,84 @@ discard """ file: "toverflw.nim" - output: "the computation overflowed" + output: "ok" + cmd: "nim $target -d:release $options $file" + """ # Tests nim's ability to detect overflows {.push overflowChecks: on.} var - a, b: int -a = high(int) -b = -2 + a = high(int) + b = -2 + overflowDetected = false + try: writeLine(stdout, b - a) except OverflowError: - writeLine(stdout, "the computation overflowed") + overflowDetected = true {.pop.} # overflow check -#OUT the computation overflowed + +doAssert(overflowDetected) + +block: # Overflow checks in a proc + var + a = high(int) + b = -2 + overflowDetected = false + + {.push overflowChecks: on.} + proc foo() = + let c = b - a + {.pop.} + + try: + foo() + except OverflowError: + overflowDetected = true + + doAssert(overflowDetected) + +block: # Overflow checks in a forward declared proc + var + a = high(int) + b = -2 + overflowDetected = false + + proc foo() + + {.push overflowChecks: on.} + proc foo() = + let c = b - a + {.pop.} + + try: + foo() + except OverflowError: + overflowDetected = true + + doAssert(overflowDetected) + +block: # Overflow checks doesn't affect fwd declaration + var + a = high(int) + b = -2 + overflowDetected = false + + {.push overflowChecks: on.} + proc foo() + {.pop.} + + proc foo() = + let c = b - a + + try: + foo() + except OverflowError: + overflowDetected = true + + doAssert(not overflowDetected) +echo "ok" diff --git a/tests/overload/tissue966.nim b/tests/overload/tissue966.nim index c5b28e217..d0a723875 100644 --- a/tests/overload/tissue966.nim +++ b/tests/overload/tissue966.nim @@ -5,7 +5,7 @@ discard """ type PTest = ref object -proc test(x: PTest, y: int) = nil +proc test(x: PTest, y: int) = discard var buf: PTest buf.test() diff --git a/tests/overload/toverl4.nim b/tests/overload/toverl4.nim index b63265bdf..537925674 100644 --- a/tests/overload/toverl4.nim +++ b/tests/overload/toverl4.nim @@ -28,9 +28,9 @@ proc newElement[TKey, TData](key: TKey, left: PElement[TKey, TData] = nil, right proc newElement[Tkey, TData](key: Tkey, data: TData) : PElement[Tkey, TData] = PElement[TKey, TData](kind: ElementKind.leaf, data: data) -proc find*[TKey, TData](root: PElement[TKey, TData], key: TKey): TData {.raises: [EInvalidKey].} = +proc find*[TKey, TData](root: PElement[TKey, TData], key: TKey): TData {.raises: [KeyError].} = if root.left == nil: - raise newException(EInvalidKey, "key does not exist: " & key) + raise newException(KeyError, "key does not exist: " & key) var tmp_element = addr(root) @@ -43,7 +43,7 @@ proc find*[TKey, TData](root: PElement[TKey, TData], key: TKey): TData {.raises: if tmp_element.key == key: return tmp_element.left.data else: - raise newException(EInvalidKey, "key does not exist: " & key) + raise newException(KeyError, "key does not exist: " & key) proc add*[TKey, TData](root: var PElement[TKey, TData], key: TKey, data: TData) : bool = if root.left == nil: diff --git a/tests/overload/toverprc.nim b/tests/overload/toverprc.nim index 112eae096..9be2203f6 100644 --- a/tests/overload/toverprc.nim +++ b/tests/overload/toverprc.nim @@ -11,7 +11,7 @@ proc parseInt(x: float): int {.noSideEffect.} = discard proc parseInt(x: bool): int {.noSideEffect.} = discard proc parseInt(x: float32): int {.noSideEffect.} = discard proc parseInt(x: int8): int {.noSideEffect.} = discard -proc parseInt(x: TFile): int {.noSideEffect.} = discard +proc parseInt(x: File): int {.noSideEffect.} = discard proc parseInt(x: char): int {.noSideEffect.} = discard proc parseInt(x: int16): int {.noSideEffect.} = discard diff --git a/tests/overload/toverwr.nim b/tests/overload/toverwr.nim index 5945a6149..b5791072e 100644 --- a/tests/overload/toverwr.nim +++ b/tests/overload/toverwr.nim @@ -4,7 +4,7 @@ discard """ """ # Test the overloading resolution in connection with a qualifier -proc write(t: TFile, s: string) = +proc write(t: File, s: string) = discard # a nop system.write(stdout, "hello") diff --git a/tests/overload/tparam_forwarding.nim b/tests/overload/tparam_forwarding.nim index c1b276bfc..b0eea42c7 100644 --- a/tests/overload/tparam_forwarding.nim +++ b/tests/overload/tparam_forwarding.nim @@ -6,6 +6,10 @@ output: '''baz a b c +x: 1, y: test 1 +x: 2, y: test 2 +x: 10, y: test 3 +x: 4, y: test 4 ''' """ @@ -35,3 +39,15 @@ templateForwarding fooVarargs, "test".len > 3, Foo(x: 10), Foo(x: 100), Foo(x: 1 procForwarding "a", "b", "c" +proc hasKeywordArgs(x = 10, y = "y") = + echo "x: ", x, ", y: ", y + +proc hasRegularArgs(x: int, y: string) = + echo "x: ", x, ", y: ", y + +templateForwarding(hasRegularArgs, true, 1, "test 1") +templateForwarding hasKeywordArgs, true, 2, "test 2" + +templateForwarding(hasKeywordArgs, true, y = "test 3") +templateForwarding hasKeywordArgs, true, y = "test 4", x = 4 + diff --git a/tests/overload/tsystemcmp.nim b/tests/overload/tsystemcmp.nim index 68cbf9fa7..aa761b759 100644 --- a/tests/overload/tsystemcmp.nim +++ b/tests/overload/tsystemcmp.nim @@ -1,12 +1,14 @@ discard """ cmd: r"nim c --hints:on $options --threads:on $file" + output: '''@["", "a", "ha", "hi", "ho", "huu"]''' """ import algorithm # bug #1657 -var modules = @["hi", "ho", "ha", "huu"] +var modules = @["hi", "ho", "", "a", "ha", "huu"] sort(modules, system.cmp) +echo modules type MyType = object diff --git a/tests/parallel/tparfind.nim b/tests/parallel/tparfind.nim index 9de5012f5..4b3610c67 100644 --- a/tests/parallel/tparfind.nim +++ b/tests/parallel/tparfind.nim @@ -4,7 +4,7 @@ discard """ import threadpool, sequtils -{.experimental.} +{.experimental: "parallel".} proc linearFind(a: openArray[int]; x, offset: int): int = for i, y in a: diff --git a/tests/parallel/tptr_to_ref.nim b/tests/parallel/tptr_to_ref.nim index fee210dcd..ae02c16e5 100644 --- a/tests/parallel/tptr_to_ref.nim +++ b/tests/parallel/tptr_to_ref.nim @@ -11,7 +11,7 @@ type Killer* = object lock: Lock bailed {.guard: lock.}: bool - processes {.guard: lock.}: array[0..MAX_WORKERS-1, foreign ptr Process] + processes {.guard: lock.}: array[0..MAX_WORKERS-1, ptr Process] # Hold a lock for a statement. template hold(lock: Lock, body: untyped) = @@ -32,7 +32,7 @@ proc initKiller*(): Killer = var killer = initKiller() # remember that a process has been launched, killing it if we have bailed. -proc launched*(process: foreign ptr Process): int {.gcsafe.} = +proc launched*(process: ptr Process): int {.gcsafe.} = result = killer.processes.high + 1 killer.lock.hold: if killer.bailed: diff --git a/tests/parallel/tsendtwice.nim b/tests/parallel/tsendtwice.nim index 0700fc4da..0c923177a 100644 --- a/tests/parallel/tsendtwice.nim +++ b/tests/parallel/tsendtwice.nim @@ -1,11 +1,11 @@ discard """ - output: '''obj2 nil -obj nil -obj3 nil + output: '''obj2 @[] +obj @[] +obj3 @[] 3 -obj2 nil -obj nil -obj3 nil''' +obj2 @[] +obj @[] +obj3 @[]''' cmd: "nim c -r --threads:on $file" """ diff --git a/tests/parallel/twaitany.nim b/tests/parallel/twaitany.nim new file mode 100644 index 000000000..fcabf691e --- /dev/null +++ b/tests/parallel/twaitany.nim @@ -0,0 +1,35 @@ +discard """ + output: '''true''' +""" + +# bug #7638 +import threadpool, os, strformat + +proc timer(d: int): int = + #echo fmt"sleeping {d}" + sleep(d) + #echo fmt"done {d}" + return d + +var durations = [1000, 2000, 3000, 4000, 5000] +var tasks: seq[FlowVarBase] = @[] +var results: seq[int] = @[] + +for i in 0 .. durations.high: + tasks.add spawn timer(durations[i]) + +var index = blockUntilAny(tasks) +while index != -1: + results.add ^cast[FlowVar[int]](tasks[index]) + tasks.del(index) + #echo repr results + index = blockUntilAny(tasks) + +doAssert results.len == 5 +doAssert 1000 in results +doAssert 2000 in results +doAssert 3000 in results +doAssert 4000 in results +doAssert 5000 in results +sync() +echo "true" diff --git a/tests/parser/tbraces.nim b/tests/parser/tbraces.nim deleted file mode 100644 index 86c854546..000000000 --- a/tests/parser/tbraces.nim +++ /dev/null @@ -1,432 +0,0 @@ -#? braces - -# -# -# Nim's Runtime Library -# (c) Copyright 2015 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements some common generic algorithms. - -type - SortOrder* = enum { ## sort order - Descending, Ascending - } - - -type( - DummyAlias = int - OtherAlias = distinct char - - SomeObject = object of RootObj { ## declaration here - fieldA, fieldB: int - case order: SortOrder { - of Descending {a: string} - of Ascending {b: seq[char]} - } - } - - MyConcept = concept x { - x is int - } -) - -{.deprecated: [TSortOrder: SortOrder].} - - -proc `*`*(x: int, order: SortOrder): int @inline { - ## flips `x` if ``order == Descending``; - ## if ``order == Ascending`` then `x` is returned. - ## `x` is supposed to be the result of a comparator, ie ``< 0`` for - ## *less than*, ``== 0`` for *equal*, ``> 0`` for *greater than*. - var y = order.ord - 1 - result = (x xor y) - y -} - -proc fill*[T](a: var openArray[T], first, last: Natural, value: T) { - ## fills the array ``a[first..last]`` with `value`. - var x = first - while x <= last { - a[x] = value - inc(x) - } -} - -proc fill*[T](a: var openArray[T], value: T) { - ## fills the array `a` with `value`. - fill(a, 0, a.high, value) -} - -proc reverse*[T](a: var openArray[T], first, last: Natural) { - ## reverses the array ``a[first..last]``. - var x = first - var y = last - while x < y { - swap(a[x], a[y]) - dec(y) - inc(x) - } -} - -proc reverse*[T](a: var openArray[T]) { - ## reverses the array `a`. - reverse(a, 0, a.high) -} - -proc reversed*[T](a: openArray[T], first: Natural, last: int): seq[T] { - ## returns the reverse of the array `a[first..last]`. - assert last >= first-1 - var i = last - first - var x = first.int - result = newSeq[T](i + 1) - while i >= 0 { - result[i] = a[x] - dec(i) - inc(x) - } -} - -proc reversed*[T](a: openArray[T]): seq[T] { - ## returns the reverse of the array `a`. - reversed(a, 0, a.high) -} - -proc binarySearch*[T](a: openArray[T], key: T): int { - ## binary search for `key` in `a`. Returns -1 if not found. - var b = len(a) - while result < b { - var mid = (result + b) div 2 - if a[mid] < key { result = mid + 1 } else { b = mid } - } - if result >= len(a) or a[result] != key { result = -1 } -} - -proc smartBinarySearch*[T](a: openArray[T], key: T): int { - ## ``a.len`` must be a power of 2 for this to work. - var step = a.len div 2 - while step > 0 { - if a[result or step] <= key { result = result or step } - step = step shr 1 - } - if a[result] != key { result = -1 } -} - -const ( - onlySafeCode = true -) - -proc lowerBound*[T](a: openArray[T], key: T, cmp: proc(x,y: T): int @closure): int { - ## same as binarySearch except that if key is not in `a` then this - ## returns the location where `key` would be if it were. In other - ## words if you have a sorted sequence and you call - ## insert(thing, elm, lowerBound(thing, elm)) - ## the sequence will still be sorted. - ## - ## `cmp` is the comparator function to use, the expected return values are - ## the same as that of system.cmp. - ## - ## example:: - ## - ## var arr = @[1,2,3,5,6,7,8,9] - ## arr.insert(4, arr.lowerBound(4)) - ## # after running the above arr is `[1,2,3,4,5,6,7,8,9]` - result = a.low - var count = a.high - a.low + 1 - var step, pos: int - while count != 0 { - step = count div 2 - pos = result + step - if cmp(a[pos], key) < 0 { - result = pos + 1 - count -= step + 1 - } else { - count = step - } - } -} - -proc lowerBound*[T](a: openArray[T], key: T): int { lowerBound(a, key, cmp[T]) } -proc merge[T](a, b: var openArray[T], lo, m, hi: int, - cmp: proc (x, y: T): int @closure, order: SortOrder) { - template `<-` (a, b) { - when false { - a = b - } elif onlySafeCode { - shallowCopy(a, b) - } else { - copyMem(addr(a), addr(b), sizeof(T)) - } - } - # optimization: If max(left) <= min(right) there is nothing to do! - # 1 2 3 4 ## 5 6 7 8 - # -> O(n) for sorted arrays. - # On random data this safes up to 40% of merge calls - if cmp(a[m], a[m+1]) * order <= 0 { return } - var j = lo - # copy a[j..m] into b: - assert j <= m - when onlySafeCode { - var bb = 0 - while j <= m { - b[bb] <- a[j] - inc(bb) - inc(j) - } - } else { - copyMem(addr(b[0]), addr(a[j]), sizeof(T)*(m-j+1)) - j = m+1 - } - var i = 0 - var k = lo - # copy proper element back: - while k < j and j <= hi { - if cmp(b[i], a[j]) * order <= 0 { - a[k] <- b[i] - inc(i) - } else { - a[k] <- a[j] - inc(j) - } - inc(k) - } - # copy rest of b: - when onlySafeCode { - while k < j { - a[k] <- b[i] - inc(k) - inc(i) - } - } else { - if k < j { copyMem(addr(a[k]), addr(b[i]), sizeof(T)*(j-k)) } - } -} - -proc sort*[T](a: var openArray[T], - cmp: proc (x, y: T): int @closure, - order = SortOrder.Ascending) { - ## Default Nim sort (an implementation of merge sort). The sorting - ## is guaranteed to be stable and the worst case is guaranteed to - ## be O(n log n). - ## The current implementation uses an iterative - ## mergesort to achieve this. It uses a temporary sequence of - ## length ``a.len div 2``. Currently Nim does not support a - ## sensible default argument for ``cmp``, so you have to provide one - ## of your own. However, the ``system.cmp`` procs can be used: - ## - ## .. code-block:: nim - ## - ## sort(myIntArray, system.cmp[int]) - ## - ## # do not use cmp[string] here as we want to use the specialized - ## # overload: - ## sort(myStrArray, system.cmp) - ## - ## You can inline adhoc comparison procs with the `do notation - ## <manual.html#procedures-do-notation>`_. Example: - ## - ## .. code-block:: nim - ## - ## people.sort do (x, y: Person) -> int: - ## result = cmp(x.surname, y.surname) - ## if result == 0: - ## result = cmp(x.name, y.name) - var n = a.len - var b: seq[T] - newSeq(b, n div 2) - var s = 1 - while s < n { - var m = n-1-s - while m >= 0 { - merge(a, b, max(m-s+1, 0), m, m+s, cmp, order) - dec(m, s*2) - } - s = s*2 - } -} - -proc sorted*[T](a: openArray[T], cmp: proc(x, y: T): int {.closure.}, - order = SortOrder.Ascending): seq[T] { - ## returns `a` sorted by `cmp` in the specified `order`. - result = newSeq[T](a.len) - for i in 0 .. a.high { result[i] = a[i] } - sort(result, cmp, order) -} - -template sortedByIt*(seq1, op: untyped): untyped { - ## Convenience template around the ``sorted`` proc to reduce typing. - ## - ## The template injects the ``it`` variable which you can use directly in an - ## expression. Example: - ## - ## .. code-block:: nim - ## - ## type Person = tuple[name: string, age: int] - ## var - ## p1: Person = (name: "p1", age: 60) - ## p2: Person = (name: "p2", age: 20) - ## p3: Person = (name: "p3", age: 30) - ## p4: Person = (name: "p4", age: 30) - ## people = @[p1,p2,p4,p3] - ## - ## echo people.sortedByIt(it.name) - ## - ## Because the underlying ``cmp()`` is defined for tuples you can do - ## a nested sort like in the following example: - ## - ## .. code-block:: nim - ## - ## echo people.sortedByIt((it.age, it.name)) - ## - var result = sorted(seq1, proc(x, y: type[seq1[0]]): int { - var it {.inject.} = x - let a = op - it = y - let b = op - result = cmp(a, b) - }) - result -} - -proc isSorted*[T](a: openarray[T], - cmp: proc(x, y: T): int {.closure.}, - order = SortOrder.Ascending): bool { - ## Checks to see whether `a` is already sorted in `order` - ## using `cmp` for the comparison. Parameters identical - ## to `sort` - result = true - for i in 0..<len(a)-1 { - case cmp(a[i],a[i+1]) * order > 0 { - of true { return false } - of false {} - } - } -} - -proc product*[T](x: openArray[seq[T]]): seq[seq[T]] { - ## produces the Cartesian product of the array. Warning: complexity - ## may explode. - result = newSeq[seq[T]]() - if x.len == 0 { return } - if x.len == 1 { - result = @x - return - } - var ( - indexes = newSeq[int](x.len) - initial = newSeq[int](x.len) - index = 0 - ) - var next = newSeq[T]() - next.setLen(x.len) - for i in 0..(x.len-1) { - if len(x[i]) == 0 { return } - initial[i] = len(x[i])-1 - } - indexes = initial - while true { - while indexes[index] == -1 { - indexes[index] = initial[index] - index += 1 - if index == x.len { return } - indexes[index] -= 1 - } - for ni, i in indexes { - next[ni] = x[ni][i] - } - var res: seq[T] - shallowCopy(res, next) - result.add(res) - index = 0 - indexes[index] -= 1 - } -} - -proc nextPermutation*[T](x: var openarray[T]): bool {.discardable.} { - ## Calculates the next lexicographic permutation, directly modifying ``x``. - ## The result is whether a permutation happened, otherwise we have reached - ## the last-ordered permutation. - ## - ## .. code-block:: nim - ## - ## var v = @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - ## v.nextPermutation() - ## echo v # @[0, 1, 2, 3, 4, 5, 6, 7, 9, 8] - if x.len < 2 { - return false } - - var i = x.high - while i > 0 and x[i-1] >= x[i] { dec i } - if i == 0 { return false } - - var j = x.high - while j >= i and x[j] <= x[i-1] { dec j } - - swap x[j], x[i-1] - x.reverse(i, x.high) - - result = true -} - -proc prevPermutation*[T](x: var openarray[T]): bool @discardable { - ## Calculates the previous lexicographic permutation, directly modifying - ## ``x``. The result is whether a permutation happened, otherwise we have - ## reached the first-ordered permutation. - ## - ## .. code-block:: nim - ## - ## var v = @[0, 1, 2, 3, 4, 5, 6, 7, 9, 8] - ## v.prevPermutation() - ## echo v # @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - if x.len < 2 { return false } - - var i = x.high - while i > 0 and x[i-1] <= x[i] { - dec i - } - if i == 0 { return false } - - x.reverse(i, x.high) - - var j = x.high - while j >= i and x[j-1] < x[i-1] { - dec j - } - swap x[i-1], x[j] - - result = true -} - -when isMainModule { - # Tests for lowerBound - var arr = @[1,2,3,5,6,7,8,9] - assert arr.lowerBound(0) == 0 - assert arr.lowerBound(4) == 3 - assert arr.lowerBound(5) == 3 - assert arr.lowerBound(10) == 8 - arr = @[1,5,10] - try { - assert arr.lowerBound(4) == 1 - assert arr.lowerBound(5) == 1 - assert arr.lowerBound(6) == 2 - } except ValueError {} - # Tests for isSorted - var srt1 = [1,2,3,4,4,4,4,5] - var srt2 = ["iello","hello"] - var srt3 = [1.0,1.0,1.0] - var srt4: seq[int] = @[] - assert srt1.isSorted(cmp) == true - assert srt2.isSorted(cmp) == false - assert srt3.isSorted(cmp) == true - var srtseq = newSeq[int]() - assert srtseq.isSorted(cmp) == true - # Tests for reversed - var arr1 = @[0,1,2,3,4] - assert arr1.reversed() == @[4,3,2,1,0] - for i in 0 .. high(arr1) { - assert arr1.reversed(0, i) == arr1.reversed()[high(arr1) - i .. high(arr1)] - assert arr1.reversed(i, high(arr1)) == arr1.reversed()[0 .. high(arr1) - i] - } -} diff --git a/tests/parser/tcommand_as_expr.nim b/tests/parser/tcommand_as_expr.nim index a244c8767..b25ec4bd8 100644 --- a/tests/parser/tcommand_as_expr.nim +++ b/tests/parser/tcommand_as_expr.nim @@ -2,7 +2,10 @@ discard """ output: '''140 5-120-120 359 -77''' +77 +-4 +-1 +-1''' """ #import math import sequtils @@ -25,3 +28,11 @@ let a = [2,4,8].map do (d:int) -> int: d + 1 echo a[0], a[1], a[2] echo(foo 8, foo 8) + +# bug #7582 +proc f(x: int): int = x + +echo f -4 + +echo int -1 # doesn't compile +echo int `-` 1 # compiles diff --git a/tests/parser/tinvcolonlocation1.nim b/tests/parser/tinvcolonlocation1.nim index cacde48bd..2fddab2f8 100644 --- a/tests/parser/tinvcolonlocation1.nim +++ b/tests/parser/tinvcolonlocation1.nim @@ -1,8 +1,8 @@ discard """ file: "tinvcolonlocation1.nim" line: 8 - column: 3 - errormsg: "':' expected" + column: 7 + errormsg: "expected: ':', but got: 'echo'" """ try #<- missing ':' echo "try" diff --git a/tests/parser/tinvcolonlocation2.nim b/tests/parser/tinvcolonlocation2.nim index 2b6a92b9d..4251598b9 100644 --- a/tests/parser/tinvcolonlocation2.nim +++ b/tests/parser/tinvcolonlocation2.nim @@ -1,8 +1,8 @@ discard """ file: "tinvcolonlocation2.nim" line: 11 - column: 1 - errormsg: "':' expected" + column: 8 + errormsg: "expected: ':', but got: 'keyword finally'" """ try: echo "try" diff --git a/tests/parser/tinvcolonlocation3.nim b/tests/parser/tinvcolonlocation3.nim index 2b30b1dbe..a8db658eb 100644 --- a/tests/parser/tinvcolonlocation3.nim +++ b/tests/parser/tinvcolonlocation3.nim @@ -1,8 +1,8 @@ discard """ file: "tinvcolonlocation3.nim" line: 12 - column: 3 - errormsg: "':' expected" + column: 7 + errormsg: "expected: ':', but got: 'echo'" """ try: echo "try" diff --git a/tests/parser/tpostexprblocks.nim b/tests/parser/tpostexprblocks.nim index 3b9c956c2..c27bbf321 100644 --- a/tests/parser/tpostexprblocks.nim +++ b/tests/parser/tpostexprblocks.nim @@ -1,82 +1,82 @@ discard """ nimout: ''' StmtList - Ident ident"foo010" + Ident "foo010" Call - Ident ident"foo020" + Ident "foo020" Call - Ident ident"foo030" - Ident ident"x" + Ident "foo030" + Ident "x" Command - Ident ident"foo040" - Ident ident"x" + Ident "foo040" + Ident "x" Call - Ident ident"foo050" + Ident "foo050" StmtList DiscardStmt Empty Call - Ident ident"foo060" + Ident "foo060" StmtList DiscardStmt Empty Call - Ident ident"foo070" - StrLit test + Ident "foo070" + StrLit "test" StmtList DiscardStmt Empty Call - Ident ident"foo080" - StrLit test + Ident "foo080" + StrLit "test" StmtList DiscardStmt Empty Command - Ident ident"foo090" - StrLit test + Ident "foo090" + StrLit "test" StmtList DiscardStmt Empty Command - Ident ident"foo100" + Ident "foo100" Call - StrLit test + StrLit "test" StmtList DiscardStmt Empty Command - Ident ident"foo101" + Ident "foo101" Call IntLit 10 StmtList DiscardStmt Empty Command - Ident ident"foo110" + Ident "foo110" IntLit 1 Par Infix - Ident ident"+" + Ident "+" IntLit 2 IntLit 3 StmtList DiscardStmt Empty Command - Ident ident"foo120" + Ident "foo120" IntLit 1 Call Par Infix - Ident ident"+" + Ident "+" IntLit 2 IntLit 3 StmtList DiscardStmt Empty Call - Ident ident"foo130" + Ident "foo130" Do Empty Empty @@ -84,7 +84,7 @@ StmtList FormalParams Empty IdentDefs - Ident ident"x" + Ident "x" Empty Empty Empty @@ -93,7 +93,7 @@ StmtList DiscardStmt Empty Call - Ident ident"foo140" + Ident "foo140" Do Empty Empty @@ -101,8 +101,8 @@ StmtList FormalParams Empty IdentDefs - Ident ident"x" - Ident ident"int" + Ident "x" + Ident "int" Empty Empty Empty @@ -110,16 +110,16 @@ StmtList DiscardStmt Empty Call - Ident ident"foo150" + Ident "foo150" Do Empty Empty Empty FormalParams - Ident ident"int" + Ident "int" IdentDefs - Ident ident"x" - Ident ident"int" + Ident "x" + Ident "int" Empty Empty Empty @@ -127,9 +127,9 @@ StmtList DiscardStmt Empty Command - Ident ident"foo160" + Ident "foo160" Call - Ident ident"x" + Ident "x" Do Empty Empty @@ -137,7 +137,7 @@ StmtList FormalParams Empty IdentDefs - Ident ident"y" + Ident "y" Empty Empty Empty @@ -146,7 +146,7 @@ StmtList DiscardStmt Empty Call - Ident ident"foo170" + Ident "foo170" StmtList DiscardStmt Empty @@ -155,7 +155,7 @@ StmtList DiscardStmt Empty Call - Ident ident"foo180" + Ident "foo180" StmtList DiscardStmt Empty @@ -167,9 +167,9 @@ StmtList DiscardStmt Empty Command - Ident ident"foo190" + Ident "foo190" Call - Ident ident"x" + Ident "x" Do Empty Empty @@ -177,7 +177,7 @@ StmtList FormalParams Empty IdentDefs - Ident ident"y" + Ident "y" Empty Empty Empty @@ -190,9 +190,9 @@ StmtList Empty Empty FormalParams - Ident ident"int" + Ident "int" IdentDefs - Ident ident"z" + Ident "z" Empty Empty Empty @@ -205,10 +205,10 @@ StmtList Empty Empty FormalParams - Ident ident"int" + Ident "int" IdentDefs - Ident ident"w" - Ident ident"int" + Ident "w" + Ident "int" Empty Empty Empty @@ -223,10 +223,10 @@ StmtList DiscardStmt Empty Call - Ident ident"foo200" - Ident ident"x" + Ident "foo200" + Ident "x" Call - Ident ident"bar" + Ident "bar" StmtList DiscardStmt Empty @@ -236,53 +236,53 @@ StmtList Empty VarSection IdentDefs - Ident ident"a" + Ident "a" Empty - Ident ident"foo210" + Ident "foo210" VarSection IdentDefs - Ident ident"a" + Ident "a" Empty Call - Ident ident"foo220" + Ident "foo220" VarSection IdentDefs - Ident ident"a" + Ident "a" Empty Call - Ident ident"foo230" - Ident ident"x" + Ident "foo230" + Ident "x" VarSection IdentDefs - Ident ident"a" + Ident "a" Empty Command - Ident ident"foo240" - Ident ident"x" + Ident "foo240" + Ident "x" VarSection IdentDefs - Ident ident"a" + Ident "a" Empty Call - Ident ident"foo250" + Ident "foo250" StmtList DiscardStmt Empty VarSection IdentDefs - Ident ident"a" + Ident "a" Empty Call - Ident ident"foo260" + Ident "foo260" StmtList DiscardStmt Empty VarSection IdentDefs - Ident ident"a" + Ident "a" Empty Call - Ident ident"foo270" + Ident "foo270" StmtList DiscardStmt Empty @@ -292,12 +292,12 @@ StmtList Empty VarSection IdentDefs - Ident ident"a" + Ident "a" Empty Command - Ident ident"foo280" + Ident "foo280" Call - Ident ident"x" + Ident "x" Do Empty Empty @@ -305,7 +305,7 @@ StmtList FormalParams Empty IdentDefs - Ident ident"y" + Ident "y" Empty Empty Empty @@ -318,40 +318,40 @@ StmtList DiscardStmt Empty Asgn - Ident ident"a" - Ident ident"foo290" + Ident "a" + Ident "foo290" Asgn - Ident ident"a" + Ident "a" Call - Ident ident"foo300" + Ident "foo300" Asgn - Ident ident"a" + Ident "a" Call - Ident ident"foo310" - Ident ident"x" + Ident "foo310" + Ident "x" Asgn - Ident ident"a" + Ident "a" Command - Ident ident"foo320" - Ident ident"x" + Ident "foo320" + Ident "x" Asgn - Ident ident"a" + Ident "a" Call - Ident ident"foo330" + Ident "foo330" StmtList DiscardStmt Empty Asgn - Ident ident"a" + Ident "a" Call - Ident ident"foo340" + Ident "foo340" StmtList DiscardStmt Empty Asgn - Ident ident"a" + Ident "a" Call - Ident ident"foo350" + Ident "foo350" StmtList DiscardStmt Empty @@ -360,13 +360,13 @@ StmtList DiscardStmt Empty Asgn - Ident ident"a" + Ident "a" Command - Ident ident"foo360" + Ident "foo360" Call DotExpr - Ident ident"x" - Ident ident"bar" + Ident "x" + Ident "bar" Do Empty Empty @@ -374,7 +374,7 @@ StmtList FormalParams Empty IdentDefs - Ident ident"y" + Ident "y" Empty Empty Empty @@ -388,20 +388,20 @@ StmtList Empty Command DotExpr - Ident ident"foo370" - Ident ident"add" + Ident "foo370" + Ident "add" Call - Ident ident"quote" + Ident "quote" StmtList DiscardStmt Empty Call DotExpr - Ident ident"foo380" - Ident ident"add" + Ident "foo380" + Ident "add" BracketExpr Call - Ident ident"quote" + Ident "quote" StmtList DiscardStmt Empty @@ -540,4 +540,3 @@ dumpTree: foo380.add((quote do: discard )[0]) - diff --git a/tests/parser/tprecedence.nim b/tests/parser/tprecedence.nim index d2c6d0b30..d586f14a3 100644 --- a/tests/parser/tprecedence.nim +++ b/tests/parser/tprecedence.nim @@ -1,6 +1,7 @@ discard """ output: '''holla -true''' +true +defabc 4''' """ # Test top level semicolon works properly: @@ -13,3 +14,8 @@ proc `\*` (x, y: int): int = result = x * y echo 5 \+ 1 \* 9 == 6*9 +proc foo[S, T](x: S, y: T): T = x & y + +proc bar[T](x: T): T = x + +echo "def".foo[:string, string]("abc"), " ", 4.bar[:int] diff --git a/tests/parser/ttypeclasses.nim b/tests/parser/ttypeclasses.nim new file mode 100644 index 000000000..06146dcb6 --- /dev/null +++ b/tests/parser/ttypeclasses.nim @@ -0,0 +1,46 @@ +type + R = ref + V = var + D = distinct + P = ptr + T = type + S = static + OBJ = object + TPL = tuple + SEQ = seq + +var i: int +var x: ref int +var y: distinct int +var z: ptr int +const C = @[1, 2, 3] + +static: + assert x is ref + assert y is distinct + assert z is ptr + assert C is static + assert C[1] is static[int] + assert C[0] is static[SomeInteger] + assert C isnot static[string] + assert C is SEQ|OBJ + assert C isnot OBJ|TPL + assert int is int + assert int is T + assert int is SomeInteger + assert seq[int] is type + assert seq[int] is type[seq] + assert seq[int] isnot type[seq[float]] + assert i isnot type[int] + assert type(i) is type[int] + assert x isnot T + assert y isnot S + assert z isnot enum + assert x isnot object + assert y isnot tuple + assert z isnot seq + + # XXX: These cases don't work properly at the moment: + # assert type[int] isnot int + # assert type(int) isnot int + diff --git a/tests/parser/ttypemodifiers.nim b/tests/parser/ttypemodifiers.nim new file mode 100644 index 000000000..2c322b44b --- /dev/null +++ b/tests/parser/ttypemodifiers.nim @@ -0,0 +1,527 @@ +discard """ +nimout: ''' +StmtList + TypeSection + TypeDef + Ident "BarePtr" + Empty + PtrTy + TypeDef + Ident "GenericPtr" + Empty + PtrTy + Bracket + Ident "int" + TypeDef + Ident "PrefixPtr" + Empty + PtrTy + Ident "int" + TypeDef + Ident "PtrTuple" + Empty + PtrTy + Par + Ident "int" + Ident "string" + TypeDef + Ident "BareRef" + Empty + RefTy + TypeDef + Ident "GenericRef" + Empty + RefTy + Bracket + Ident "int" + TypeDef + Ident "RefTupleCl" + Empty + RefTy + TupleTy + TypeDef + Ident "RefTupleType" + Empty + RefTy + Par + Ident "int" + Ident "string" + TypeDef + Ident "RefTupleVars" + Empty + RefTy + Par + Ident "a" + Ident "b" + TypeDef + Ident "BareStatic" + Empty + Ident "static" + TypeDef + Ident "GenericStatic" + Empty + BracketExpr + Ident "static" + Ident "int" + TypeDef + Ident "PrefixStatic" + Empty + Command + Ident "static" + Ident "int" + TypeDef + Ident "StaticTupleCl" + Empty + Command + Ident "static" + TupleClassTy + TypeDef + Ident "StaticTuple" + Empty + Command + Ident "static" + Par + Ident "int" + Ident "string" + TypeDef + Ident "BareType" + Empty + Ident "type" + TypeDef + Ident "GenericType" + Empty + BracketExpr + Ident "type" + Ident "float" + TypeDef + Ident "TypeTupleGen" + Empty + BracketExpr + Ident "type" + TupleClassTy + TypeDef + Ident "TypeTupleCl" + Empty + Command + Ident "type" + TupleClassTy + TypeDef + Ident "TypeInstance" + Empty + Command + Ident "type" + BracketExpr + Ident "Foo" + RefTy + TypeDef + Ident "bareTypeDesc" + Empty + Ident "typedesc" + TypeDef + Ident "TypeOfVar" + Empty + Call + Ident "type" + Ident "a" + TypeDef + Ident "TypeOfVarAlt" + Empty + Command + Ident "type" + Par + Ident "a" + TypeDef + Ident "TypeOfTuple1" + Empty + Call + Ident "type" + Ident "a" + TypeDef + Ident "TypeOfTuple2" + Empty + Call + Ident "type" + Ident "a" + Ident "b" + TypeDef + Ident "TypeOfTuple1A" + Empty + Command + Ident "type" + TupleConstr + Ident "a" + TypeDef + Ident "TypeOfTuple2A" + Empty + Command + Ident "type" + Par + Ident "a" + Ident "b" + TypeDef + Ident "TypeTuple" + Empty + Command + Ident "type" + Par + Ident "int" + Ident "string" + TypeDef + Ident "GenericTypedesc" + Empty + BracketExpr + Ident "typedesc" + Ident "int" + TypeDef + Ident "T" + Empty + Ident "type" + ProcDef + Ident "foo" + Empty + Empty + FormalParams + Ident "type" + IdentDefs + Ident "bareType" + Ident "type" + Empty + IdentDefs + Ident "genType" + BracketExpr + Ident "type" + Ident "int" + Empty + IdentDefs + Ident "typeInt" + Command + Ident "type" + Ident "int" + Empty + IdentDefs + Ident "typeIntAlt" + Call + Ident "type" + Ident "int" + Empty + IdentDefs + Ident "typeOfVar" + Call + Ident "type" + Ident "a" + Empty + IdentDefs + Ident "typeDotType" + DotExpr + Ident "foo" + Ident "type" + Empty + IdentDefs + Ident "typeTupleCl" + Command + Ident "type" + TupleClassTy + Empty + IdentDefs + Ident "bareStatic" + Ident "static" + Empty + IdentDefs + Ident "genStatic" + BracketExpr + Ident "static" + Ident "int" + Empty + IdentDefs + Ident "staticInt" + Command + Ident "static" + Ident "int" + Empty + IdentDefs + Ident "staticVal1" + Command + Ident "static" + IntLit 10 + Empty + IdentDefs + Ident "staticVal2" + Call + Ident "static" + StrLit "str" + Empty + IdentDefs + Ident "staticVal3" + Command + Ident "static" + StrLit "str" + Empty + IdentDefs + Ident "staticVal4" + CallStrLit + Ident "static" + RStrLit "str" + Empty + IdentDefs + Ident "staticDotVal" + DotExpr + IntLit 10 + Ident "static" + Empty + IdentDefs + Ident "bareRef" + RefTy + Empty + IdentDefs + Ident "refTuple1" + RefTy + Par + Ident "int" + Empty + IdentDefs + Ident "refTuple1A" + RefTy + TupleConstr + Ident "int" + Empty + IdentDefs + Ident "refTuple2" + RefTy + Par + Ident "int" + Ident "string" + Empty + IdentDefs + Ident "genRef" + RefTy + Bracket + Ident "int" + Empty + IdentDefs + Ident "refInt" + RefTy + Ident "int" + Empty + IdentDefs + Ident "refCall" + RefTy + Par + Ident "a" + Empty + IdentDefs + Ident "macroCall1" + Command + Ident "foo" + Ident "bar" + Empty + IdentDefs + Ident "macroCall2" + Call + Ident "foo" + Ident "bar" + Empty + IdentDefs + Ident "macroCall3" + Call + DotExpr + Ident "foo" + Ident "bar" + Ident "baz" + Empty + IdentDefs + Ident "macroCall4" + Call + BracketExpr + Ident "foo" + Ident "bar" + Ident "baz" + Empty + IdentDefs + Ident "macroCall5" + Command + Ident "foo" + Command + Ident "bar" + Ident "baz" + IntLit 10 + Empty + Empty + StmtList + Asgn + Ident "staticTen" + Command + Ident "static" + IntLit 10 + Asgn + Ident "staticA" + Call + Ident "static" + Ident "a" + Asgn + Ident "staticCall" + Command + Ident "static" + Call + Ident "foo" + IntLit 1 + Asgn + Ident "staticStrCall" + Command + Ident "static" + CallStrLit + Ident "foo" + RStrLit "x" + Asgn + Ident "staticChainCall" + Command + Ident "static" + Command + Ident "foo" + Ident "bar" + Asgn + Ident "typeTen" + Command + Ident "type" + IntLit 10 + Asgn + Ident "typeA" + Call + Ident "type" + Ident "a" + Asgn + Ident "typeCall" + Command + Ident "type" + Call + Ident "foo" + IntLit 1 + Asgn + Ident "typeStrCall" + Command + Ident "type" + CallStrLit + Ident "foo" + RStrLit "x" + Asgn + Ident "typeChainCall" + Command + Ident "type" + Command + Ident "foo" + Ident "bar" + Asgn + Ident "normalChainCall" + Command + Ident "foo" + Command + Ident "bar" + Ident "baz" + Asgn + Ident "normalTupleCall2" + Call + Ident "foo" + Ident "a" + Ident "b" + StaticStmt + StmtList + Ident "singleStaticStmt" + StaticStmt + StmtList + Ident "staticStmtList1" + Ident "staticStmtList2" +''' +""" + +import macros + +dumpTree: + type + BarePtr = ptr + GenericPtr = ptr[int] + PrefixPtr = ptr int + PtrTuple = ptr (int, string) + BareRef = ref + GenericRef = ref[int] + RefTupleCl = ref tuple + RefTupleType = ref (int, string) + RefTupleVars = ref (a, b) + BareStatic = static # Used to be Error: invalid indentation + GenericStatic = static[int] + PrefixStatic = static int + StaticTupleCl = static tuple + StaticTuple = static (int, string) + BareType = type + GenericType = type[float] + TypeTupleGen = type[tuple] + TypeTupleCl = type tuple # Used to be Error: invalid indentation + TypeInstance = type Foo[ref] + bareTypeDesc = typedesc + TypeOfVar = type(a) + TypeOfVarAlt = type (a) # Used to be Error: invalid indentation + TypeOfTuple1 = type(a,) + TypeOfTuple2 = type(a,b) + TypeOfTuple1A = type (a,) # Used to be Error: invalid indentation + TypeOfTuple2A = type (a,b) # Used to be Error: invalid indentation + TypeTuple = type (int, string) # Used to be Error: invalid indentation + GenericTypedesc = typedesc[int] + T = type + + proc foo( + bareType : type, + genType : type[int], + typeInt : type int, + typeIntAlt : type(int), + typeOfVar : type(a), + typeDotType : foo.type, + typeTupleCl : type tuple, # Used to be Error: ')' expected + bareStatic : static, # Used to be Error: expression expected, but found ',' + genStatic : static[int], + staticInt : static int, + staticVal1 : static 10, + staticVal2 : static("str"), + staticVal3 : static "str", + staticVal4 : static"str", # Used to be Error: expression expected, but found 'str' + staticDotVal : 10.static, + bareRef : ref, + refTuple1 : ref (int), + refTuple1A : ref (int,), + refTuple2 : ref (int,string), + genRef : ref[int], + refInt : ref int, + refCall : ref(a), + macroCall1 : foo bar, + macroCall2 : foo(bar), + macroCall3 : foo.bar(baz), + macroCall4 : foo[bar](baz), + macroCall5 : foo bar baz = 10 + ): type = + staticTen = static 10 + staticA = static(a) + # staticAspace = static (a) # With newTypedesc: Error: invalid indentation + # staticAtuple = static (a,) # With newTypedesc: Error: invalid indentation + # staticTuple = static (a,b) # With newTypedesc: Error: invalid indentation + # staticTypeTuple = static (int,string) # With newTypedesc: Error: invalid indentation + staticCall = static foo(1) + staticStrCall = static foo"x" + staticChainCall = static foo bar + + typeTen = type 10 + typeA = type(a) + # typeAspace = type (a) # Error: invalid indentation + # typeAtuple = type (a,) # Error: invalid indentation + # typeTuple = type (a,b) # Error: invalid indentation + # typeTypeTuple = type (int,string) # Error: invalid indentation + typeCall = type foo(1) + typeStrCall = type foo"x" + typeChainCall = type foo bar + + normalChainCall = foo bar baz + # normalTupleCall1 = foo(a,) # Error: invalid indentation + normalTupleCall2 = foo(a,b) + # normalTupleCall3 = foo (a,b) # Error: invalid indentation + + static: singleStaticStmt + static: + staticStmtList1 + staticStmtList2 + diff --git a/tests/parser/twhen_in_enum.nim b/tests/parser/twhen_in_enum.nim index d4a3ea56a..3890b686e 100644 --- a/tests/parser/twhen_in_enum.nim +++ b/tests/parser/twhen_in_enum.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "identifier expected, but found 'keyword when'" + errormsg: "identifier expected, but got 'keyword when'" """ # bug #2123 diff --git a/tests/parser/twrongcmdsyntax.nim b/tests/parser/twrongcmdsyntax.nim index affe72c34..c2962bed4 100644 --- a/tests/parser/twrongcmdsyntax.nim +++ b/tests/parser/twrongcmdsyntax.nim @@ -1,5 +1,5 @@ discard """ - errormsg: '''identifier expected, but found 'echo 4''' + errormsg: '''in expression '4 2': identifier expected, but found '4''' line: 6 """ diff --git a/tests/pragmas/custom_pragma.nim b/tests/pragmas/custom_pragma.nim index 9e8f51deb..d2fc969d0 100644 --- a/tests/pragmas/custom_pragma.nim +++ b/tests/pragmas/custom_pragma.nim @@ -2,4 +2,4 @@ template serializationKey*(s: string) {.pragma.} template defaultValue*(V: typed) {.pragma.} -template alternativeKey*(s: string = nil, V: typed) {.pragma.} \ No newline at end of file +template alternativeKey*(s: string = "", V: typed) {.pragma.} diff --git a/tests/pragmas/mpushexperimental.nim b/tests/pragmas/mpushexperimental.nim new file mode 100644 index 000000000..569861c1d --- /dev/null +++ b/tests/pragmas/mpushexperimental.nim @@ -0,0 +1,30 @@ + +import macros + +macro enumerate(x: ForLoopStmt): untyped = + expectKind x, nnkForStmt + # we strip off the first for loop variable and use + # it as an integer counter: + result = newStmtList() + result.add newVarStmt(x[0], newLit(0)) + var body = x[^1] + if body.kind != nnkStmtList: + body = newTree(nnkStmtList, body) + body.add newCall(bindSym"inc", x[0]) + var newFor = newTree(nnkForStmt) + for i in 1..x.len-3: + newFor.add x[i] + # transform enumerate(X) to 'X' + newFor.add x[^2][1] + newFor.add body + result.add newFor + +proc main*[T](x: T) = + {.push experimental: "forLoopMacros".} + + for a, b in enumerate(items([1, 2, 3])): + echo a, " ", b + + for a2, b2 in enumerate([1, 2, 3, 5]): + echo a2, " ", b2 + {.pop.} diff --git a/tests/pragmas/t8741.nim b/tests/pragmas/t8741.nim new file mode 100644 index 000000000..41f2f9e8a --- /dev/null +++ b/tests/pragmas/t8741.nim @@ -0,0 +1,10 @@ +discard """ + line: 9 + errormsg: "cannot attach a custom pragma to 'a'" +""" + +for a {.gensym, inject.} in @[1,2,3]: + discard + +for a {.foobar.} in @[1,2,3]: + discard diff --git a/tests/pragmas/tcustom_pragma.nim b/tests/pragmas/tcustom_pragma.nim index 415ae6a32..d7b199a22 100644 --- a/tests/pragmas/tcustom_pragma.nim +++ b/tests/pragmas/tcustom_pragma.nim @@ -1,12 +1,12 @@ import macros - + block: template myAttr() {.pragma.} proc myProc():int {.myAttr.} = 2 - const myAttrIdx = myProc.hasCustomPragma(myAttr) - static: - assert(myAttrIdx) + const hasMyAttr = myProc.hasCustomPragma(myAttr) + static: + assert(hasMyAttr) block: template myAttr(a: string) {.pragma.} @@ -14,14 +14,14 @@ block: type MyObj = object myField1, myField2 {.myAttr: "hi".}: int var o: MyObj - static: + static: assert o.myField2.hasCustomPragma(myAttr) assert(not o.myField1.hasCustomPragma(myAttr)) -import custom_pragma +import custom_pragma block: # A bit more advanced case - type - Subfield = object + type + Subfield {.defaultValue: "catman".} = object c {.serializationKey: "cc".}: float MySerializable = object @@ -29,10 +29,9 @@ block: # A bit more advanced case b {.custom_pragma.defaultValue"hello".} : int field: Subfield d {.alternativeKey("df", 5).}: float - e {.alternativeKey(V = 5).}: seq[bool] - + e {.alternativeKey(V = 5).}: seq[bool] - proc myproc(x: int, s: string) {.alternativeKey(V = 5), serializationKey"myprocSS".} = + proc myproc(x: int, s: string) {.alternativeKey(V = 5), serializationKey"myprocSS".} = echo x, s @@ -51,3 +50,127 @@ block: # A bit more advanced case static: assert(procSerKey == "myprocSS") static: assert(hasCustomPragma(myproc, alternativeKey)) + + const hasFieldCustomPragma = s.field.hasCustomPragma(defaultValue) + static: assert(hasFieldCustomPragma == false) + + # pragma on an object + static: + assert Subfield.hasCustomPragma(defaultValue) + assert(Subfield.getCustomPragmaVal(defaultValue) == "catman") + + assert hasCustomPragma(type(s.field), defaultValue) + +block: # ref types + type + Node = object of RootObj + left {.serializationKey:"l".}, right {.serializationKey:"r".}: NodeRef + NodeRef = ref Node + NodePtr = ptr Node + + SpecialNodeRef = ref object of NodeRef + data {.defaultValue"none".}: string + + MyFile {.defaultValue: "closed".} = ref object + path {.defaultValue: "invalid".}: string + + TypeWithoutPragma = object + + var s = NodeRef() + + const + leftSerKey = getCustomPragmaVal(s.left, serializationKey) + rightSerKey = getCustomPragmaVal(s.right, serializationKey) + static: + assert leftSerKey == "l" + assert rightSerKey == "r" + + var specS = SpecialNodeRef() + + const + dataDefVal = hasCustomPragma(specS.data, defaultValue) + specLeftSerKey = hasCustomPragma(specS.left, serializationKey) + static: + assert dataDefVal == true + assert specLeftSerKey == true + + var ptrS = NodePtr(nil) + const + ptrRightSerKey = getCustomPragmaVal(ptrS.right, serializationKey) + static: + assert ptrRightSerKey == "r" + + var f = MyFile() + const + fileDefVal = f.getCustomPragmaVal(defaultValue) + filePathDefVal = f.path.getCustomPragmaVal(defaultValue) + static: + assert fileDefVal == "closed" + assert filePathDefVal == "invalid" + + static: + assert TypeWithoutPragma.hasCustomPragma(defaultValue) == false + +block: + type + VariantKind = enum + variInt, + variFloat + variString + variNestedCase + Variant = object + case kind: VariantKind + of variInt: integer {.serializationKey: "int".}: BiggestInt + of variFloat: floatp: BiggestFloat + of variString: str {.serializationKey: "string".}: string + of variNestedCase: + case nestedKind: VariantKind + of variInt..variNestedCase: nestedItem {.defaultValue: "Nimmers of the world, unite!".}: int + + let vari = Variant(kind: variInt) + + const + hasIntSerKey = vari.integer.hasCustomPragma(serializationKey) + strSerKey = vari.str.getCustomPragmaVal(serializationKey) + nestedItemDefVal = vari.nestedItem.getCustomPragmaVal(defaultValue) + + static: + assert hasIntSerKey + assert strSerKey == "string" + assert nestedItemDefVal == "Nimmers of the world, unite!" + +block: + template simpleAttr {.pragma.} + + type Annotated {.simpleAttr.} = object + + proc generic_proc[T]() = + assert Annotated.hasCustomPragma(simpleAttr) + + +#-------------------------------------------------------------------------- +# Pragma on proc type + +let a: proc(x: int) {.defaultValue(5).} = nil +static: + doAssert hasCustomPragma(a.type, defaultValue) + +# bug #8371 +template thingy {.pragma.} + +type + Cardinal = enum + north, east, south, west + Something = object + a: float32 + case cardinal: Cardinal + of north: + b {.thingy.}: int + of east: + c: int + of south: discard + else: discard + +var foo: Something +foo.cardinal = north +doAssert foo.b.hasCustomPragma(thingy) == true diff --git a/tests/pragmas/tnoreturn.nim b/tests/pragmas/tnoreturn.nim index 4d00c6034..bb59b1c71 100644 --- a/tests/pragmas/tnoreturn.nim +++ b/tests/pragmas/tnoreturn.nim @@ -2,13 +2,16 @@ discard """ ccodeCheck: "\\i @'__attribute__((noreturn))' .*" """ -proc noret1*(i: int) {.noreturn.} = +proc noret1*(i: int) {.noreturn.} = echo i -proc noret2*(i: int): void {.noreturn.} = +proc noret2*(i: int): void {.noreturn.} = echo i +noret1(1) +noret2(2) + var p {.used.}: proc(i: int): int doAssert(not compiles( p = proc(i: int): int {.noreturn.} = i # noreturn lambda returns int diff --git a/tests/pragmas/tpushexperimental.nim b/tests/pragmas/tpushexperimental.nim new file mode 100644 index 000000000..301419f60 --- /dev/null +++ b/tests/pragmas/tpushexperimental.nim @@ -0,0 +1,13 @@ +discard """ + output: '''0 1 +1 2 +2 3 +0 1 +1 2 +2 3 +3 5''' +""" + +import mpushexperimental + +main(12) diff --git a/tests/pragmas/treorder.nim b/tests/pragmas/treorder.nim index 6a6bbff4d..1006af527 100644 --- a/tests/pragmas/treorder.nim +++ b/tests/pragmas/treorder.nim @@ -71,5 +71,4 @@ macro make(arg: untyped): untyped = proc first(i: int): void = make(second) -static: - var ss: string = "" \ No newline at end of file +var ss {.compileTime.}: string = "" \ No newline at end of file diff --git a/tests/proc/t8357.nim b/tests/proc/t8357.nim new file mode 100644 index 000000000..350ebe356 --- /dev/null +++ b/tests/proc/t8357.nim @@ -0,0 +1,10 @@ +discard """ + output: "Hello" +""" + +type + T = ref int + +let r = new(string) +r[] = "Hello" +echo r[] diff --git a/tests/proc/t8683.nim b/tests/proc/t8683.nim new file mode 100644 index 000000000..8c7e7af08 --- /dev/null +++ b/tests/proc/t8683.nim @@ -0,0 +1,11 @@ +discard """ + output: "1" +""" + +proc foo[T](bar: proc (x, y: T): int = system.cmp, baz: int) = + echo "1" + +proc foo[T](bar: proc (x, y: T): int = system.cmp) = + echo "2" + +foo[int](baz = 5) diff --git a/tests/proc/tprocredef.nim b/tests/proc/tprocredef.nim index 86ed92b62..4ec771510 100644 --- a/tests/proc/tprocredef.nim +++ b/tests/proc/tprocredef.nim @@ -4,6 +4,6 @@ discard """ errormsg: "redefinition of \'foo\'" """ -proc foo(a: int, b: string) = nil -proc foo(a: int, b: string) = nil +proc foo(a: int, b: string) = discard +proc foo(a: int, b: string) = discard diff --git a/tests/range/tsubrange.nim b/tests/range/tsubrange.nim index 6f88c5a22..914e7c6e7 100644 --- a/tests/range/tsubrange.nim +++ b/tests/range/tsubrange.nim @@ -7,7 +7,7 @@ type TRange = range[0..40] proc p(r: TRange) = - nil + discard var r: TRange diff --git a/tests/rodfiles/deadg.nim b/tests/rodfiles/deadg.nim index 587608e14..0aee59bb8 100644 --- a/tests/rodfiles/deadg.nim +++ b/tests/rodfiles/deadg.nim @@ -1,6 +1,3 @@ - -{.deadCodeElim: on.} - proc p1*(x, y: int): int = result = x + y diff --git a/tests/sets/t2669.nim b/tests/sets/t2669.nim new file mode 100644 index 000000000..6b8eb0f54 --- /dev/null +++ b/tests/sets/t2669.nim @@ -0,0 +1,6 @@ +discard """ +line: 6 +errormsg: "cannot convert 6 to range 1..5(int8)" +""" + +var c: set[range[1i8..5i8]] = {1i8, 2i8, 6i8} diff --git a/tests/sets/tsetpop.nim b/tests/sets/tsetpop.nim new file mode 100644 index 000000000..c37bda57d --- /dev/null +++ b/tests/sets/tsetpop.nim @@ -0,0 +1,22 @@ +discard """ + targets: "c c++ js" + output: '''1000 +0 +set is empty +''' +""" + +import sets + +var a = initSet[int]() +for i in 1..1000: + a.incl(i) +echo len(a) +for i in 1..1000: + discard a.pop() +echo len(a) + +try: + echo a.pop() +except KeyError as e: + echo e.msg \ No newline at end of file diff --git a/tests/sets/tsets.nim b/tests/sets/tsets.nim index 53a955af8..13a5f54e6 100644 --- a/tests/sets/tsets.nim +++ b/tests/sets/tsets.nim @@ -202,3 +202,15 @@ var #import compiler.msgs echo warnUninit in gNotes + +# 7555 +doAssert {-1.int8, -2, -2}.card == 2 +doAssert {1, 2, 2, 3..5, 4..6}.card == 6 + +type Foo = enum + Foo1 = 0 + Foo2 = 1 + Foo3 = 3 + +let x = { Foo1, Foo2 } +# bug #8425 diff --git a/tests/statictypes/tcryptodigest.nim b/tests/statictypes/tcryptodigest.nim new file mode 100644 index 000000000..c78a4188a --- /dev/null +++ b/tests/statictypes/tcryptodigest.nim @@ -0,0 +1,44 @@ +discard """ + output: "Digest[128]\nDigest[256]" +""" + +import typetraits + +type + Digest[bits: static[int]] = object + data: array[bits div 8, byte] + + ContextKind = enum + A, B, C + + HashingContext[bits: static[int], kind: static[ContextKind]] = object + ctx: array[bits div 8, byte] + + Hash128 = HashingContext[128, A] + Hash256 = HashingContext[256, B] + + HMAC[HashType] = object + h: HashType + +proc init(c: var HashingContext) = discard +proc update(c: var HashingContext, data: ptr byte, dataLen: uint) = discard +proc finish(c: var HashingContext): Digest[c.bits] = discard + +proc digest(T: typedesc, data: ptr byte, dataLen: uint): Digest[T.bits] = + mixin init, update, finish + + var ctx: T + ctx.init() + ctx.update(data, dataLen) + result = ctx.finish() + +var h = Hash128.digest(nil, 0) +echo h.type.name + +proc finish(hmac: var HMAC): Digest[HMAC.HashType.bits] = + discard + +var hm: HMAC[Hash256] +var d = hm.finish +echo d.type.name + diff --git a/tests/statictypes/tstackmatrix.nim b/tests/statictypes/tstackmatrix.nim new file mode 100644 index 000000000..2509d21f8 --- /dev/null +++ b/tests/statictypes/tstackmatrix.nim @@ -0,0 +1,29 @@ +discard """ + output: "(M: 3, N: 3, fp: ...)" +""" + +# bug #6843 + +type + OrderType = enum colMajor, rowMajor + Matrix[A] = object + M, N: int + fp: ptr A # float pointer + DoubleArray64[M, N: static[int]] = array[M, array[N, float64]] + + +proc stackMatrix[M, N: static[int]](a: var DoubleArray64[M, N], order = colMajor): Matrix[float64] = + Matrix[float64]( + fp: addr a[0][0], + M: (if order == colMajor: N else: M), + N: (if order == colMajor: M else: N) + ) + +var + data = [ + [1'f64, 2, 3], + [4'f64, 5, 6], + [7'f64, 8, 9] + ] + m = stackMatrix(data) +echo m \ No newline at end of file diff --git a/tests/statictypes/tstaticimportcpp.nim b/tests/statictypes/tstaticimportcpp.nim new file mode 100644 index 000000000..125dcb98d --- /dev/null +++ b/tests/statictypes/tstaticimportcpp.nim @@ -0,0 +1,63 @@ +discard """ +targets: "cpp" +output: "[0, 0, 10, 0]\n5\n1.2\n15\ntest\n[0, 0, 20, 0]\n4" +""" + +{.emit: """/*TYPESECTION*/ + +template <int N, class T> +struct GenericIntType { + T data[N]; +}; + +template <class T> +struct GenericTType { + T field; +}; + +struct SimpleStruct { + int field; +}; + + +""" .} + +type + GenericIntType {.importcpp: "GenericIntType<'0, '1>".} [N: static[int]; T] = object + data: array[N, T] + + GenericIntTypeAlt {.importcpp: "GenericIntType".} [N: static[int]; T] = object + + GenericTType {.importcpp: "GenericTType<'0>".} [T] = object + field: T + + GenInt4 = GenericIntType[4, int] + + SimpleStruct {.importcpp: "SimpleStruct"} = object + field: int + +var + a = GenInt4() + b = SimpleStruct() + c = GenericTType[float]() + d = SimpleStruct(field: 15) + e = GenericTType[string](field: "test") + f = GenericIntTypeAlt[4, int8]() + +a.data[2] = 10 +b.field = 5 +c.field = 1.2 + +echo a.data +echo b.field +echo c.field +echo d.field +echo e.field + +proc plus(a, b: GenInt4): GenInt4 = + for i in 0 ..< result.data.len: + result.data[i] = a.data[i] + b.data[i] + +echo plus(a, a).data + +echo sizeof(f) diff --git a/tests/statictypes/tstatictypes.nim b/tests/statictypes/tstatictypes.nim new file mode 100644 index 000000000..2a3b7332d --- /dev/null +++ b/tests/statictypes/tstatictypes.nim @@ -0,0 +1,118 @@ +discard """ +nimout: ''' +staticAlialProc instantiated with 358 +staticAlialProc instantiated with 368 +''' +output: ''' +16 +16 +b is 2 times a +17 +''' +""" + +import macros + +template ok(x) = assert(x) +template no(x) = assert(not x) + +template accept(x) = + static: assert(compiles(x)) + +template reject(x) = + static: assert(not compiles(x)) + +proc plus(a, b: int): int = a + b + +template isStatic(x: static): bool = true +template isStatic(x: auto): bool = false + +var v = 1 + +when true: + # test that `isStatic` works as expected + const C = 2 + + static: + ok C.isStatic + ok isStatic(plus(1, 2)) + ok plus(C, 2).isStatic + + no isStatic(v) + no plus(1, v).isStatic + +when true: + # test that proc instantiation works as expected + type + StaticTypeAlias = static[int] + + proc staticAliasProc(a: StaticTypeAlias, + b: static[int], + c: static int) = + static: + assert a.isStatic and b.isStatic and c.isStatic + assert isStatic(a + plus(b, c)) + echo "staticAlialProc instantiated with ", a, b, c + + when b mod a == 0: + echo "b is ", b div a, " times a" + + echo a + b + c + + staticAliasProc 1+2, 5, 8 + staticAliasProc 3, 2+3, 9-1 + staticAliasProc 3, 3+3, 4+4 + +when true: + # test static coercions. normal cases that should work: + accept: + var s1 = static[int] plus(1, 2) + var s2 = static(plus(1,2)) + var s3 = static plus(1,2) + var s4 = static[SomeInteger](1 + 2) + + # the sub-script operator can be used only with types: + reject: + var just_static3 = static[plus(1,2)] + + # static coercion takes into account the type: + reject: + var x = static[string](plus(1, 2)) + reject: + var x = static[string] plus(1, 2) + reject: + var x = static[SomeFloat] plus(3, 4) + + # you cannot coerce a run-time variable + reject: + var x = static(v) + +when true: + type + ArrayWrapper1[S: static int] = object + data: array[S + 1, int] + + ArrayWrapper2[S: static[int]] = object + data: array[S.plus(2), int] + + ArrayWrapper3[S: static[(int, string)]] = object + data: array[S[0], int] + + var aw1: ArrayWrapper1[5] + var aw2: ArrayWrapper2[5] + var aw3: ArrayWrapper3[(10, "str")] + + static: + assert aw1.data.high == 5 + assert aw2.data.high == 6 + assert aw3.data.high == 9 + +# #6077 +block: + type + Backend = enum + Cpu + + Tensor[B: static[Backend]; T] = object + + BackProp[B: static[Backend],T] = proc (gradient: Tensor[B,T]): Tensor[B,T] diff --git a/tests/stdlib/nre/captures.nim b/tests/stdlib/nre/captures.nim index 19c344a8d..31de71154 100644 --- a/tests/stdlib/nre/captures.nim +++ b/tests/stdlib/nre/captures.nim @@ -27,7 +27,7 @@ suite "captures": let ex2 = "foo".find(re("(?<foo>foo)(?<bar>bar)?")) check(ex2.captures["foo"] == "foo") - check(ex2.captures["bar"] == nil) + check(ex2.captures["bar"] == "") test "named capture bounds": let ex1 = "foo".find(re("(?<foo>foo)(?<bar>bar)?")) @@ -41,7 +41,7 @@ suite "captures": test "named capture table": let ex1 = "foo".find(re("(?<foo>foo)(?<bar>bar)?")) - check(ex1.captures.toTable == {"foo" : "foo", "bar" : nil}.toTable()) + check(ex1.captures.toTable == {"foo" : "foo", "bar" : ""}.toTable()) check(ex1.captureBounds.toTable == {"foo" : some(0..2), "bar" : none(Slice[int])}.toTable()) check(ex1.captures.toTable("") == {"foo" : "foo", "bar" : ""}.toTable()) @@ -50,7 +50,7 @@ suite "captures": test "capture sequence": let ex1 = "foo".find(re("(?<foo>foo)(?<bar>bar)?")) - check(ex1.captures.toSeq == @["foo", nil]) + check(ex1.captures.toSeq == @["foo", ""]) check(ex1.captureBounds.toSeq == @[some(0..2), none(Slice[int])]) check(ex1.captures.toSeq("") == @["foo", ""]) diff --git a/tests/stdlib/nre/replace.nim b/tests/stdlib/nre/replace.nim index 516fd4328..b762271a2 100644 --- a/tests/stdlib/nre/replace.nim +++ b/tests/stdlib/nre/replace.nim @@ -16,5 +16,5 @@ suite "replace": check("123".replace(re"(?<foo>\d)(\d)", "${foo}$#$#") == "1123") test "replacing missing captures should throw instead of segfaulting": - expect ValueError: discard "ab".replace(re"(a)|(b)", "$1$2") - expect ValueError: discard "b".replace(re"(a)?(b)", "$1$2") + discard "ab".replace(re"(a)|(b)", "$1$2") + discard "b".replace(re"(a)?(b)", "$1$2") diff --git a/tests/stdlib/talgorithm.nim b/tests/stdlib/talgorithm.nim deleted file mode 100644 index f200e54c5..000000000 --- a/tests/stdlib/talgorithm.nim +++ /dev/null @@ -1,11 +0,0 @@ -import algorithm - -doAssert product[int](newSeq[seq[int]]()) == newSeq[seq[int]](), "empty input" -doAssert product[int](@[newSeq[int](), @[], @[]]) == newSeq[seq[int]](), "bit more empty input" -doAssert product(@[@[1,2]]) == @[@[1,2]], "a simple case of one element" -doAssert product(@[@[1,2], @[3,4]]) == @[@[2,4],@[1,4],@[2,3],@[1,3]], "two elements" -doAssert product(@[@[1,2], @[3,4], @[5,6]]) == @[@[2,4,6],@[1,4,6],@[2,3,6],@[1,3,6], @[2,4,5],@[1,4,5],@[2,3,5],@[1,3,5]], "three elements" -doAssert product(@[@[1,2], @[]]) == newSeq[seq[int]](), "two elements, but one empty" -doAssert lowerBound([1,2,4], 3, system.cmp[int]) == 2 -doAssert lowerBound([1,2,2,3], 4, system.cmp[int]) == 4 -doAssert lowerBound([1,2,3,10], 11) == 4 diff --git a/tests/stdlib/tencoding.nim b/tests/stdlib/tencoding.nim new file mode 100644 index 000000000..d6ff7ab32 --- /dev/null +++ b/tests/stdlib/tencoding.nim @@ -0,0 +1,21 @@ +discard """ + output: '''OK''' +""" + +#bug #8468 + +import encodings, strutils + +when defined(windows): + var utf16to8 = open(destEncoding = "utf-16", srcEncoding = "utf-8") + var s = "some string" + var c = utf16to8.convert(s) + + var z = newStringOfCap(s.len * 2) + for x in s: + z.add x + z.add chr(0) + + doAssert z == c + +echo "OK" diff --git a/tests/stdlib/tgetfileinfo.nim b/tests/stdlib/tgetfileinfo.nim index 019c2eb7f..e0b73da0c 100644 --- a/tests/stdlib/tgetfileinfo.nim +++ b/tests/stdlib/tgetfileinfo.nim @@ -77,8 +77,8 @@ proc testGetFileInfo = # Case 6 and 8 block: let - testFile: TFile = nil - testHandle = TFileHandle(-1) + testFile: File = nil + testHandle = FileHandle(-1) try: discard getFileInfo(testFile) echo("Handle : Invalid File : Failure") diff --git a/tests/stdlib/tjsonmacro.nim b/tests/stdlib/tjsonmacro.nim index a69b03426..bf0bb3ea7 100644 --- a/tests/stdlib/tjsonmacro.nim +++ b/tests/stdlib/tjsonmacro.nim @@ -40,7 +40,7 @@ when isMainModule: test: 18827361, test2: "hello world", test3: true, - testNil: nil + testNil: "nil" ) let node = %x @@ -53,7 +53,7 @@ when isMainModule: doAssert y.test == 18827361 doAssert y.test2 == "hello world" doAssert y.test3 - doAssert y.testNil.isNil + doAssert y.testNil == "nil" # Test for custom object variants (without an enum) and with an else branch. block: @@ -315,6 +315,36 @@ when isMainModule: doAssert noYearDeser.year.isNone doAssert noYearDeser.engine.name == "V8" + # Issue #7433 + type + Obj2 = object + n1: int + n2: Option[string] + n3: bool + + var j = %*[ { "n1": 4, "n2": "ABC", "n3": true }, + { "n1": 1, "n3": false }, + { "n1": 1, "n2": "XYZ", "n3": false } ] + + let jDeser = j.to(seq[Obj2]) + doAssert jDeser[0].n2.get() == "ABC" + doAssert jDeser[1].n2.isNone() + + # Issue #6902 + type + Obj = object + n1: int + n2: Option[int] + n3: Option[string] + n4: Option[bool] + + var j0 = parseJson("""{"n1": 1, "n2": null, "n3": null, "n4": null}""") + let j0Deser = j0.to(Obj) + doAssert j0Deser.n1 == 1 + doAssert j0Deser.n2.isNone() + doAssert j0Deser.n3.isNone() + doAssert j0Deser.n4.isNone() + # Table[T, Y] support. block: type @@ -381,10 +411,109 @@ when isMainModule: doAssert dataDeser.a == 1 doAssert dataDeser.f == 6 doAssert dataDeser.i == 9.9'f32 - + # deserialize directly into a table block: let s = """{"a": 1, "b": 2}""" let t = parseJson(s).to(Table[string, int]) doAssert t["a"] == 1 - doAssert t["b"] == 2 \ No newline at end of file + doAssert t["b"] == 2 + + block: + # bug #8037 + type + Apple = distinct string + String = distinct Apple + Email = distinct string + MyList = distinct seq[int] + MyYear = distinct Option[int] + MyTable = distinct Table[string, int] + MyArr = distinct array[3, float] + MyRef = ref object + name: string + MyObj = object + color: int + MyDistRef = distinct MyRef + MyDistObj = distinct MyObj + Toot = object + name*: String + email*: Email + list: MyList + year: MyYear + dict: MyTable + arr: MyArr + person: MyDistRef + distfruit: MyDistObj + dog: MyRef + fruit: MyObj + emails: seq[String] + + var tJson = parseJson(""" + { + "name":"Bongo", + "email":"bongo@bingo.com", + "list": [11,7,15], + "year": 1975, + "dict": {"a": 1, "b": 2}, + "arr": [1.0, 2.0, 7.0], + "person": {"name": "boney"}, + "dog": {"name": "honey"}, + "fruit": {"color": 10}, + "distfruit": {"color": 11}, + "emails": ["abc", "123"] + } + """) + + var t = to(tJson, Toot) + doAssert string(t.name) == "Bongo" + doAssert string(t.email) == "bongo@bingo.com" + doAssert seq[int](t.list) == @[11,7,15] + doAssert Option[int](t.year).get() == 1975 + doAssert Table[string,int](t.dict)["a"] == 1 + doAssert Table[string,int](t.dict)["b"] == 2 + doAssert array[3, float](t.arr) == [1.0,2.0,7.0] + doAssert MyRef(t.person).name == "boney" + doAssert MyObj(t.distFruit).color == 11 + doAssert t.dog.name == "honey" + doAssert t.fruit.color == 10 + doAssert seq[string](t.emails) == @["abc", "123"] + + block test_table: + var y = parseJson("""{"a": 1, "b": 2, "c": 3}""") + var u = y.to(MyTable) + var v = y.to(Table[string, int]) + doAssert Table[string, int](u)["a"] == 1 + doAssert Table[string, int](u)["b"] == 2 + doAssert Table[string, int](u)["c"] == 3 + doAssert v["a"] == 1 + + block primitive_string: + const kApple = "apple" + var u = newJString(kApple) + var v = u.to(Email) + var w = u.to(Apple) + var x = u.to(String) + doAssert string(v) == kApple + doAssert string(w) == kApple + doAssert string(x) == kApple + + block test_option: + var u = newJInt(1137) + var v = u.to(MyYear) + var w = u.to(Option[int]) + doAssert Option[int](v).get() == 1137 + doAssert w.get() == 1137 + + block test_object: + var u = parseJson("""{"color": 987}""") + var v = u.to(MyObj) + var w = u.to(MyDistObj) + doAssert v.color == 987 + doAssert MyObj(w).color == 987 + + block test_ref_object: + var u = parseJson("""{"name": "smith"}""") + var v = u.to(MyRef) + var w = u.to(MyDistRef) + doAssert v.name == "smith" + doAssert MyRef(w).name == "smith" diff --git a/tests/stdlib/tmemfiles2.nim b/tests/stdlib/tmemfiles2.nim index 7ea94cffc..1b249898e 100644 --- a/tests/stdlib/tmemfiles2.nim +++ b/tests/stdlib/tmemfiles2.nim @@ -1,12 +1,13 @@ discard """ - file: "tmemfiles2.nim" + disabled: "Windows" output: '''Full read size: 20 Half read size: 10 Data: Hello''' """ import memfiles, os +const + fn = "test.mmap" var mm, mm_full, mm_half: MemFile - fn = "test.mmap" p: pointer if fileExists(fn): removeFile(fn) @@ -17,8 +18,9 @@ mm.close() # read, change mm_full = memfiles.open(fn, mode = fmWrite, mappedSize = -1, allowRemap = true) -echo "Full read size: ",mm_full.size +let size = mm_full.size p = mm_full.mapMem(fmReadWrite, 20, 0) +echo "Full read size: ", size var p2 = cast[cstring](p) p2[0] = 'H' p2[1] = 'e' @@ -31,7 +33,7 @@ mm_full.close() # read half, and verify data change mm_half = memfiles.open(fn, mode = fmRead, mappedSize = 10) -echo "Half read size: ",mm_half.size, " Data: ", cast[cstring](mm_half.mem) +echo "Half read size: ", mm_half.size, " Data: ", cast[cstring](mm_half.mem) mm_half.close() if fileExists(fn): removeFile(fn) diff --git a/tests/stdlib/tmemmapstreams.nim b/tests/stdlib/tmemmapstreams.nim new file mode 100644 index 000000000..243574f1a --- /dev/null +++ b/tests/stdlib/tmemmapstreams.nim @@ -0,0 +1,53 @@ +discard """ + file: "tmemmapstreams.nim" + output: '''Created size: 10 +Position after writing: 5 +Position after writing one char: 6 +Peeked data: Hello +Position after peeking: 0 +Readed data: Hello! +Position after reading line: 7 +Position after setting position: 6 +Readed line: Hello! +Position after reading line: 7''' +""" +import os, streams, memfiles +const + fn = "test.mmapstream" +var + mms: MemMapFileStream + +if fileExists(fn): removeFile(fn) + +# Create a new memory mapped file, data all zeros +mms = newMemMapFileStream(fn, mode = fmReadWrite, fileSize = 10) +mms.close() +if fileExists(fn): echo "Created size: ", getFileSize(fn) + +# write, flush, peek, read +mms = newMemMapFileStream(fn, mode = fmReadWrite) +let s = "Hello" + +mms.write(s) +mms.flush +echo "Position after writing: ", mms.getPosition() +mms.write('!') +mms.flush +echo "Position after writing one char: ", mms.getPosition() +mms.close() + +mms = newMemMapFileStream(fn, mode = fmRead) +echo "Peeked data: ", mms.peekStr(s.len) +echo "Position after peeking: ", mms.getPosition() +echo "Readed data: ", mms.readLine +echo "Position after reading line: ", mms.getPosition() +mms.setPosition(mms.getPosition() - 1) +echo "Position after setting position: ", mms.getPosition() + +mms.setPosition(0) +echo "Readed line: ", mms.readLine +echo "Position after reading line: ", mms.getPosition() + +mms.close() + +if fileExists(fn): removeFile(fn) diff --git a/tests/stdlib/tnet.nim b/tests/stdlib/tnet.nim index e8ada05e7..d364447da 100644 --- a/tests/stdlib/tnet.nim +++ b/tests/stdlib/tnet.nim @@ -1,4 +1,4 @@ -import net +import net, nativesockets import unittest suite "isIpAddress tests": @@ -45,3 +45,45 @@ suite "parseIpAddress tests": test "invalid ipv6": expect(ValueError): discard parseIpAddress("gggg:cdba:0000:0000:0000:0000:3257:9652") + +block: # "IpAddress/Sockaddr conversion" + proc test(ipaddrstr: string) = + var ipaddr_1 = parseIpAddress(ipaddrstr) + # echo ipaddrstr, " ", $ipaddr_1 + + doAssert($ipaddrstr == $ipaddr_1) + + var sockaddr: Sockaddr_storage + var socklen: Socklen + var ipaddr_2: IpAddress + var port_2: Port + + toSockAddr(ipaddr_1, Port(0), sockaddr, socklen) + fromSockAddr(sockaddr, socklen, ipaddr_2, port_2) + + doAssert(ipaddrstr == $ipaddr_1) + + doAssert(ipaddr_1 == ipaddr_2) + doAssert($ipaddr_1 == $ipaddr_2) + + if sockaddr.ss_family == AF_INET.toInt: + var sockaddr4: Sockaddr_in + copyMem(addr sockaddr4, addr sockaddr, sizeof(sockaddr4)) + fromSockAddr(sockaddr4, socklen, ipaddr_2, port_2) + elif sockaddr.ss_family == AF_INET6.toInt: + var sockaddr6: Sockaddr_in6 + copyMem(addr sockaddr6, addr sockaddr, sizeof(sockaddr6)) + fromSockAddr(sockaddr6, socklen, ipaddr_2, port_2) + + doAssert(ipaddr_1 == ipaddr_2) + doAssert($ipaddr_1 == $ipaddr_2) + + + # ipv6 address of example.com + test("2606:2800:220:1:248:1893:25c8:1946") + # ipv6 address of localhost + test("::1") + # ipv4 address of example.com + test("93.184.216.34") + # ipv4 address of localhost + test("127.0.0.1") diff --git a/tests/stdlib/tnilecho.nim b/tests/stdlib/tnilecho.nim index 147b5e492..ec8d71dab 100644 --- a/tests/stdlib/tnilecho.nim +++ b/tests/stdlib/tnilecho.nim @@ -1,2 +1,6 @@ -var x = @["1", nil, "3"] -doAssert $x == "@[1, nil, 3]" +discard """ + output: "" +""" + +var x = @["1", "", "3"] +doAssert $x == """@["1", "", "3"]""" diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim index 771dc2456..10f439dfa 100644 --- a/tests/stdlib/tos.nim +++ b/tests/stdlib/tos.nim @@ -42,6 +42,8 @@ Raises true true true +true + ''' """ # test os path creation, iteration, and deletion @@ -129,3 +131,76 @@ echo fileExists("../dest/a/b/file.txt") echo fileExists("../dest/a/b/c/fileC.txt") removeDir("../dest") + +# Test get/set modification times +# Should support at least microsecond resolution +import times +let tm = fromUnix(0) + 100.microseconds +writeFile("a", "") +setLastModificationTime("a", tm) + +when defined(macosx): + echo "true" +else: + echo getLastModificationTime("a") == tm +removeFile("a") + +when defined(posix): + + block normalizedPath: + block relative: + doAssert normalizedPath(".") == "." + doAssert normalizedPath("..") == ".." + doAssert normalizedPath("../") == ".." + doAssert normalizedPath("../..") == "../.." + doAssert normalizedPath("../a/..") == ".." + doAssert normalizedPath("../a/../") == ".." + doAssert normalizedPath("./") == "." + + block absolute: + doAssert normalizedPath("/") == "/" + doAssert normalizedPath("/.") == "/" + doAssert normalizedPath("/..") == "/" + doAssert normalizedPath("/../") == "/" + doAssert normalizedPath("/../..") == "/" + doAssert normalizedPath("/../../") == "/" + doAssert normalizedPath("/../../../") == "/" + doAssert normalizedPath("/a/b/../../foo") == "/foo" + doAssert normalizedPath("/a/b/../../../foo") == "/foo" + doAssert normalizedPath("/./") == "/" + doAssert normalizedPath("//") == "/" + doAssert normalizedPath("///") == "/" + doAssert normalizedPath("/a//b") == "/a/b" + doAssert normalizedPath("/a///b") == "/a/b" + doAssert normalizedPath("/a/b/c/..") == "/a/b" + doAssert normalizedPath("/a/b/c/../") == "/a/b" + +else: + + block normalizedPath: + block relative: + doAssert normalizedPath(".") == "." + doAssert normalizedPath("..") == ".." + doAssert normalizedPath("..\\") == ".." + doAssert normalizedPath("..\\..") == "..\\.." + doAssert normalizedPath("..\\a\\..") == ".." + doAssert normalizedPath("..\\a\\..\\") == ".." + doAssert normalizedPath(".\\") == "." + + block absolute: + doAssert normalizedPath("\\") == "\\" + doAssert normalizedPath("\\.") == "\\" + doAssert normalizedPath("\\..") == "\\" + doAssert normalizedPath("\\..\\") == "\\" + doAssert normalizedPath("\\..\\..") == "\\" + doAssert normalizedPath("\\..\\..\\") == "\\" + doAssert normalizedPath("\\..\\..\\..\\") == "\\" + doAssert normalizedPath("\\a\\b\\..\\..\\foo") == "\\foo" + doAssert normalizedPath("\\a\\b\\..\\..\\..\\foo") == "\\foo" + doAssert normalizedPath("\\.\\") == "\\" + doAssert normalizedPath("\\\\") == "\\" + doAssert normalizedPath("\\\\\\") == "\\" + doAssert normalizedPath("\\a\\\\b") == "\\a\\b" + doAssert normalizedPath("\\a\\\\\\b") == "\\a\\b" + doAssert normalizedPath("\\a\\b\\c\\..") == "\\a\\b" + doAssert normalizedPath("\\a\\b\\c\\..\\") == "\\a\\b" diff --git a/tests/stdlib/tospaths.nim b/tests/stdlib/tospaths.nim new file mode 100644 index 000000000..0ac7729d9 --- /dev/null +++ b/tests/stdlib/tospaths.nim @@ -0,0 +1,41 @@ +discard """ + file: "tospaths.nim" + output: "" +""" +# test the ospaths module + +import os + +doAssert unixToNativePath("") == "" +doAssert unixToNativePath(".") == $CurDir +doAssert unixToNativePath("..") == $ParDir +doAssert isAbsolute(unixToNativePath("/")) +doAssert isAbsolute(unixToNativePath("/", "a")) +doAssert isAbsolute(unixToNativePath("/a")) +doAssert isAbsolute(unixToNativePath("/a", "a")) +doAssert isAbsolute(unixToNativePath("/a/b")) +doAssert isAbsolute(unixToNativePath("/a/b", "a")) +doAssert unixToNativePath("a/b") == joinPath("a", "b") + +when defined(macos): + doAssert unixToNativePath("./") == ":" + doAssert unixToNativePath("./abc") == ":abc" + doAssert unixToNativePath("../abc") == "::abc" + doAssert unixToNativePath("../../abc") == ":::abc" + doAssert unixToNativePath("/abc", "a") == "abc" + doAssert unixToNativePath("/abc/def", "a") == "abc:def" +elif doslikeFileSystem: + doAssert unixToNativePath("./") == ".\\" + doAssert unixToNativePath("./abc") == ".\\abc" + doAssert unixToNativePath("../abc") == "..\\abc" + doAssert unixToNativePath("../../abc") == "..\\..\\abc" + doAssert unixToNativePath("/abc", "a") == "a:\\abc" + doAssert unixToNativePath("/abc/def", "a") == "a:\\abc\\def" +else: + #Tests for unix + doAssert unixToNativePath("./") == "./" + doAssert unixToNativePath("./abc") == "./abc" + doAssert unixToNativePath("../abc") == "../abc" + doAssert unixToNativePath("../../abc") == "../../abc" + doAssert unixToNativePath("/abc", "a") == "/abc" + doAssert unixToNativePath("/abc/def", "a") == "/abc/def" diff --git a/tests/stdlib/tparsesql.nim b/tests/stdlib/tparsesql.nim index 3dc949ea1..126020ed6 100644 --- a/tests/stdlib/tparsesql.nim +++ b/tests/stdlib/tparsesql.nim @@ -26,10 +26,9 @@ doAssert $parseSQL("SELECT foo, bar, baz FROM table limit 10") == "select foo, b doAssert $parseSQL("SELECT foo AS bar FROM table") == "select foo as bar from table;" doAssert $parseSQL("SELECT foo AS foo_prime, bar AS bar_prime, baz AS baz_prime FROM table") == "select foo as foo_prime, bar as bar_prime, baz as baz_prime from table;" doAssert $parseSQL("SELECT * FROM table") == "select * from table;" - - -#TODO add count(*) -#doAssert $parseSQL("SELECT COUNT(*) FROM table" +doAssert $parseSQL("SELECT count(*) FROM table") == "select count(*) from table;" +doAssert $parseSQL("SELECT count(*) as 'Total' FROM table") == "select count(*) as 'Total' from table;" +doAssert $parseSQL("SELECT count(*) as 'Total', sum(a) as 'Aggr' FROM table") == "select count(*) as 'Total', sum(a) as 'Aggr' from table;" doAssert $parseSQL(""" SELECT * FROM table @@ -52,6 +51,23 @@ WHERE doAssert $parseSQL(""" SELECT * FROM table +ORDER BY 1 +""") == "select * from table order by 1;" + +doAssert $parseSQL(""" +SELECT * FROM table +GROUP BY 1 +ORDER BY 1 +""") == "select * from table group by 1 order by 1;" + +doAssert $parseSQL(""" +SELECT * FROM table +ORDER BY 1 +LIMIT 100 +""") == "select * from table order by 1 limit 100;" + +doAssert $parseSQL(""" +SELECT * FROM table WHERE a = b and c = d or n is null and not b + 1 = 3 """) == "select * from table where a = b and c = d or n is null and not b + 1 = 3;" @@ -185,7 +201,10 @@ AND Country='USA' ORDER BY CustomerName; """) == "select * from Customers where(CustomerName like 'L%' or CustomerName like 'R%' or CustomerName like 'W%') and Country = 'USA' order by CustomerName;" -# parse keywords as identifires +# parse quoted keywords as identifires doAssert $parseSQL(""" SELECT `SELECT`, `FROM` as `GROUP` FROM `WHERE`; """) == """select "SELECT", "FROM" as "GROUP" from "WHERE";""" +doAssert $parseSQL(""" +SELECT "SELECT", "FROM" as "GROUP" FROM "WHERE"; +""") == """select "SELECT", "FROM" as "GROUP" from "WHERE";""" diff --git a/tests/stdlib/tpegs.nim b/tests/stdlib/tpegs.nim index e2a5a1715..b07442dcb 100644 --- a/tests/stdlib/tpegs.nim +++ b/tests/stdlib/tpegs.nim @@ -1,1770 +1,148 @@ discard """ - output: '''this -is -an -example -d -e -f -('keyvalue' 'key'*)''' + output: ''' +PEG AST traversal output +------------------------ +pkNonTerminal: Sum @(2, 3) + pkSequence: (Product (('+' / '-') Product)*) + pkNonTerminal: Product @(3, 7) + pkSequence: (Value (('*' / '/') Value)*) + pkNonTerminal: Value @(4, 5) + pkOrderedChoice: (([0-9] [0-9]*) / ('(' Expr ')')) + pkSequence: ([0-9] [0-9]*) + pkCharChoice: [0-9] + pkGreedyRepSet: [0-9]* + pkSequence: ('(' Expr ')') + pkChar: '(' + pkNonTerminal: Expr @(1, 4) + pkNonTerminal: Sum @(2, 3) + pkChar: ')' + pkGreedyRep: (('*' / '/') Value)* + pkSequence: (('*' / '/') Value) + pkOrderedChoice: ('*' / '/') + pkChar: '*' + pkChar: '/' + pkNonTerminal: Value @(4, 5) + pkGreedyRep: (('+' / '-') Product)* + pkSequence: (('+' / '-') Product) + pkOrderedChoice: ('+' / '-') + pkChar: '+' + pkChar: '-' + pkNonTerminal: Product @(3, 7) + +Event parser output +------------------- +@[5.0] ++ +@[5.0, 3.0] +@[8.0] + +/ +@[8.0, 2.0] +@[4.0] + +- +@[4.0, 7.0] +-* +@[4.0, 7.0, 22.0] +@[4.0, 154.0] +- +@[-150.0] +''' """ -# PEGs module turned out to be a good test to detect memory management bugs. -include "system/inclrtl" +import strutils, streams +import pegs const - useUnicode = true ## change this to deactivate proper UTF-8 support - -import - strutils - -when useUnicode: - import unicode - -const - InlineThreshold = 5 ## number of leaves; -1 to disable inlining - MaxSubpatterns* = 10 ## defines the maximum number of subpatterns that - ## can be captured. More subpatterns cannot be captured! - -type - TPegKind = enum - pkEmpty, - pkAny, ## any character (.) - pkAnyRune, ## any Unicode character (_) - pkNewLine, ## CR-LF, LF, CR - pkLetter, ## Unicode letter - pkLower, ## Unicode lower case letter - pkUpper, ## Unicode upper case letter - pkTitle, ## Unicode title character - pkWhitespace, ## Unicode whitespace character - pkTerminal, - pkTerminalIgnoreCase, - pkTerminalIgnoreStyle, - pkChar, ## single character to match - pkCharChoice, - pkNonTerminal, - pkSequence, ## a b c ... --> Internal DSL: peg(a, b, c) - pkOrderedChoice, ## a / b / ... --> Internal DSL: a / b or /[a, b, c] - pkGreedyRep, ## a* --> Internal DSL: *a - ## a+ --> (a a*) - pkGreedyRepChar, ## x* where x is a single character (superop) - pkGreedyRepSet, ## [set]* (superop) - pkGreedyAny, ## .* or _* (superop) - pkOption, ## a? --> Internal DSL: ?a - pkAndPredicate, ## &a --> Internal DSL: &a - pkNotPredicate, ## !a --> Internal DSL: !a - pkCapture, ## {a} --> Internal DSL: capture(a) - pkBackRef, ## $i --> Internal DSL: backref(i) - pkBackRefIgnoreCase, - pkBackRefIgnoreStyle, - pkSearch, ## @a --> Internal DSL: !*a - pkCapturedSearch, ## {@} a --> Internal DSL: !*\a - pkRule, ## a <- b - pkList, ## a, b - pkStartAnchor ## ^ --> Internal DSL: startAnchor() - TNonTerminalFlag = enum - ntDeclared, ntUsed - TNonTerminal {.final.} = object ## represents a non terminal symbol - name: string ## the name of the symbol - line: int ## line the symbol has been declared/used in - col: int ## column the symbol has been declared/used in - flags: set[TNonTerminalFlag] ## the nonterminal's flags - rule: TNode ## the rule that the symbol refers to - TNode {.final, shallow.} = object - case kind: TPegKind - of pkEmpty..pkWhitespace: nil - of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle: term: string - of pkChar, pkGreedyRepChar: ch: char - of pkCharChoice, pkGreedyRepSet: charChoice: ref set[char] - of pkNonTerminal: nt: PNonTerminal - of pkBackRef..pkBackRefIgnoreStyle: index: range[0..MaxSubpatterns] - else: sons: seq[TNode] - PNonTerminal* = ref TNonTerminal - - TPeg* = TNode ## type that represents a PEG - -proc term*(t: string): TPeg {.rtl, extern: "npegs$1Str".} = - ## constructs a PEG from a terminal string - if t.len != 1: - result.kind = pkTerminal - result.term = t - else: - result.kind = pkChar - result.ch = t[0] - -proc termIgnoreCase*(t: string): TPeg {. - rtl, extern: "npegs$1".} = - ## constructs a PEG from a terminal string; ignore case for matching - result.kind = pkTerminalIgnoreCase - result.term = t - -proc termIgnoreStyle*(t: string): TPeg {. - rtl, extern: "npegs$1".} = - ## constructs a PEG from a terminal string; ignore style for matching - result.kind = pkTerminalIgnoreStyle - result.term = t - -proc term*(t: char): TPeg {.rtl, extern: "npegs$1Char".} = - ## constructs a PEG from a terminal char - assert t != '\0' - result.kind = pkChar - result.ch = t - -proc charSet*(s: set[char]): TPeg {.rtl, extern: "npegs$1".} = - ## constructs a PEG from a character set `s` - assert '\0' notin s - result.kind = pkCharChoice - new(result.charChoice) - result.charChoice[] = s - -proc len(a: TPeg): int {.inline.} = return a.sons.len -proc add(d: var TPeg, s: TPeg) {.inline.} = add(d.sons, s) - -proc copyPeg(a: TPeg): TPeg = - result.kind = a.kind - case a.kind - of pkEmpty..pkWhitespace: discard - of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle: - result.term = a.term - of pkChar, pkGreedyRepChar: - result.ch = a.ch - of pkCharChoice, pkGreedyRepSet: - new(result.charChoice) - result.charChoice[] = a.charChoice[] - of pkNonTerminal: result.nt = a.nt - of pkBackRef..pkBackRefIgnoreStyle: - result.index = a.index - else: - result.sons = a.sons - -proc addChoice(dest: var TPeg, elem: TPeg) = - var L = dest.len-1 - if L >= 0 and dest.sons[L].kind == pkCharChoice: - # caution! Do not introduce false aliasing here! - case elem.kind - of pkCharChoice: - dest.sons[L] = charSet(dest.sons[L].charChoice[] + elem.charChoice[]) - of pkChar: - dest.sons[L] = charSet(dest.sons[L].charChoice[] + {elem.ch}) - else: add(dest, elem) - else: add(dest, elem) - -template multipleOp(k: TPegKind, localOpt) = - result.kind = k - result.sons = @[] - for x in items(a): - if x.kind == k: - for y in items(x.sons): - localOpt(result, y) - else: - localOpt(result, x) - if result.len == 1: - result = result.sons[0] - -proc `/`*(a: varargs[TPeg]): TPeg {. - rtl, extern: "npegsOrderedChoice".} = - ## constructs an ordered choice with the PEGs in `a` - multipleOp(pkOrderedChoice, addChoice) - -proc addSequence(dest: var TPeg, elem: TPeg) = - var L = dest.len-1 - if L >= 0 and dest.sons[L].kind == pkTerminal: - # caution! Do not introduce false aliasing here! - case elem.kind - of pkTerminal: - dest.sons[L] = term(dest.sons[L].term & elem.term) - of pkChar: - dest.sons[L] = term(dest.sons[L].term & elem.ch) - else: add(dest, elem) - else: add(dest, elem) - -proc sequence*(a: varargs[TPeg]): TPeg {. - rtl, extern: "npegs$1".} = - ## constructs a sequence with all the PEGs from `a` - multipleOp(pkSequence, addSequence) - -proc `?`*(a: TPeg): TPeg {.rtl, extern: "npegsOptional".} = - ## constructs an optional for the PEG `a` - if a.kind in {pkOption, pkGreedyRep, pkGreedyAny, pkGreedyRepChar, - pkGreedyRepSet}: - # a* ? --> a* - # a? ? --> a? - result = a - else: - result.kind = pkOption - result.sons = @[a] - -proc `*`*(a: TPeg): TPeg {.rtl, extern: "npegsGreedyRep".} = - ## constructs a "greedy repetition" for the PEG `a` - case a.kind - of pkGreedyRep, pkGreedyRepChar, pkGreedyRepSet, pkGreedyAny, pkOption: - assert false - # produces endless loop! - of pkChar: - result.kind = pkGreedyRepChar - result.ch = a.ch - of pkCharChoice: - result.kind = pkGreedyRepSet - result.charChoice = a.charChoice # copying a reference suffices! - of pkAny, pkAnyRune: - result.kind = pkGreedyAny - else: - result.kind = pkGreedyRep - result.sons = @[a] - -proc `!*`*(a: TPeg): TPeg {.rtl, extern: "npegsSearch".} = - ## constructs a "search" for the PEG `a` - result.kind = pkSearch - result.sons = @[a] - -proc `!*\`*(a: TPeg): TPeg {.rtl, - extern: "npgegsCapturedSearch".} = - ## constructs a "captured search" for the PEG `a` - result.kind = pkCapturedSearch - result.sons = @[a] - -when false: - proc contains(a: TPeg, k: TPegKind): bool = - if a.kind == k: return true - case a.kind - of pkEmpty, pkAny, pkAnyRune, pkGreedyAny, pkNewLine, pkTerminal, - pkTerminalIgnoreCase, pkTerminalIgnoreStyle, pkChar, pkGreedyRepChar, - pkCharChoice, pkGreedyRepSet: discard - of pkNonTerminal: return true - else: - for i in 0..a.sons.len-1: - if contains(a.sons[i], k): return true - -proc `+`*(a: TPeg): TPeg {.rtl, extern: "npegsGreedyPosRep".} = - ## constructs a "greedy positive repetition" with the PEG `a` - return sequence(a, *a) - -proc `&`*(a: TPeg): TPeg {.rtl, extern: "npegsAndPredicate".} = - ## constructs an "and predicate" with the PEG `a` - result.kind = pkAndPredicate - result.sons = @[a] - -proc `!`*(a: TPeg): TPeg {.rtl, extern: "npegsNotPredicate".} = - ## constructs a "not predicate" with the PEG `a` - result.kind = pkNotPredicate - result.sons = @[a] - -proc any*: TPeg {.inline.} = - ## constructs the PEG `any character`:idx: (``.``) - result.kind = pkAny - -proc anyRune*: TPeg {.inline.} = - ## constructs the PEG `any rune`:idx: (``_``) - result.kind = pkAnyRune - -proc newLine*: TPeg {.inline.} = - ## constructs the PEG `newline`:idx: (``\n``) - result.kind = pkNewline - -proc UnicodeLetter*: TPeg {.inline.} = - ## constructs the PEG ``\letter`` which matches any Unicode letter. - result.kind = pkLetter - -proc UnicodeLower*: TPeg {.inline.} = - ## constructs the PEG ``\lower`` which matches any Unicode lowercase letter. - result.kind = pkLower - -proc UnicodeUpper*: TPeg {.inline.} = - ## constructs the PEG ``\upper`` which matches any Unicode lowercase letter. - result.kind = pkUpper - -proc UnicodeTitle*: TPeg {.inline.} = - ## constructs the PEG ``\title`` which matches any Unicode title letter. - result.kind = pkTitle - -proc UnicodeWhitespace*: TPeg {.inline.} = - ## constructs the PEG ``\white`` which matches any Unicode - ## whitespace character. - result.kind = pkWhitespace - -proc startAnchor*: TPeg {.inline.} = - ## constructs the PEG ``^`` which matches the start of the input. - result.kind = pkStartAnchor - -proc endAnchor*: TPeg {.inline.} = - ## constructs the PEG ``$`` which matches the end of the input. - result = !any() - -proc capture*(a: TPeg): TPeg {.rtl, extern: "npegsCapture".} = - ## constructs a capture with the PEG `a` - result.kind = pkCapture - result.sons = @[a] - -proc backref*(index: range[1..MaxSubPatterns]): TPeg {. - rtl, extern: "npegs$1".} = - ## constructs a back reference of the given `index`. `index` starts counting - ## from 1. - result.kind = pkBackRef - result.index = index-1 - -proc backrefIgnoreCase*(index: range[1..MaxSubPatterns]): TPeg {. - rtl, extern: "npegs$1".} = - ## constructs a back reference of the given `index`. `index` starts counting - ## from 1. Ignores case for matching. - result.kind = pkBackRefIgnoreCase - result.index = index-1 - -proc backrefIgnoreStyle*(index: range[1..MaxSubPatterns]): TPeg {. - rtl, extern: "npegs$1".}= - ## constructs a back reference of the given `index`. `index` starts counting - ## from 1. Ignores style for matching. - result.kind = pkBackRefIgnoreStyle - result.index = index-1 - -proc spaceCost(n: TPeg): int = - case n.kind - of pkEmpty: discard - of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle, pkChar, - pkGreedyRepChar, pkCharChoice, pkGreedyRepSet, - pkAny..pkWhitespace, pkGreedyAny: - result = 1 - of pkNonTerminal: - # we cannot inline a rule with a non-terminal - result = InlineThreshold+1 - else: - for i in 0..n.len-1: - inc(result, spaceCost(n.sons[i])) - if result >= InlineThreshold: break - -proc nonterminal*(n: PNonTerminal): TPeg {. - rtl, extern: "npegs$1".} = - ## constructs a PEG that consists of the nonterminal symbol - assert n != nil - if ntDeclared in n.flags and spaceCost(n.rule) < InlineThreshold: - when false: echo "inlining symbol: ", n.name - result = n.rule # inlining of rule enables better optimizations - else: - result.kind = pkNonTerminal - result.nt = n - -proc newNonTerminal*(name: string, line, column: int): PNonTerminal {. - rtl, extern: "npegs$1".} = - ## constructs a nonterminal symbol - new(result) - result.name = name - result.line = line - result.col = column - -template letters*: TPeg = - ## expands to ``charset({'A'..'Z', 'a'..'z'})`` - charset({'A'..'Z', 'a'..'z'}) - -template digits*: TPeg = - ## expands to ``charset({'0'..'9'})`` - charset({'0'..'9'}) - -template whitespace*: TPeg = - ## expands to ``charset({' ', '\9'..'\13'})`` - charset({' ', '\9'..'\13'}) - -template identChars*: TPeg = - ## expands to ``charset({'a'..'z', 'A'..'Z', '0'..'9', '_'})`` - charset({'a'..'z', 'A'..'Z', '0'..'9', '_'}) - -template identStartChars*: TPeg = - ## expands to ``charset({'A'..'Z', 'a'..'z', '_'})`` - charset({'a'..'z', 'A'..'Z', '_'}) - -template ident*: TPeg = - ## same as ``[a-zA-Z_][a-zA-z_0-9]*``; standard identifier - sequence(charset({'a'..'z', 'A'..'Z', '_'}), - *charset({'a'..'z', 'A'..'Z', '0'..'9', '_'})) - -template natural*: TPeg = - ## same as ``\d+`` - +digits - -# ------------------------- debugging ----------------------------------------- - -proc esc(c: char, reserved = {'\0'..'\255'}): string = - case c - of '\b': result = "\\b" - of '\t': result = "\\t" - of '\c': result = "\\c" - of '\L': result = "\\l" - of '\v': result = "\\v" - of '\f': result = "\\f" - of '\e': result = "\\e" - of '\a': result = "\\a" - of '\\': result = "\\\\" - of 'a'..'z', 'A'..'Z', '0'..'9', '_': result = $c - elif c < ' ' or c >= '\128': result = '\\' & $ord(c) - elif c in reserved: result = '\\' & c - else: result = $c - -proc singleQuoteEsc(c: char): string = return "'" & esc(c, {'\''}) & "'" - -proc singleQuoteEsc(str: string): string = - result = "'" - for c in items(str): add result, esc(c, {'\''}) - add result, '\'' - -proc charSetEscAux(cc: set[char]): string = - const reserved = {'^', '-', ']'} - result = "" - var c1 = 0 - while c1 <= 0xff: - if chr(c1) in cc: - var c2 = c1 - while c2 < 0xff and chr(succ(c2)) in cc: inc(c2) - if c1 == c2: - add result, esc(chr(c1), reserved) - elif c2 == succ(c1): - add result, esc(chr(c1), reserved) & esc(chr(c2), reserved) - else: - add result, esc(chr(c1), reserved) & '-' & esc(chr(c2), reserved) - c1 = c2 - inc(c1) - -proc charSetEsc(cc: set[char]): string = - if card(cc) >= 128+64: - result = "[^" & charSetEscAux({'\1'..'\xFF'} - cc) & ']' - else: - result = '[' & charSetEscAux(cc) & ']' - -proc toStrAux(r: TPeg, res: var string) = - case r.kind - of pkEmpty: add(res, "()") - of pkAny: add(res, '.') - of pkAnyRune: add(res, '_') - of pkLetter: add(res, "\\letter") - of pkLower: add(res, "\\lower") - of pkUpper: add(res, "\\upper") - of pkTitle: add(res, "\\title") - of pkWhitespace: add(res, "\\white") - - of pkNewline: add(res, "\\n") - of pkTerminal: add(res, singleQuoteEsc(r.term)) - of pkTerminalIgnoreCase: - add(res, 'i') - add(res, singleQuoteEsc(r.term)) - of pkTerminalIgnoreStyle: - add(res, 'y') - add(res, singleQuoteEsc(r.term)) - of pkChar: add(res, singleQuoteEsc(r.ch)) - of pkCharChoice: add(res, charSetEsc(r.charChoice[])) - of pkNonTerminal: add(res, r.nt.name) - of pkSequence: - add(res, '(') - toStrAux(r.sons[0], res) - for i in 1 .. high(r.sons): - add(res, ' ') - toStrAux(r.sons[i], res) - add(res, ')') - of pkOrderedChoice: - add(res, '(') - toStrAux(r.sons[0], res) - for i in 1 .. high(r.sons): - add(res, " / ") - toStrAux(r.sons[i], res) - add(res, ')') - of pkGreedyRep: - toStrAux(r.sons[0], res) - add(res, '*') - of pkGreedyRepChar: - add(res, singleQuoteEsc(r.ch)) - add(res, '*') - of pkGreedyRepSet: - add(res, charSetEsc(r.charChoice[])) - add(res, '*') - of pkGreedyAny: - add(res, ".*") - of pkOption: - toStrAux(r.sons[0], res) - add(res, '?') - of pkAndPredicate: - add(res, '&') - toStrAux(r.sons[0], res) - of pkNotPredicate: - add(res, '!') - toStrAux(r.sons[0], res) - of pkSearch: - add(res, '@') - toStrAux(r.sons[0], res) - of pkCapturedSearch: - add(res, "{@}") - toStrAux(r.sons[0], res) - of pkCapture: - add(res, '{') - toStrAux(r.sons[0], res) - add(res, '}') - of pkBackRef: - add(res, '$') - add(res, $r.index) - of pkBackRefIgnoreCase: - add(res, "i$") - add(res, $r.index) - of pkBackRefIgnoreStyle: - add(res, "y$") - add(res, $r.index) - of pkRule: - toStrAux(r.sons[0], res) - add(res, " <- ") - toStrAux(r.sons[1], res) - of pkList: - for i in 0 .. high(r.sons): - toStrAux(r.sons[i], res) - add(res, "\n") - of pkStartAnchor: - add(res, '^') - -proc `$` *(r: TPeg): string {.rtl, extern: "npegsToString".} = - ## converts a PEG to its string representation - result = "" - toStrAux(r, result) - -# --------------------- core engine ------------------------------------------- - -type - TCaptures* {.final.} = object ## contains the captured substrings. - matches: array[0..MaxSubpatterns-1, tuple[first, last: int]] - ml: int - origStart: int - -proc bounds*(c: TCaptures, - i: range[0..MaxSubpatterns-1]): tuple[first, last: int] = - ## returns the bounds ``[first..last]`` of the `i`'th capture. - result = c.matches[i] - -when not useUnicode: - type - Rune = char - template fastRuneAt(s, i, ch) = - ch = s[i] - inc(i) - template runeLenAt(s, i): untyped = 1 - - proc isAlpha(a: char): bool {.inline.} = return a in {'a'..'z','A'..'Z'} - proc isUpper(a: char): bool {.inline.} = return a in {'A'..'Z'} - proc isLower(a: char): bool {.inline.} = return a in {'a'..'z'} - proc isTitle(a: char): bool {.inline.} = return false - proc isWhiteSpace(a: char): bool {.inline.} = return a in {' ', '\9'..'\13'} - -proc rawMatch*(s: string, p: TPeg, start: int, c: var TCaptures): int {. - rtl, extern: "npegs$1".} = - ## low-level matching proc that implements the PEG interpreter. Use this - ## for maximum efficiency (every other PEG operation ends up calling this - ## proc). - ## Returns -1 if it does not match, else the length of the match - case p.kind - of pkEmpty: result = 0 # match of length 0 - of pkAny: - if s[start] != '\0': result = 1 - else: result = -1 - of pkAnyRune: - if s[start] != '\0': - result = runeLenAt(s, start) - else: - result = -1 - of pkLetter: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isAlpha(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkLower: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isLower(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkUpper: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isUpper(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkTitle: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isTitle(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkWhitespace: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isWhitespace(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkGreedyAny: - result = len(s) - start - of pkNewLine: - if s[start] == '\L': result = 1 - elif s[start] == '\C': - if s[start+1] == '\L': result = 2 - else: result = 1 - else: result = -1 - of pkTerminal: - result = len(p.term) - for i in 0..result-1: - if p.term[i] != s[start+i]: - result = -1 - break - of pkTerminalIgnoreCase: - var - i = 0 - a, b: Rune - result = start - while i < len(p.term): - fastRuneAt(p.term, i, a) - fastRuneAt(s, result, b) - if toLower(a) != toLower(b): - result = -1 - break - dec(result, start) - of pkTerminalIgnoreStyle: - var - i = 0 - a, b: Rune - result = start - while i < len(p.term): - while true: - fastRuneAt(p.term, i, a) - if a != Rune('_'): break - while true: - fastRuneAt(s, result, b) - if b != Rune('_'): break - if toLower(a) != toLower(b): - result = -1 - break - dec(result, start) - of pkChar: - if p.ch == s[start]: result = 1 - else: result = -1 - of pkCharChoice: - if contains(p.charChoice[], s[start]): result = 1 - else: result = -1 - of pkNonTerminal: - var oldMl = c.ml - when false: echo "enter: ", p.nt.name - result = rawMatch(s, p.nt.rule, start, c) - when false: echo "leave: ", p.nt.name - if result < 0: c.ml = oldMl - of pkSequence: - var oldMl = c.ml - result = 0 - assert(not isNil(p.sons)) - for i in 0..high(p.sons): - var x = rawMatch(s, p.sons[i], start+result, c) - if x < 0: - c.ml = oldMl - result = -1 - break - else: inc(result, x) - of pkOrderedChoice: - var oldMl = c.ml - for i in 0..high(p.sons): - result = rawMatch(s, p.sons[i], start, c) - if result >= 0: break - c.ml = oldMl - of pkSearch: - var oldMl = c.ml - result = 0 - while start+result < s.len: - var x = rawMatch(s, p.sons[0], start+result, c) - if x >= 0: - inc(result, x) - return - inc(result) - result = -1 - c.ml = oldMl - of pkCapturedSearch: - var idx = c.ml # reserve a slot for the subpattern - inc(c.ml) - result = 0 - while start+result < s.len: - var x = rawMatch(s, p.sons[0], start+result, c) - if x >= 0: - if idx < MaxSubpatterns: - c.matches[idx] = (start, start+result-1) - #else: silently ignore the capture - inc(result, x) - return - inc(result) - result = -1 - c.ml = idx - of pkGreedyRep: - result = 0 - while true: - var x = rawMatch(s, p.sons[0], start+result, c) - # if x == 0, we have an endless loop; so the correct behaviour would be - # not to break. But endless loops can be easily introduced: - # ``(comment / \w*)*`` is such an example. Breaking for x == 0 does the - # expected thing in this case. - if x <= 0: break - inc(result, x) - of pkGreedyRepChar: - result = 0 - var ch = p.ch - while ch == s[start+result]: inc(result) - of pkGreedyRepSet: - result = 0 - while contains(p.charChoice[], s[start+result]): inc(result) - of pkOption: - result = max(0, rawMatch(s, p.sons[0], start, c)) - of pkAndPredicate: - var oldMl = c.ml - result = rawMatch(s, p.sons[0], start, c) - if result >= 0: result = 0 # do not consume anything - else: c.ml = oldMl - of pkNotPredicate: - var oldMl = c.ml - result = rawMatch(s, p.sons[0], start, c) - if result < 0: result = 0 - else: - c.ml = oldMl - result = -1 - of pkCapture: - var idx = c.ml # reserve a slot for the subpattern - inc(c.ml) - result = rawMatch(s, p.sons[0], start, c) - if result >= 0: - if idx < MaxSubpatterns: - c.matches[idx] = (start, start+result-1) - #else: silently ignore the capture - else: - c.ml = idx - of pkBackRef..pkBackRefIgnoreStyle: - if p.index >= c.ml: return -1 - var (a, b) = c.matches[p.index] - var n: TPeg - n.kind = succ(pkTerminal, ord(p.kind)-ord(pkBackRef)) - n.term = s.substr(a, b) - result = rawMatch(s, n, start, c) - of pkStartAnchor: - if c.origStart == start: result = 0 - else: result = -1 - of pkRule, pkList: assert false - -proc match*(s: string, pattern: TPeg, matches: var openarray[string], - start = 0): bool {.rtl, extern: "npegs$1Capture".} = - ## returns ``true`` if ``s[start..]`` matches the ``pattern`` and - ## the captured substrings in the array ``matches``. If it does not - ## match, nothing is written into ``matches`` and ``false`` is - ## returned. - var c: TCaptures - c.origStart = start - result = rawMatch(s, pattern, start, c) == len(s)-start - if result: - for i in 0..c.ml-1: - matches[i] = substr(s, c.matches[i][0], c.matches[i][1]) - -proc match*(s: string, pattern: TPeg, - start = 0): bool {.rtl, extern: "npegs$1".} = - ## returns ``true`` if ``s`` matches the ``pattern`` beginning from ``start``. - var c: TCaptures - c.origStart = start - result = rawMatch(s, pattern, start, c) == len(s)-start - -proc matchLen*(s: string, pattern: TPeg, matches: var openarray[string], - start = 0): int {.rtl, extern: "npegs$1Capture".} = - ## the same as ``match``, but it returns the length of the match, - ## if there is no match, -1 is returned. Note that a match length - ## of zero can happen. It's possible that a suffix of `s` remains - ## that does not belong to the match. - var c: TCaptures - c.origStart = start - result = rawMatch(s, pattern, start, c) - if result >= 0: - for i in 0..c.ml-1: - matches[i] = substr(s, c.matches[i][0], c.matches[i][1]) - -proc matchLen*(s: string, pattern: TPeg, - start = 0): int {.rtl, extern: "npegs$1".} = - ## the same as ``match``, but it returns the length of the match, - ## if there is no match, -1 is returned. Note that a match length - ## of zero can happen. It's possible that a suffix of `s` remains - ## that does not belong to the match. - var c: TCaptures - c.origStart = start - result = rawMatch(s, pattern, start, c) - -proc find*(s: string, pattern: TPeg, matches: var openarray[string], - start = 0): int {.rtl, extern: "npegs$1Capture".} = - ## returns the starting position of ``pattern`` in ``s`` and the captured - ## substrings in the array ``matches``. If it does not match, nothing - ## is written into ``matches`` and -1 is returned. - for i in start .. s.len-1: - if matchLen(s, pattern, matches, i) >= 0: return i - return -1 - # could also use the pattern here: (!P .)* P - -proc findBounds*(s: string, pattern: TPeg, matches: var openarray[string], - start = 0): tuple[first, last: int] {. - rtl, extern: "npegs$1Capture".} = - ## returns the starting position and end position of ``pattern`` in ``s`` - ## and the captured - ## substrings in the array ``matches``. If it does not match, nothing - ## is written into ``matches`` and (-1,0) is returned. - for i in start .. s.len-1: - var L = matchLen(s, pattern, matches, i) - if L >= 0: return (i, i+L-1) - return (-1, 0) - -proc find*(s: string, pattern: TPeg, - start = 0): int {.rtl, extern: "npegs$1".} = - ## returns the starting position of ``pattern`` in ``s``. If it does not - ## match, -1 is returned. - for i in start .. s.len-1: - if matchLen(s, pattern, i) >= 0: return i - return -1 - -iterator findAll*(s: string, pattern: TPeg, start = 0): string = - ## yields all matching captures of pattern in `s`. - var matches: array[0..MaxSubpatterns-1, string] - var i = start - while i < s.len: - var L = matchLen(s, pattern, matches, i) - if L < 0: break - for k in 0..MaxSubPatterns-1: - if isNil(matches[k]): break - yield matches[k] - inc(i, L) - -proc findAll*(s: string, pattern: TPeg, start = 0): seq[string] {. - rtl, extern: "npegs$1".} = - ## returns all matching captures of pattern in `s`. - ## If it does not match, @[] is returned. - accumulateResult(findAll(s, pattern, start)) - -template `=~`*(s: string, pattern: TPeg): untyped = - ## This calls ``match`` with an implicit declared ``matches`` array that - ## can be used in the scope of the ``=~`` call: - ## - ## .. code-block:: nim - ## - ## if line =~ peg"\s* {\w+} \s* '=' \s* {\w+}": - ## # matches a key=value pair: - ## echo("Key: ", matches[0]) - ## echo("Value: ", matches[1]) - ## elif line =~ peg"\s*{'#'.*}": - ## # matches a comment - ## # note that the implicit ``matches`` array is different from the - ## # ``matches`` array of the first branch - ## echo("comment: ", matches[0]) - ## else: - ## echo("syntax error") - ## - when not declaredInScope(matches): - var matches {.inject.}: array[0..MaxSubpatterns-1, string] - match(s, pattern, matches) - -# ------------------------- more string handling ------------------------------ - -proc contains*(s: string, pattern: TPeg, start = 0): bool {. - rtl, extern: "npegs$1".} = - ## same as ``find(s, pattern, start) >= 0`` - return find(s, pattern, start) >= 0 - -proc contains*(s: string, pattern: TPeg, matches: var openArray[string], - start = 0): bool {.rtl, extern: "npegs$1Capture".} = - ## same as ``find(s, pattern, matches, start) >= 0`` - return find(s, pattern, matches, start) >= 0 - -proc startsWith*(s: string, prefix: TPeg, start = 0): bool {. - rtl, extern: "npegs$1".} = - ## returns true if `s` starts with the pattern `prefix` - result = matchLen(s, prefix, start) >= 0 - -proc endsWith*(s: string, suffix: TPeg, start = 0): bool {. - rtl, extern: "npegs$1".} = - ## returns true if `s` ends with the pattern `prefix` - for i in start .. s.len-1: - if matchLen(s, suffix, i) == s.len - i: return true - -proc replacef*(s: string, sub: TPeg, by: string): string {. - rtl, extern: "npegs$1".} = - ## Replaces `sub` in `s` by the string `by`. Captures can be accessed in `by` - ## with the notation ``$i`` and ``$#`` (see strutils.`%`). Examples: - ## - ## .. code-block:: nim - ## "var1=key; var2=key2".replace(peg"{\ident}'='{\ident}", "$1<-$2$2") - ## - ## Results in: - ## - ## .. code-block:: nim - ## - ## "var1<-keykey; val2<-key2key2" - result = "" - var i = 0 - var caps: array[0..MaxSubpatterns-1, string] - while i < s.len: - var x = matchLen(s, sub, caps, i) - if x <= 0: - add(result, s[i]) - inc(i) - else: - addf(result, by, caps) - inc(i, x) - add(result, substr(s, i)) - -proc replace*(s: string, sub: TPeg, by = ""): string {. - rtl, extern: "npegs$1".} = - ## Replaces `sub` in `s` by the string `by`. Captures cannot be accessed - ## in `by`. - result = "" - var i = 0 - var caps: array[0..MaxSubpatterns-1, string] - while i < s.len: - var x = matchLen(s, sub, caps, i) - if x <= 0: - add(result, s[i]) - inc(i) - else: - addf(result, by, caps) - inc(i, x) - add(result, substr(s, i)) - -proc parallelReplace*(s: string, subs: varargs[ - tuple[pattern: TPeg, repl: string]]): string {. - rtl, extern: "npegs$1".} = - ## Returns a modified copy of `s` with the substitutions in `subs` - ## applied in parallel. - result = "" - var i = 0 - var caps: array[0..MaxSubpatterns-1, string] - while i < s.len: - block searchSubs: - for j in 0..high(subs): - var x = matchLen(s, subs[j][0], caps, i) - if x > 0: - addf(result, subs[j][1], caps) - inc(i, x) - break searchSubs - add(result, s[i]) - inc(i) - # copy the rest: - add(result, substr(s, i)) - -proc transformFile*(infile, outfile: string, - subs: varargs[tuple[pattern: TPeg, repl: string]]) {. - rtl, extern: "npegs$1".} = - ## reads in the file `infile`, performs a parallel replacement (calls - ## `parallelReplace`) and writes back to `outfile`. Calls ``quit`` if an - ## error occurs. This is supposed to be used for quick scripting. - var x = readFile(infile) - if not isNil(x): - var f: File - if open(f, outfile, fmWrite): - write(f, x.parallelReplace(subs)) - close(f) - else: - quit("cannot open for writing: " & outfile) - else: - quit("cannot open for reading: " & infile) - -iterator split*(s: string, sep: TPeg): string = - ## Splits the string `s` into substrings. - ## - ## Substrings are separated by the PEG `sep`. - ## Examples: - ## - ## .. code-block:: nim - ## for word in split("00232this02939is39an22example111", peg"\d+"): - ## writeLine(stdout, word) - ## - ## Results in: - ## - ## .. code-block:: nim - ## "this" - ## "is" - ## "an" - ## "example" - ## + indent = " " + +let + pegAst = """ +Expr <- Sum +Sum <- Product (('+' / '-')Product)* +Product <- Value (('*' / '/')Value)* +Value <- [0-9]+ / '(' Expr ')' + """.peg + txt = "(5+3)/2-7*22" + +block: var - first = 0 - last = 0 - while last < len(s): - var x = matchLen(s, sep, last) - if x > 0: inc(last, x) - first = last - while last < len(s): - inc(last) - x = matchLen(s, sep, last) - if x > 0: break - if first < last: - yield substr(s, first, last-1) - -proc split*(s: string, sep: TPeg): seq[string] {. - rtl, extern: "npegs$1".} = - ## Splits the string `s` into substrings. - accumulateResult(split(s, sep)) - -# ------------------- scanner ------------------------------------------------- - -type - TModifier = enum - modNone, - modVerbatim, - modIgnoreCase, - modIgnoreStyle - TTokKind = enum ## enumeration of all tokens - tkInvalid, ## invalid token - tkEof, ## end of file reached - tkAny, ## . - tkAnyRune, ## _ - tkIdentifier, ## abc - tkStringLit, ## "abc" or 'abc' - tkCharSet, ## [^A-Z] - tkParLe, ## '(' - tkParRi, ## ')' - tkCurlyLe, ## '{' - tkCurlyRi, ## '}' - tkCurlyAt, ## '{@}' - tkArrow, ## '<-' - tkBar, ## '/' - tkStar, ## '*' - tkPlus, ## '+' - tkAmp, ## '&' - tkNot, ## '!' - tkOption, ## '?' - tkAt, ## '@' - tkBuiltin, ## \identifier - tkEscaped, ## \\ - tkBackref, ## '$' - tkDollar, ## '$' - tkHat ## '^' - - TToken {.final.} = object ## a token - kind: TTokKind ## the type of the token - modifier: TModifier - literal: string ## the parsed (string) literal - charset: set[char] ## if kind == tkCharSet - index: int ## if kind == tkBackref - - TPegLexer {.inheritable.} = object ## the lexer object. - bufpos: int ## the current position within the buffer - buf: cstring ## the buffer itself - lineNumber: int ## the current line number - lineStart: int ## index of last line start in buffer - colOffset: int ## column to add - filename: string - -const - tokKindToStr: array[TTokKind, string] = [ - "invalid", "[EOF]", ".", "_", "identifier", "string literal", - "character set", "(", ")", "{", "}", "{@}", - "<-", "/", "*", "+", "&", "!", "?", - "@", "built-in", "escaped", "$", "$", "^" - ] - -proc HandleCR(L: var TPegLexer, pos: int): int = - assert(L.buf[pos] == '\c') - inc(L.linenumber) - result = pos+1 - if L.buf[result] == '\L': inc(result) - L.lineStart = result - -proc HandleLF(L: var TPegLexer, pos: int): int = - assert(L.buf[pos] == '\L') - inc(L.linenumber) - result = pos+1 - L.lineStart = result - -proc init(L: var TPegLexer, input, filename: string, line = 1, col = 0) = - L.buf = input - L.bufpos = 0 - L.lineNumber = line - L.colOffset = col - L.lineStart = 0 - L.filename = filename - -proc getColumn(L: TPegLexer): int {.inline.} = - result = abs(L.bufpos - L.lineStart) + L.colOffset - -proc getLine(L: TPegLexer): int {.inline.} = - result = L.linenumber - -proc errorStr(L: TPegLexer, msg: string, line = -1, col = -1): string = - var line = if line < 0: getLine(L) else: line - var col = if col < 0: getColumn(L) else: col - result = "$1($2, $3) Error: $4" % [L.filename, $line, $col, msg] - -proc handleHexChar(c: var TPegLexer, xi: var int) = - case c.buf[c.bufpos] - of '0'..'9': - xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('0')) - inc(c.bufpos) - of 'a'..'f': - xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('a') + 10) - inc(c.bufpos) - of 'A'..'F': - xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('A') + 10) - inc(c.bufpos) - else: discard - -proc getEscapedChar(c: var TPegLexer, tok: var TToken) = - inc(c.bufpos) - case c.buf[c.bufpos] - of 'r', 'R', 'c', 'C': - add(tok.literal, '\c') - inc(c.bufpos) - of 'l', 'L': - add(tok.literal, '\L') - inc(c.bufpos) - of 'f', 'F': - add(tok.literal, '\f') - inc(c.bufpos) - of 'e', 'E': - add(tok.literal, '\e') - inc(c.bufpos) - of 'a', 'A': - add(tok.literal, '\a') - inc(c.bufpos) - of 'b', 'B': - add(tok.literal, '\b') - inc(c.bufpos) - of 'v', 'V': - add(tok.literal, '\v') - inc(c.bufpos) - of 't', 'T': - add(tok.literal, '\t') - inc(c.bufpos) - of 'x', 'X': - inc(c.bufpos) - var xi = 0 - handleHexChar(c, xi) - handleHexChar(c, xi) - if xi == 0: tok.kind = tkInvalid - else: add(tok.literal, chr(xi)) - of '0'..'9': - var val = ord(c.buf[c.bufpos]) - ord('0') - inc(c.bufpos) - var i = 1 - while (i <= 3) and (c.buf[c.bufpos] in {'0'..'9'}): - val = val * 10 + ord(c.buf[c.bufpos]) - ord('0') - inc(c.bufpos) - inc(i) - if val > 0 and val <= 255: add(tok.literal, chr(val)) - else: tok.kind = tkInvalid - of '\0'..'\31': - tok.kind = tkInvalid - elif c.buf[c.bufpos] in strutils.Letters: - tok.kind = tkInvalid - else: - add(tok.literal, c.buf[c.bufpos]) - inc(c.bufpos) - -proc skip(c: var TPegLexer) = - var pos = c.bufpos - var buf = c.buf - while true: - case buf[pos] - of ' ', '\t': - inc(pos) - of '#': - while not (buf[pos] in {'\c', '\L', '\0'}): inc(pos) - of '\c': - pos = HandleCR(c, pos) - buf = c.buf - of '\L': - pos = HandleLF(c, pos) - buf = c.buf - else: - break # EndOfFile also leaves the loop - c.bufpos = pos - -proc getString(c: var TPegLexer, tok: var TToken) = - tok.kind = tkStringLit - var pos = c.bufPos + 1 - var buf = c.buf - var quote = buf[pos-1] - while true: - case buf[pos] - of '\\': - c.bufpos = pos - getEscapedChar(c, tok) - pos = c.bufpos - of '\c', '\L', '\0': - tok.kind = tkInvalid - break - elif buf[pos] == quote: - inc(pos) - break - else: - add(tok.literal, buf[pos]) - inc(pos) - c.bufpos = pos - -proc getDollar(c: var TPegLexer, tok: var TToken) = - var pos = c.bufPos + 1 - var buf = c.buf - if buf[pos] in {'0'..'9'}: - tok.kind = tkBackref - tok.index = 0 - while buf[pos] in {'0'..'9'}: - tok.index = tok.index * 10 + ord(buf[pos]) - ord('0') - inc(pos) - else: - tok.kind = tkDollar - c.bufpos = pos - -proc getCharSet(c: var TPegLexer, tok: var TToken) = - tok.kind = tkCharSet - tok.charset = {} - var pos = c.bufPos + 1 - var buf = c.buf - var caret = false - if buf[pos] == '^': - inc(pos) - caret = true - while true: - var ch: char - case buf[pos] - of ']': - inc(pos) - break - of '\\': - c.bufpos = pos - getEscapedChar(c, tok) - pos = c.bufpos - ch = tok.literal[tok.literal.len-1] - of '\C', '\L', '\0': - tok.kind = tkInvalid - break - else: - ch = buf[pos] - inc(pos) - incl(tok.charset, ch) - if buf[pos] == '-': - if buf[pos+1] == ']': - incl(tok.charset, '-') - inc(pos) - else: - inc(pos) - var ch2: char - case buf[pos] - of '\\': - c.bufpos = pos - getEscapedChar(c, tok) - pos = c.bufpos - ch2 = tok.literal[tok.literal.len-1] - of '\C', '\L', '\0': - tok.kind = tkInvalid - break - else: - ch2 = buf[pos] - inc(pos) - for i in ord(ch)+1 .. ord(ch2): - incl(tok.charset, chr(i)) - c.bufpos = pos - if caret: tok.charset = {'\1'..'\xFF'} - tok.charset - -proc getSymbol(c: var TPegLexer, tok: var TToken) = - var pos = c.bufpos - var buf = c.buf - while true: - add(tok.literal, buf[pos]) - inc(pos) - if buf[pos] notin strutils.IdentChars: break - c.bufpos = pos - tok.kind = tkIdentifier - -proc getBuiltin(c: var TPegLexer, tok: var TToken) = - if c.buf[c.bufpos+1] in strutils.Letters: - inc(c.bufpos) - getSymbol(c, tok) - tok.kind = tkBuiltin - else: - tok.kind = tkEscaped - getEscapedChar(c, tok) # may set tok.kind to tkInvalid - -proc getTok(c: var TPegLexer, tok: var TToken) = - tok.kind = tkInvalid - tok.modifier = modNone - setlen(tok.literal, 0) - skip(c) - case c.buf[c.bufpos] - of '{': - inc(c.bufpos) - if c.buf[c.bufpos] == '@' and c.buf[c.bufpos+1] == '}': - tok.kind = tkCurlyAt - inc(c.bufpos, 2) - add(tok.literal, "{@}") - else: - tok.kind = tkCurlyLe - add(tok.literal, '{') - of '}': - tok.kind = tkCurlyRi - inc(c.bufpos) - add(tok.literal, '}') - of '[': - getCharset(c, tok) - of '(': - tok.kind = tkParLe - inc(c.bufpos) - add(tok.literal, '(') - of ')': - tok.kind = tkParRi - inc(c.bufpos) - add(tok.literal, ')') - of '.': - tok.kind = tkAny - inc(c.bufpos) - add(tok.literal, '.') - of '_': - tok.kind = tkAnyRune - inc(c.bufpos) - add(tok.literal, '_') - of '\\': - getBuiltin(c, tok) - of '\'', '"': getString(c, tok) - of '$': getDollar(c, tok) - of '\0': - tok.kind = tkEof - tok.literal = "[EOF]" - of 'a'..'z', 'A'..'Z', '\128'..'\255': - getSymbol(c, tok) - if c.buf[c.bufpos] in {'\'', '"'} or - c.buf[c.bufpos] == '$' and c.buf[c.bufpos+1] in {'0'..'9'}: - case tok.literal - of "i": tok.modifier = modIgnoreCase - of "y": tok.modifier = modIgnoreStyle - of "v": tok.modifier = modVerbatim - else: discard - setLen(tok.literal, 0) - if c.buf[c.bufpos] == '$': - getDollar(c, tok) - else: - getString(c, tok) - if tok.modifier == modNone: tok.kind = tkInvalid - of '+': - tok.kind = tkPlus - inc(c.bufpos) - add(tok.literal, '+') - of '*': - tok.kind = tkStar - inc(c.bufpos) - add(tok.literal, '+') - of '<': - if c.buf[c.bufpos+1] == '-': - inc(c.bufpos, 2) - tok.kind = tkArrow - add(tok.literal, "<-") - else: - add(tok.literal, '<') - of '/': - tok.kind = tkBar - inc(c.bufpos) - add(tok.literal, '/') - of '?': - tok.kind = tkOption - inc(c.bufpos) - add(tok.literal, '?') - of '!': - tok.kind = tkNot - inc(c.bufpos) - add(tok.literal, '!') - of '&': - tok.kind = tkAmp - inc(c.bufpos) - add(tok.literal, '!') - of '@': - tok.kind = tkAt - inc(c.bufpos) - add(tok.literal, '@') - if c.buf[c.bufpos] == '@': - tok.kind = tkCurlyAt - inc(c.bufpos) - add(tok.literal, '@') - of '^': - tok.kind = tkHat - inc(c.bufpos) - add(tok.literal, '^') - else: - add(tok.literal, c.buf[c.bufpos]) - inc(c.bufpos) - -proc arrowIsNextTok(c: TPegLexer): bool = - # the only look ahead we need - var pos = c.bufpos - while c.buf[pos] in {'\t', ' '}: inc(pos) - result = c.buf[pos] == '<' and c.buf[pos+1] == '-' - -# ----------------------------- parser ---------------------------------------- - -type - EInvalidPeg* = object of ValueError ## raised if an invalid - ## PEG has been detected - TPegParser = object of TPegLexer ## the PEG parser object - tok: TToken - nonterms: seq[PNonTerminal] - modifier: TModifier - captures: int - identIsVerbatim: bool - skip: TPeg - -proc pegError(p: TPegParser, msg: string, line = -1, col = -1) = - var e: ref EInvalidPeg - new(e) - e.msg = errorStr(p, msg, line, col) - raise e - -proc getTok(p: var TPegParser) = - getTok(p, p.tok) - if p.tok.kind == tkInvalid: pegError(p, "invalid token") - -proc eat(p: var TPegParser, kind: TTokKind) = - if p.tok.kind == kind: getTok(p) - else: pegError(p, tokKindToStr[kind] & " expected") - -proc parseExpr(p: var TPegParser): TPeg - -proc getNonTerminal(p: var TPegParser, name: string): PNonTerminal = - for i in 0..high(p.nonterms): - result = p.nonterms[i] - if cmpIgnoreStyle(result.name, name) == 0: return - # forward reference: - result = newNonTerminal(name, getLine(p), getColumn(p)) - add(p.nonterms, result) - -proc modifiedTerm(s: string, m: TModifier): TPeg = - case m - of modNone, modVerbatim: result = term(s) - of modIgnoreCase: result = termIgnoreCase(s) - of modIgnoreStyle: result = termIgnoreStyle(s) - -proc modifiedBackref(s: int, m: TModifier): TPeg = - case m - of modNone, modVerbatim: result = backRef(s) - of modIgnoreCase: result = backRefIgnoreCase(s) - of modIgnoreStyle: result = backRefIgnoreStyle(s) - -proc builtin(p: var TPegParser): TPeg = - # do not use "y", "skip" or "i" as these would be ambiguous - case p.tok.literal - of "n": result = newLine() - of "d": result = charset({'0'..'9'}) - of "D": result = charset({'\1'..'\xff'} - {'0'..'9'}) - of "s": result = charset({' ', '\9'..'\13'}) - of "S": result = charset({'\1'..'\xff'} - {' ', '\9'..'\13'}) - of "w": result = charset({'a'..'z', 'A'..'Z', '_', '0'..'9'}) - of "W": result = charset({'\1'..'\xff'} - {'a'..'z','A'..'Z','_','0'..'9'}) - of "a": result = charset({'a'..'z', 'A'..'Z'}) - of "A": result = charset({'\1'..'\xff'} - {'a'..'z', 'A'..'Z'}) - of "ident": result = tpegs.ident - of "letter": result = UnicodeLetter() - of "upper": result = UnicodeUpper() - of "lower": result = UnicodeLower() - of "title": result = UnicodeTitle() - of "white": result = UnicodeWhitespace() - else: pegError(p, "unknown built-in: " & p.tok.literal) - -proc token(terminal: TPeg, p: TPegParser): TPeg = - if p.skip.kind == pkEmpty: result = terminal - else: result = sequence(p.skip, terminal) - -proc primary(p: var TPegParser): TPeg = - case p.tok.kind - of tkAmp: - getTok(p) - return &primary(p) - of tkNot: - getTok(p) - return !primary(p) - of tkAt: - getTok(p) - return !*primary(p) - of tkCurlyAt: - getTok(p) - return !*\primary(p).token(p) - else: discard - case p.tok.kind - of tkIdentifier: - if p.identIsVerbatim: - var m = p.tok.modifier - if m == modNone: m = p.modifier - result = modifiedTerm(p.tok.literal, m).token(p) - getTok(p) - elif not arrowIsNextTok(p): - var nt = getNonTerminal(p, p.tok.literal) - incl(nt.flags, ntUsed) - result = nonTerminal(nt).token(p) - getTok(p) - else: - pegError(p, "expression expected, but found: " & p.tok.literal) - of tkStringLit: - var m = p.tok.modifier - if m == modNone: m = p.modifier - result = modifiedTerm(p.tok.literal, m).token(p) - getTok(p) - of tkCharSet: - if '\0' in p.tok.charset: - pegError(p, "binary zero ('\\0') not allowed in character class") - result = charset(p.tok.charset).token(p) - getTok(p) - of tkParLe: - getTok(p) - result = parseExpr(p) - eat(p, tkParRi) - of tkCurlyLe: - getTok(p) - result = capture(parseExpr(p)).token(p) - eat(p, tkCurlyRi) - inc(p.captures) - of tkAny: - result = any().token(p) - getTok(p) - of tkAnyRune: - result = anyRune().token(p) - getTok(p) - of tkBuiltin: - result = builtin(p).token(p) - getTok(p) - of tkEscaped: - result = term(p.tok.literal[0]).token(p) - getTok(p) - of tkDollar: - result = endAnchor() - getTok(p) - of tkHat: - result = startAnchor() - getTok(p) - of tkBackref: - var m = p.tok.modifier - if m == modNone: m = p.modifier - result = modifiedBackRef(p.tok.index, m).token(p) - if p.tok.index < 0 or p.tok.index > p.captures: - pegError(p, "invalid back reference index: " & $p.tok.index) - getTok(p) - else: - pegError(p, "expression expected, but found: " & p.tok.literal) - getTok(p) # we must consume a token here to prevent endless loops! - while true: - case p.tok.kind - of tkOption: - result = ?result - getTok(p) - of tkStar: - result = *result - getTok(p) - of tkPlus: - result = +result - getTok(p) - else: break - -proc seqExpr(p: var TPegParser): TPeg = - result = primary(p) - while true: - case p.tok.kind - of tkAmp, tkNot, tkAt, tkStringLit, tkCharset, tkParLe, tkCurlyLe, - tkAny, tkAnyRune, tkBuiltin, tkEscaped, tkDollar, tkBackref, - tkHat, tkCurlyAt: - result = sequence(result, primary(p)) - of tkIdentifier: - if not arrowIsNextTok(p): - result = sequence(result, primary(p)) - else: break - else: break - -proc parseExpr(p: var TPegParser): TPeg = - result = seqExpr(p) - while p.tok.kind == tkBar: - getTok(p) - result = result / seqExpr(p) - -proc parseRule(p: var TPegParser): PNonTerminal = - if p.tok.kind == tkIdentifier and arrowIsNextTok(p): - result = getNonTerminal(p, p.tok.literal) - if ntDeclared in result.flags: - pegError(p, "attempt to redefine: " & result.name) - result.line = getLine(p) - result.col = getColumn(p) - getTok(p) - eat(p, tkArrow) - result.rule = parseExpr(p) - incl(result.flags, ntDeclared) # NOW inlining may be attempted - else: - pegError(p, "rule expected, but found: " & p.tok.literal) - -proc rawParse(p: var TPegParser): TPeg = - ## parses a rule or a PEG expression - while p.tok.kind == tkBuiltin: - case p.tok.literal - of "i": - p.modifier = modIgnoreCase - getTok(p) - of "y": - p.modifier = modIgnoreStyle - getTok(p) - of "skip": - getTok(p) - p.skip = ?primary(p) - else: break - if p.tok.kind == tkIdentifier and arrowIsNextTok(p): - result = parseRule(p).rule - while p.tok.kind != tkEof: - discard parseRule(p) - else: - p.identIsVerbatim = true - result = parseExpr(p) - if p.tok.kind != tkEof: - pegError(p, "EOF expected, but found: " & p.tok.literal) - for i in 0..high(p.nonterms): - var nt = p.nonterms[i] - if ntDeclared notin nt.flags: - pegError(p, "undeclared identifier: " & nt.name, nt.line, nt.col) - elif ntUsed notin nt.flags and i > 0: - pegError(p, "unused rule: " & nt.name, nt.line, nt.col) - -proc parsePeg*(pattern: string, filename = "pattern", line = 1, col = 0): TPeg = - ## constructs a TPeg object from `pattern`. `filename`, `line`, `col` are - ## used for error messages, but they only provide start offsets. `parsePeg` - ## keeps track of line and column numbers within `pattern`. - var p: TPegParser - init(TPegLexer(p), pattern, filename, line, col) - p.tok.kind = tkInvalid - p.tok.modifier = modNone - p.tok.literal = "" - p.tok.charset = {} - p.nonterms = @[] - p.identIsVerbatim = false - getTok(p) - result = rawParse(p) - -proc peg*(pattern: string): TPeg = - ## constructs a TPeg object from the `pattern`. The short name has been - ## chosen to encourage its use as a raw string modifier:: - ## - ## peg"{\ident} \s* '=' \s* {.*}" - result = parsePeg(pattern, "pattern") - -proc escapePeg*(s: string): string = - ## escapes `s` so that it is matched verbatim when used as a peg. - result = "" - var inQuote = false - for c in items(s): - case c - of '\0'..'\31', '\'', '"', '\\': - if inQuote: - result.add('\'') - inQuote = false - result.add("\\x") - result.add(toHex(ord(c), 2)) - else: - if not inQuote: - result.add('\'') - inQuote = true - result.add(c) - if inQuote: result.add('\'') - -when isMainModule: - doAssert escapePeg("abc''def'") == r"'abc'\x27\x27'def'\x27" - #doAssert match("(a b c)", peg"'(' @ ')'") - doAssert match("W_HI_Le", peg"\y 'while'") - doAssert(not match("W_HI_L", peg"\y 'while'")) - doAssert(not match("W_HI_Le", peg"\y v'while'")) - doAssert match("W_HI_Le", peg"y'while'") - - doAssert($ +digits == $peg"\d+") - doAssert "0158787".match(peg"\d+") - doAssert "ABC 0232".match(peg"\w+\s+\d+") - doAssert "ABC".match(peg"\d+ / \w+") - - for word in split("00232this02939is39an22example111", peg"\d+"): - writeLine(stdout, word) - - doAssert matchLen("key", ident) == 3 - - var pattern = sequence(ident, *whitespace, term('='), *whitespace, ident) - doAssert matchLen("key1= cal9", pattern) == 11 - - var ws = newNonTerminal("ws", 1, 1) - ws.rule = *whitespace - - var expr = newNonTerminal("expr", 1, 1) - expr.rule = sequence(capture(ident), *sequence( - nonterminal(ws), term('+'), nonterminal(ws), nonterminal(expr))) - - var c: TCaptures - var s = "a+b + c +d+e+f" - doAssert rawMatch(s, expr.rule, 0, c) == len(s) - var a = "" - for i in 0..c.ml-1: - a.add(substr(s, c.matches[i][0], c.matches[i][1])) - doAssert a == "abcdef" - #echo expr.rule - - #const filename = "lib/devel/peg/grammar.txt" - #var grammar = parsePeg(newFileStream(filename, fmRead), filename) - #echo "a <- [abc]*?".match(grammar) - doAssert find("_____abc_______", term("abc"), 2) == 5 - doAssert match("_______ana", peg"A <- 'ana' / . A") - doAssert match("abcs%%%", peg"A <- ..A / .A / '%'") - - if "abc" =~ peg"{'a'}'bc' 'xyz' / {\ident}": - doAssert matches[0] == "abc" - else: - doAssert false - - var g2 = peg"""S <- A B / C D - A <- 'a'+ - B <- 'b'+ - C <- 'c'+ - D <- 'd'+ - """ - doAssert($g2 == "((A B) / (C D))") - doAssert match("cccccdddddd", g2) - doAssert("var1=key; var2=key2".replacef(peg"{\ident}'='{\ident}", "$1<-$2$2") == - "var1<-keykey; var2<-key2key2") - doAssert "var1=key; var2=key2".endsWith(peg"{\ident}'='{\ident}") - - if "aaaaaa" =~ peg"'aa' !. / ({'a'})+": - doAssert matches[0] == "a" - else: - doAssert false - - block: - var matches: array[0..2, string] - if match("abcdefg", peg"c {d} ef {g}", matches, 2): - doAssert matches[0] == "d" - doAssert matches[1] == "g" - else: - doAssert false - - for x in findAll("abcdef", peg"{.}", 3): - echo x - - if "f(a, b)" =~ peg"{[0-9]+} / ({\ident} '(' {@} ')')": - doAssert matches[0] == "f" - doAssert matches[1] == "a, b" - else: - doAssert false - - doAssert match("eine übersicht und außerdem", peg"(\letter \white*)+") - # ß is not a lower cased letter?! - doAssert match("eine übersicht und auerdem", peg"(\lower \white*)+") - doAssert match("EINE ÜBERSICHT UND AUSSERDEM", peg"(\upper \white*)+") - doAssert(not match("456678", peg"(\letter)+")) - - doAssert("var1 = key; var2 = key2".replacef( - peg"\skip(\s*) {\ident}'='{\ident}", "$1<-$2$2") == - "var1<-keykey;var2<-key2key2") - - doAssert match("prefix/start", peg"^start$", 7) - - # tricky test to check for false aliasing: - block: - var a = term"key" - echo($sequence(sequence(a, term"value"), *a)) - + outp = newStringStream() + processed: seq[string] = @[] + + proc prt(outp: Stream, kind: PegKind, s: string; level: int = 0) = + outp.writeLine indent.repeat(level) & "$1: $2" % [$kind, s] + + proc recLoop(p: Peg, level: int = 0) = + case p.kind + of pkEmpty..pkWhitespace: + discard + of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle: + outp.prt(p.kind, $p, level) + of pkChar, pkGreedyRepChar: + outp.prt(p.kind, $p, level) + of pkCharChoice, pkGreedyRepSet: + outp.prt(p.kind, $p, level) + of pkNonTerminal: + outp.prt(p.kind, + "$1 @($3, $4)" % [p.nt.name, $p.nt.rule.kind, $p.nt.line, $p.nt.col], level) + if not(p.nt.name in processed): + processed.add p.nt.name + p.nt.rule.recLoop level+1 + of pkBackRef..pkBackRefIgnoreStyle: + outp.prt(p.kind, $p, level) + else: + outp.prt(p.kind, $p, level) + for s in items(p): + s.recLoop level+1 + + pegAst.recLoop + echo "PEG AST traversal output" + echo "------------------------" + echo outp.data + +block: + var + pStack: seq[string] = @[] + valStack: seq[float] = @[] + opStack = "" + let + parseArithExpr = pegAst.eventParser: + pkNonTerminal: + enter: + pStack.add p.nt.name + leave: + pStack.setLen pStack.high + if length > 0: + let matchStr = s.substr(start, start+length-1) + case p.nt.name + of "Value": + try: + valStack.add matchStr.parseFloat + echo valStack + except ValueError: + discard + of "Sum", "Product": + try: + let val = matchStr.parseFloat + except ValueError: + if valStack.len > 1 and opStack.len > 0: + valStack[^2] = case opStack[^1] + of '+': valStack[^2] + valStack[^1] + of '-': valStack[^2] - valStack[^1] + of '*': valStack[^2] * valStack[^1] + else: valStack[^2] / valStack[^1] + valStack.setLen valStack.high + echo valStack + opStack.setLen opStack.high + echo opStack + pkChar: + leave: + if length == 1 and "Value" != pStack[^1]: + let matchChar = s[start] + opStack.add matchChar + echo opStack + echo "Event parser output" + echo "-------------------" + let pLen = parseArithExpr(txt) + assert txt.len == pLen diff --git a/tests/stdlib/tropes.nim b/tests/stdlib/tropes.nim new file mode 100644 index 000000000..59239a600 --- /dev/null +++ b/tests/stdlib/tropes.nim @@ -0,0 +1,36 @@ +discard """ + file: "tropes.nim" + output: '''0 +3 + +123 +3 +6 +123 +123456 +2 +3''' +""" +import ropes + +var + r1 = rope("") + r2 = rope("123") + +echo r1.len +echo r2.len + +echo r1 +echo r2 + +r1.add("123") +r2.add("456") + +echo r1.len +echo r2.len + +echo r1 +echo r2 + +echo r1[1] +echo r2[2] \ No newline at end of file diff --git a/tests/stdlib/tstreams3.nim b/tests/stdlib/tstreams3.nim new file mode 100644 index 000000000..b2c9170e3 --- /dev/null +++ b/tests/stdlib/tstreams3.nim @@ -0,0 +1,10 @@ +discard """ + file: "tstreams3.nim" + output: "threw exception" +""" +import streams + +try: + var fs = openFileStream("shouldneverexist.txt") +except IoError: + echo "threw exception" diff --git a/tests/stdlib/tstrformat.nim b/tests/stdlib/tstrformat.nim index 4e5c614a7..db76899d4 100644 --- a/tests/stdlib/tstrformat.nim +++ b/tests/stdlib/tstrformat.nim @@ -10,4 +10,47 @@ proc `$`(o: Obj): string = "foobar" var o: Obj doAssert fmt"{o}" == "foobar" -doAssert fmt"{o:10}" == "foobar " \ No newline at end of file +doAssert fmt"{o:10}" == "foobar " + +# see issue #7933 +var str = "abc" +doAssert fmt">7.1 :: {str:>7.1}" == ">7.1 :: a" +doAssert fmt">7.2 :: {str:>7.2}" == ">7.2 :: ab" +doAssert fmt">7.3 :: {str:>7.3}" == ">7.3 :: abc" +doAssert fmt">7.9 :: {str:>7.9}" == ">7.9 :: abc" +doAssert fmt">7.0 :: {str:>7.0}" == ">7.0 :: " +doAssert fmt" 7.1 :: {str:7.1}" == " 7.1 :: a " +doAssert fmt" 7.2 :: {str:7.2}" == " 7.2 :: ab " +doAssert fmt" 7.3 :: {str:7.3}" == " 7.3 :: abc " +doAssert fmt" 7.9 :: {str:7.9}" == " 7.9 :: abc " +doAssert fmt" 7.0 :: {str:7.0}" == " 7.0 :: " +doAssert fmt"^7.1 :: {str:^7.1}" == "^7.1 :: a " +doAssert fmt"^7.2 :: {str:^7.2}" == "^7.2 :: ab " +doAssert fmt"^7.3 :: {str:^7.3}" == "^7.3 :: abc " +doAssert fmt"^7.9 :: {str:^7.9}" == "^7.9 :: abc " +doAssert fmt"^7.0 :: {str:^7.0}" == "^7.0 :: " +str = "äöüe\u0309\u0319o\u0307\u0359" +doAssert fmt"^7.1 :: {str:^7.1}" == "^7.1 :: ä " +doAssert fmt"^7.2 :: {str:^7.2}" == "^7.2 :: äö " +doAssert fmt"^7.3 :: {str:^7.3}" == "^7.3 :: äöü " +doAssert fmt"^7.0 :: {str:^7.0}" == "^7.0 :: " +# this is actually wrong, but the unicode module has no support for graphemes +doAssert fmt"^7.4 :: {str:^7.4}" == "^7.4 :: äöüe " +doAssert fmt"^7.9 :: {str:^7.9}" == "^7.9 :: äöüe\u0309\u0319o\u0307\u0359" + +# see issue #7932 +doAssert fmt"{15:08}" == "00000015" # int, works +doAssert fmt"{1.5:08}" == "000001.5" # float, works +doAssert fmt"{1.5:0>8}" == "000001.5" # workaround using fill char works for positive floats +doAssert fmt"{-1.5:0>8}" == "0000-1.5" # even that does not work for negative floats +doAssert fmt"{-1.5:08}" == "-00001.5" # works +doAssert fmt"{1.5:+08}" == "+00001.5" # works +doAssert fmt"{1.5: 08}" == " 00001.5" # works + +# only add explicitly requested sign if value != -0.0 (neg zero) +doAssert fmt"{-0.0:g}" == "-0" +doassert fmt"{-0.0:+g}" == "-0" +doassert fmt"{-0.0: g}" == "-0" +doAssert fmt"{0.0:g}" == "0" +doAssert fmt"{0.0:+g}" == "+0" +doAssert fmt"{0.0: g}" == " 0" diff --git a/tests/stdlib/tstrutil.nim b/tests/stdlib/tstrutil.nim index 6f78a91ac..f0ee755f7 100644 --- a/tests/stdlib/tstrutil.nim +++ b/tests/stdlib/tstrutil.nim @@ -7,6 +7,14 @@ discard """ import strutils +import macros + +template rejectParse(e) = + try: + discard e + raise newException(AssertionError, "This was supposed to fail: $#!" % astToStr(e)) + except ValueError: discard + proc testStrip() = write(stdout, strip(" ha ")) @@ -148,7 +156,6 @@ proc testDelete = delete(s, 0, 0) assert s == "1236789ABCDEFG" - proc testIsAlphaNumeric = assert isAlphaNumeric("abcdABC1234") == true assert isAlphaNumeric("a") == true @@ -192,6 +199,12 @@ proc testRFind = assert "0123456789ABCDEFGAH".rfind({'A'..'C'}, 13) == 12 assert "0123456789ABCDEFGAH".rfind({'G'..'H'}, 13) == -1 +proc testSplitLines() = + let fixture = "a\nb\rc\r\nd" + assert len(fixture.splitLines) == 4 + assert splitLines(fixture) == @["a", "b", "c", "d"] + assert splitLines(fixture, keepEol=true) == @["a\n", "b\r", "c\r\n", "d"] + proc testCountLines = proc assertCountLines(s: string) = assert s.countLines == s.splitLines.len assertCountLines("") @@ -203,10 +216,51 @@ proc testCountLines = assertCountLines("\nabc\n123") assertCountLines("\nabc\n123\n") +proc testParseInts = + # binary + assert "0b1111".parseBinInt == 15 + assert "0B1111".parseBinInt == 15 + assert "1111".parseBinInt == 15 + assert "1110".parseBinInt == 14 + assert "1_1_1_1".parseBinInt == 15 + assert "0b1_1_1_1".parseBinInt == 15 + rejectParse "".parseBinInt + rejectParse "_".parseBinInt + rejectParse "0b".parseBinInt + rejectParse "0b1234".parseBinInt + # hex + assert "0x72".parseHexInt == 114 + assert "0X72".parseHexInt == 114 + assert "#72".parseHexInt == 114 + assert "72".parseHexInt == 114 + assert "FF".parseHexInt == 255 + assert "ff".parseHexInt == 255 + assert "fF".parseHexInt == 255 + assert "0x7_2".parseHexInt == 114 + rejectParse "".parseHexInt + rejectParse "_".parseHexInt + rejectParse "0x".parseHexInt + rejectParse "0xFFG".parseHexInt + rejectParse "reject".parseHexInt + # octal + assert "0o17".parseOctInt == 15 + assert "0O17".parseOctInt == 15 + assert "17".parseOctInt == 15 + assert "10".parseOctInt == 8 + assert "0o1_0_0".parseOctInt == 64 + rejectParse "".parseOctInt + rejectParse "_".parseOctInt + rejectParse "0o".parseOctInt + rejectParse "9".parseOctInt + rejectParse "0o9".parseOctInt + rejectParse "reject".parseOctInt + testDelete() testFind() testRFind() +testSplitLines() testCountLines() +testParseInts() assert(insertSep($1000_000) == "1_000_000") assert(insertSep($232) == "232") diff --git a/tests/stdlib/tsugar.nim b/tests/stdlib/tsugar.nim new file mode 100644 index 000000000..a870bf6fe --- /dev/null +++ b/tests/stdlib/tsugar.nim @@ -0,0 +1,29 @@ +discard """ + file: "tsugar.nim" + output: "" +""" +import sugar +import macros + +block distinctBase: + block: + type + Foo[T] = distinct seq[T] + var a: Foo[int] + doAssert a.type.distinctBase is seq[int] + + block: + # simplified from https://github.com/nim-lang/Nim/pull/8531#issuecomment-410436458 + macro uintImpl(bits: static[int]): untyped = + if bits >= 128: + let inner = getAST(uintImpl(bits div 2)) + result = newTree(nnkBracketExpr, ident("UintImpl"), inner) + else: + result = ident("uint64") + + type + BaseUint = UintImpl or SomeUnsignedInt + UintImpl[Baseuint] = object + Uint[bits: static[int]] = distinct uintImpl(bits) + + doAssert Uint[128].distinctBase is UintImpl[uint64] diff --git a/tests/stdlib/ttimes.nim b/tests/stdlib/ttimes.nim index ae056a79f..fcafe933a 100644 --- a/tests/stdlib/ttimes.nim +++ b/tests/stdlib/ttimes.nim @@ -1,135 +1,42 @@ -# test the new time module discard """ file: "ttimes.nim" + target: "c js" output: '''[Suite] ttimes ''' """ -import - times, os, strutils, unittest - -# $ date --date='@2147483647' -# Tue 19 Jan 03:14:07 GMT 2038 - -proc checkFormat(t: DateTime, format, expected: string) = - let actual = t.format(format) - if actual != expected: - echo "Formatting failure!" - echo "expected: ", expected - echo "actual : ", actual - doAssert false - -let t = fromUnix(2147483647).utc -t.checkFormat("ddd dd MMM hh:mm:ss yyyy", "Tue 19 Jan 03:14:07 2038") -t.checkFormat("ddd ddMMMhh:mm:ssyyyy", "Tue 19Jan03:14:072038") -t.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz", - "19 19 Tue Tuesday 3 03 3 03 14 14 1 01 Jan January 7 07 A AM 8 38 038 2038 02038 +0 +00 +00:00") - -t.checkFormat("yyyyMMddhhmmss", "20380119031407") - -let t2 = fromUnix(160070789).utc # Mon 27 Jan 16:06:29 GMT 1975 -t2.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz", - "27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 +0 +00 +00:00") - -var t4 = fromUnix(876124714).utc # Mon 6 Oct 08:58:34 BST 1997 -t4.checkFormat("M MM MMM MMMM", "10 10 Oct October") - -# Interval tests -(t4 - initInterval(years = 2)).checkFormat("yyyy", "1995") -(t4 - initInterval(years = 7, minutes = 34, seconds = 24)).checkFormat("yyyy mm ss", "1990 24 10") - -# checking dayOfWeek matches known days -doAssert getDayOfWeek(01, mJan, 0000) == dSat -doAssert getDayOfWeek(01, mJan, -0023) == dSat -doAssert getDayOfWeek(21, mSep, 1900) == dFri -doAssert getDayOfWeek(01, mJan, 1970) == dThu -doAssert getDayOfWeek(21, mSep, 1970) == dMon -doAssert getDayOfWeek(01, mJan, 2000) == dSat -doAssert getDayOfWeek(01, mJan, 2021) == dFri - -# toUnix tests with GM timezone -let t4L = fromUnix(876124714).utc -doAssert toUnix(toTime(t4L)) == 876124714 -doAssert toUnix(toTime(t4L)) + t4L.utcOffset == toUnix(toTime(t4)) - -# adding intervals -var - a1L = toUnix(toTime(t4L + initInterval(hours = 1))) + t4L.utcOffset - a1G = toUnix(toTime(t4)) + 60 * 60 -doAssert a1L == a1G - -# subtracting intervals -a1L = toUnix(toTime(t4L - initInterval(hours = 1))) + t4L.utcOffset -a1G = toUnix(toTime(t4)) - (60 * 60) -doAssert a1L == a1G - -# add/subtract TimeIntervals and Time/TimeInfo -doAssert getTime() - 1.seconds == getTime() - 3.seconds + 2.seconds -doAssert getTime() + 65.seconds == getTime() + 1.minutes + 5.seconds -doAssert getTime() + 60.minutes == getTime() + 1.hours -doAssert getTime() + 24.hours == getTime() + 1.days -doAssert getTime() + 13.months == getTime() + 1.years + 1.months -var - ti1 = getTime() + 1.years -ti1 -= 1.years -doAssert ti1 == getTime() -ti1 += 1.days -doAssert ti1 == getTime() + 1.days - -# Bug with adding a day to a Time -let day = 24.hours -let tomorrow = getTime() + day -doAssert tomorrow - getTime() == 60*60*24 - -# Comparison between Time objects should be detected by compiler -# as 'noSideEffect'. -proc cmpTimeNoSideEffect(t1: Time, t2: Time): bool {.noSideEffect.} = - result = t1 == t2 -doAssert cmpTimeNoSideEffect(0.fromUnix, 0.fromUnix) -# Additionally `==` generic for seq[T] has explicit 'noSideEffect' pragma -# so we can check above condition by comparing seq[Time] sequences -let seqA: seq[Time] = @[] -let seqB: seq[Time] = @[] -doAssert seqA == seqB - -for tz in [ - (0, "+0", "+00", "+00:00"), # UTC - (-3600, "+1", "+01", "+01:00"), # CET - (-39600, "+11", "+11", "+11:00"), # two digits - (-1800, "+0", "+00", "+00:30"), # half an hour - (7200, "-2", "-02", "-02:00"), # positive - (38700, "-10", "-10", "-10:45")]: # positive with three quaters hour - let ti = DateTime(month: mJan, monthday: 1, utcOffset: tz[0]) - doAssert ti.format("z") == tz[1] - doAssert ti.format("zz") == tz[2] - doAssert ti.format("zzz") == tz[3] - -block countLeapYears: - # 1920, 2004 and 2020 are leap years, and should be counted starting at the following year - doAssert countLeapYears(1920) + 1 == countLeapYears(1921) - doAssert countLeapYears(2004) + 1 == countLeapYears(2005) - doAssert countLeapYears(2020) + 1 == countLeapYears(2021) - -block timezoneConversion: - var l = now() - let u = l.utc - l = u.local - - doAssert l.timezone == local() - doAssert u.timezone == utc() +import times, strutils, unittest + +when not defined(js): + import os + +# Normally testament configures unittest with environment variables, +# but that doesn't work for the JS target. So instead we must set the correct +# settings here. +addOutputFormatter( + newConsoleOutputFormatter(PRINT_FAILURES, colorOutput = false)) + +proc staticTz(hours, minutes, seconds: int = 0): Timezone {.noSideEffect.} = + let offset = hours * 3600 + minutes * 60 + seconds + + proc zonedTimeFromAdjTime(adjTime: Time): ZonedTime {.locks: 0.} = + result.isDst = false + result.utcOffset = offset + result.time = adjTime + initDuration(seconds = offset) + + proc zonedTimeFromTime(time: Time): ZonedTime {.locks: 0.}= + result.isDst = false + result.utcOffset = offset + result.time = time + + newTimezone("", zonedTimeFromTime, zonedTImeFromAdjTime) template parseTest(s, f, sExpected: string, ydExpected: int) = let parsed = s.parse(f, utc()) parsedStr = $parsed check parsedStr == sExpected - if parsed.yearday != ydExpected: - echo s - echo parsed.repr - echo parsed.yearday, " exp: ", ydExpected - check(parsed.yearday == ydExpected) + check parsed.yearday == ydExpected template parseTestExcp(s, f: string) = expect ValueError: @@ -142,51 +49,43 @@ template parseTestTimeOnly(s, f, sExpected: string) = # explicit timezone offsets in all tests. template runTimezoneTests() = parseTest("Tuesday at 09:04am on Dec 15, 2015 +0", - "dddd at hh:mmtt on MMM d, yyyy z", "2015-12-15T09:04:00+00:00", 348) + "dddd 'at' hh:mmtt 'on' MMM d, yyyy z", "2015-12-15T09:04:00Z", 348) # ANSIC = "Mon Jan _2 15:04:05 2006" parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z", - "2006-01-12T15:04:05+00:00", 11) + "2006-01-12T15:04:05Z", 11) # UnixDate = "Mon Jan _2 15:04:05 MST 2006" parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z", - "2006-01-12T15:04:05+00:00", 11) + "2006-01-12T15:04:05Z", 11) # RubyDate = "Mon Jan 02 15:04:05 -0700 2006" parseTest("Mon Feb 29 15:04:05 -07:00 2016 +0", "ddd MMM dd HH:mm:ss zzz yyyy z", - "2016-02-29T15:04:05+00:00", 59) # leap day + "2016-02-29T15:04:05Z", 59) # leap day # RFC822 = "02 Jan 06 15:04 MST" parseTest("12 Jan 16 15:04 +0", "dd MMM yy HH:mm z", - "2016-01-12T15:04:00+00:00", 11) + "2016-01-12T15:04:00Z", 11) # RFC822Z = "02 Jan 06 15:04 -0700" # RFC822 with numeric zone parseTest("01 Mar 16 15:04 -07:00", "dd MMM yy HH:mm zzz", - "2016-03-01T22:04:00+00:00", 60) # day after february in leap year + "2016-03-01T22:04:00Z", 60) # day after february in leap year # RFC850 = "Monday, 02-Jan-06 15:04:05 MST" parseTest("Monday, 12-Jan-06 15:04:05 +0", "dddd, dd-MMM-yy HH:mm:ss z", - "2006-01-12T15:04:05+00:00", 11) + "2006-01-12T15:04:05Z", 11) # RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" parseTest("Sun, 01 Mar 2015 15:04:05 +0", "ddd, dd MMM yyyy HH:mm:ss z", - "2015-03-01T15:04:05+00:00", 59) # day after february in non-leap year + "2015-03-01T15:04:05Z", 59) # day after february in non-leap year # RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" # RFC1123 with numeric zone parseTest("Thu, 12 Jan 2006 15:04:05 -07:00", "ddd, dd MMM yyyy HH:mm:ss zzz", - "2006-01-12T22:04:05+00:00", 11) + "2006-01-12T22:04:05Z", 11) # RFC3339 = "2006-01-02T15:04:05Z07:00" - parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-ddTHH:mm:ssZzzz", - "2006-01-12T22:04:05+00:00", 11) parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-dd'T'HH:mm:ss'Z'zzz", - "2006-01-12T22:04:05+00:00", 11) + "2006-01-12T22:04:05Z", 11) # RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" parseTest("2006-01-12T15:04:05.999999999Z-07:00", - "yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "2006-01-12T22:04:05+00:00", 11) + "yyyy-MM-dd'T'HH:mm:ss'.999999999Z'zzz", "2006-01-12T22:04:05Z", 11) for tzFormat in ["z", "zz", "zzz"]: # formatting timezone as 'Z' for UTC parseTest("2001-01-12T22:04:05Z", "yyyy-MM-dd'T'HH:mm:ss" & tzFormat, - "2001-01-12T22:04:05+00:00", 11) + "2001-01-12T22:04:05Z", 11) # Kitchen = "3:04PM" parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00") - #when not defined(testing): - # echo "Kitchen: " & $s.parse(f) - # var ti = timeToTimeInfo(getTime()) - # echo "Todays date after decoding: ", ti - # var tint = timeToTimeInterval(getTime()) - # echo "Todays date after decoding to interval: ", tint # Bug with parse not setting DST properly if the current local DST differs from # the date being parsed. Need to test parse dates both in and out of DST. We @@ -207,20 +106,20 @@ template runTimezoneTests() = let parsedJan = parse("2016-01-05 04:00:00+01:00", "yyyy-MM-dd HH:mm:sszzz") parsedJul = parse("2016-07-01 04:00:00+01:00", "yyyy-MM-dd HH:mm:sszzz") - doAssert toTime(parsedJan) == fromUnix(1451962800) - doAssert toTime(parsedJul) == fromUnix(1467342000) + check toTime(parsedJan).toUnix == 1451962800 + check toTime(parsedJul).toUnix == 1467342000 suite "ttimes": # Generate tests for multiple timezone files where available # Set the TZ env var for each test - when defined(Linux) or defined(macosx): + when defined(linux) or defined(macosx): const tz_dir = "/usr/share/zoneinfo" const f = "yyyy-MM-dd HH:mm zzz" let orig_tz = getEnv("TZ") var tz_cnt = 0 - for tz_fn in walkFiles(tz_dir & "/*"): + for tz_fn in walkFiles(tz_dir & "/**/*"): if symlinkExists(tz_fn) or tz_fn.endsWith(".tab") or tz_fn.endsWith(".list"): continue @@ -252,22 +151,23 @@ suite "ttimes": var local = fromUnix(1469275200).local var utc = fromUnix(1469275200).utc - let claimedOffset = local.utcOffset + let claimedOffset = initDuration(seconds = local.utcOffset) local.utcOffset = 0 check claimedOffset == utc.toTime - local.toTime test "issue #5704": putEnv("TZ", "Asia/Seoul") let diff = parse("19700101-000000", "yyyyMMdd-hhmmss").toTime - parse("19000101-000000", "yyyyMMdd-hhmmss").toTime - check diff == 2208986872 + check diff == initDuration(seconds = 2208986872) test "issue #6465": putEnv("TZ", "Europe/Stockholm") let dt = parse("2017-03-25 12:00", "yyyy-MM-dd hh:mm") - check $(dt + 1.days) == "2017-03-26T12:00:00+02:00" + check $(dt + initTimeInterval(days = 1)) == "2017-03-26T12:00:00+02:00" + check $(dt + initDuration(days = 1)) == "2017-03-26T13:00:00+02:00" test "datetime before epoch": - check $fromUnix(-2147483648).utc == "1901-12-13T20:45:52+00:00" + check $fromUnix(-2147483648).utc == "1901-12-13T20:45:52Z" test "adding/subtracting time across dst": putenv("TZ", "Europe/Stockholm") @@ -281,7 +181,7 @@ suite "ttimes": putEnv("TZ", orig_tz) else: - # not on Linux or macosx: run one parseTest only + # not on Linux or macosx: run in the local timezone only test "parseTest": runTimezoneTests() @@ -330,28 +230,22 @@ suite "ttimes": test "incorrect inputs: timezone (zzz) 3": parseTestExcp("2018-02-19 16:30:00 +01:0", "yyyy-MM-dd hh:mm:ss zzz") + test "incorrect inputs: year (yyyy/uuuu)": + parseTestExcp("-0001", "yyyy") + parseTestExcp("-0001", "YYYY") + parseTestExcp("1", "yyyy") + parseTestExcp("12345", "yyyy") + parseTestExcp("1", "uuuu") + parseTestExcp("12345", "uuuu") + parseTestExcp("-1 BC", "UUUU g") + test "dynamic timezone": - proc staticOffset(offset: int): Timezone = - proc zoneInfoFromTz(adjTime: Time): ZonedTime = - result.isDst = false - result.utcOffset = offset - result.adjTime = adjTime - - proc zoneInfoFromUtc(time: Time): ZonedTime = - result.isDst = false - result.utcOffset = offset - result.adjTime = fromUnix(time.toUnix - offset) - - result.name = "" - result.zoneInfoFromTz = zoneInfoFromTz - result.zoneInfoFromUtc = zoneInfoFromUtc - - let tz = staticOffset(-9000) + let tz = staticTz(seconds = -9000) let dt = initDateTime(1, mJan, 2000, 12, 00, 00, tz) check dt.utcOffset == -9000 check dt.isDst == false check $dt == "2000-01-01T12:00:00+02:30" - check $dt.utc == "2000-01-01T09:30:00+00:00" + check $dt.utc == "2000-01-01T09:30:00Z" check $dt.utc.inZone(tz) == $dt test "isLeapYear": @@ -360,11 +254,217 @@ suite "ttimes": check isLeapYear(2000) check (not isLeapYear(1900)) - test "subtract months": + test "TimeInterval": + let t = fromUnix(876124714).utc # Mon 6 Oct 08:58:34 BST 1997 + # Interval tests + let t2 = t - 2.years + check t2.year == 1995 + let t3 = (t - 7.years - 34.minutes - 24.seconds) + check t3.year == 1990 + check t3.minute == 24 + check t3.second == 10 + check (t + 1.hours).toTime.toUnix == t.toTime.toUnix + 60 * 60 + check (t - 1.hours).toTime.toUnix == t.toTime.toUnix - 60 * 60 + + test "TimeInterval - months": var dt = initDateTime(1, mFeb, 2017, 00, 00, 00, utc()) - check $(dt - 1.months) == "2017-01-01T00:00:00+00:00" + check $(dt - initTimeInterval(months = 1)) == "2017-01-01T00:00:00Z" dt = initDateTime(15, mMar, 2017, 00, 00, 00, utc()) - check $(dt - 1.months) == "2017-02-15T00:00:00+00:00" + check $(dt - initTimeInterval(months = 1)) == "2017-02-15T00:00:00Z" dt = initDateTime(31, mMar, 2017, 00, 00, 00, utc()) # This happens due to monthday overflow. It's consistent with Phobos. - check $(dt - 1.months) == "2017-03-03T00:00:00+00:00" \ No newline at end of file + check $(dt - initTimeInterval(months = 1)) == "2017-03-03T00:00:00Z" + + test "duration": + let d = initDuration + check d(hours = 48) + d(days = 5) == d(weeks = 1) + let dt = initDateTime(01, mFeb, 2000, 00, 00, 00, 0, utc()) + d(milliseconds = 1) + check dt.nanosecond == convert(Milliseconds, Nanoseconds, 1) + check d(seconds = 1, milliseconds = 500) * 2 == d(seconds = 3) + check d(seconds = 3) div 2 == d(seconds = 1, milliseconds = 500) + check d(milliseconds = 1001).seconds == 1 + check d(seconds = 1, milliseconds = 500) - d(milliseconds = 1250) == + d(milliseconds = 250) + check d(seconds = 1, milliseconds = 1) < d(seconds = 1, milliseconds = 2) + check d(seconds = 1) <= d(seconds = 1) + check d(seconds = 0) - d(milliseconds = 1500) == d(milliseconds = -1500) + check d(milliseconds = -1500) == d(seconds = -1, milliseconds = -500) + check d(seconds = -1, milliseconds = 500) == d(milliseconds = -500) + check initDuration(seconds = 1, nanoseconds = 2) <= + initDuration(seconds = 1, nanoseconds = 3) + check (initDuration(seconds = 1, nanoseconds = 3) <= + initDuration(seconds = 1, nanoseconds = 1)).not + + test "large/small dates": + discard initDateTime(1, mJan, -35_000, 12, 00, 00, utc()) + # with local tz + discard initDateTime(1, mJan, -35_000, 12, 00, 00) + discard initDateTime(1, mJan, 35_000, 12, 00, 00) + # with duration/timeinterval + let dt = initDateTime(1, mJan, -35_000, 12, 00, 00, utc()) + + initDuration(seconds = 1) + check dt.second == 1 + let dt2 = dt + 35_001.years + check $dt2 == "0001-01-01T12:00:01Z" + + test "compare datetimes": + var dt1 = now() + var dt2 = dt1 + check dt1 == dt2 + check dt1 <= dt2 + dt2 = dt2 + 1.seconds + check dt1 < dt2 + + test "adding/subtracting TimeInterval": + # add/subtract TimeIntervals and Time/TimeInfo + let now = getTime().utc + check now + convert(Seconds, Nanoseconds, 1).nanoseconds == now + 1.seconds + check now + 1.weeks == now + 7.days + check now - 1.seconds == now - 3.seconds + 2.seconds + check now + 65.seconds == now + 1.minutes + 5.seconds + check now + 60.minutes == now + 1.hours + check now + 24.hours == now + 1.days + check now + 13.months == now + 1.years + 1.months + check toUnix(fromUnix(0) + 2.seconds) == 2 + check toUnix(fromUnix(0) - 2.seconds) == -2 + var ti1 = now + 1.years + ti1 = ti1 - 1.years + check ti1 == now + ti1 = ti1 + 1.days + check ti1 == now + 1.days + + # Bug with adding a day to a Time + let day = 24.hours + let tomorrow = now + day + check tomorrow - now == initDuration(days = 1) + + # Disabled for JS because it fails due to precision errors + # (The JS target uses float64 for int64). + when not defined(js): + test "fromWinTime/toWinTime": + check 0.fromUnix.toWinTime.fromWinTime.toUnix == 0 + check (-1).fromWinTime.nanosecond == convert(Seconds, Nanoseconds, 1) - 100 + check (-1).fromWinTime.toWinTime == -1 + # One nanosecond is discarded due to differences in time resolution + check initTime(0, 101).toWinTime.fromWinTime.nanosecond == 100 + check initTime(0, 101).toWinTime.fromWinTime.nanosecond == 100 + + test "issue 7620": + let layout = "M/d/yyyy' 'h:mm:ss' 'tt' 'z" + let t7620_am = parse("4/15/2017 12:01:02 AM +0", layout, utc()) + check t7620_am.format(layout) == "4/15/2017 12:01:02 AM Z" + let t7620_pm = parse("4/15/2017 12:01:02 PM +0", layout, utc()) + check t7620_pm.format(layout) == "4/15/2017 12:01:02 PM Z" + + test "format": + var dt = initDateTime(1, mJan, -0001, + 17, 01, 02, 123_456_789, + staticTz(hours = 1, minutes = 2, seconds = 3)) + check dt.format("d") == "1" + check dt.format("dd") == "01" + check dt.format("ddd") == "Fri" + check dt.format("dddd") == "Friday" + check dt.format("h") == "5" + check dt.format("hh") == "05" + check dt.format("H") == "17" + check dt.format("HH") == "17" + check dt.format("m") == "1" + check dt.format("mm") == "01" + check dt.format("M") == "1" + check dt.format("MM") == "01" + check dt.format("MMM") == "Jan" + check dt.format("MMMM") == "January" + check dt.format("s") == "2" + check dt.format("ss") == "02" + check dt.format("t") == "P" + check dt.format("tt") == "PM" + check dt.format("yy") == "02" + check dt.format("yyyy") == "0002" + check dt.format("YYYY") == "2" + check dt.format("uuuu") == "-0001" + check dt.format("UUUU") == "-1" + check dt.format("z") == "-1" + check dt.format("zz") == "-01" + check dt.format("zzz") == "-01:02" + check dt.format("zzzz") == "-01:02:03" + check dt.format("g") == "BC" + + check dt.format("fff") == "123" + check dt.format("ffffff") == "123456" + check dt.format("fffffffff") == "123456789" + dt.nanosecond = 1 + check dt.format("fff") == "000" + check dt.format("ffffff") == "000000" + check dt.format("fffffffff") == "000000001" + + dt.year = 12345 + check dt.format("yyyy") == "+12345" + check dt.format("uuuu") == "+12345" + dt.year = -12345 + check dt.format("yyyy") == "+12346" + check dt.format("uuuu") == "-12345" + + expect ValueError: + discard initTimeFormat("'") + + expect ValueError: + discard initTimeFormat("'foo") + + expect ValueError: + discard initTimeFormat("foo'") + + for tz in [ + (staticTz(seconds = 0), "+0", "+00", "+00:00"), # UTC + (staticTz(seconds = -3600), "+1", "+01", "+01:00"), # CET + (staticTz(seconds = -39600), "+11", "+11", "+11:00"), # two digits + (staticTz(seconds = -1800), "+0", "+00", "+00:30"), # half an hour + (staticTz(seconds = 7200), "-2", "-02", "-02:00"), # positive + (staticTz(seconds = 38700), "-10", "-10", "-10:45")]: # positive with three quaters hour + let dt = initDateTime(1, mJan, 2000, 00, 00, 00, tz[0]) + doAssert dt.format("z") == tz[1] + doAssert dt.format("zz") == tz[2] + doAssert dt.format("zzz") == tz[3] + + test "parse": + check $parse("20180101", "yyyyMMdd", utc()) == "2018-01-01T00:00:00Z" + parseTestExcp("+120180101", "yyyyMMdd") + + check parse("1", "YYYY", utc()).year == 1 + check parse("1 BC", "YYYY g", utc()).year == 0 + check parse("0001 BC", "yyyy g", utc()).year == 0 + check parse("+12345 BC", "yyyy g", utc()).year == -12344 + check parse("1 AD", "YYYY g", utc()).year == 1 + check parse("0001 AD", "yyyy g", utc()).year == 1 + check parse("+12345 AD", "yyyy g", utc()).year == 12345 + + check parse("-1", "UUUU", utc()).year == -1 + check parse("-0001", "uuuu", utc()).year == -1 + + discard parse("foobar", "'foobar'") + discard parse("foo'bar", "'foo''''bar'") + discard parse("'", "''") + + parseTestExcp("2000 A", "yyyy g") + + test "countLeapYears": + # 1920, 2004 and 2020 are leap years, and should be counted starting at the following year + check countLeapYears(1920) + 1 == countLeapYears(1921) + check countLeapYears(2004) + 1 == countLeapYears(2005) + check countLeapYears(2020) + 1 == countLeapYears(2021) + + test "timezoneConversion": + var l = now() + let u = l.utc + l = u.local + + check l.timezone == local() + check u.timezone == utc() + + test "getDayOfWeek": + check getDayOfWeek(01, mJan, 0000) == dSat + check getDayOfWeek(01, mJan, -0023) == dSat + check getDayOfWeek(21, mSep, 1900) == dFri + check getDayOfWeek(01, mJan, 1970) == dThu + check getDayOfWeek(21, mSep, 1970) == dMon + check getDayOfWeek(01, mJan, 2000) == dSat + check getDayOfWeek(01, mJan, 2021) == dFri diff --git a/tests/stdlib/tunittest.nim b/tests/stdlib/tunittest.nim index 86b9fd037..c8656bbff 100644 --- a/tests/stdlib/tunittest.nim +++ b/tests/stdlib/tunittest.nim @@ -13,6 +13,8 @@ discard """ [Suite] bug #5784 +[Suite] test suite + [Suite] test name filtering ''' @@ -123,6 +125,23 @@ suite "bug #5784": var obj: Obj check obj.isNil or obj.field == 0 +type + SomeType = object + value: int + children: seq[SomeType] + +# bug #5252 + +proc `==`(a, b: SomeType): bool = + return a.value == b.value + +suite "test suite": + test "test": + let a = SomeType(value: 10) + let b = SomeType(value: 10) + + check(a == b) + when defined(testing): suite "test name filtering": test "test name": diff --git a/tests/stdlib/tunittestexceptiontype.nim b/tests/stdlib/tunittestexceptiontype.nim new file mode 100644 index 000000000..e05a25409 --- /dev/null +++ b/tests/stdlib/tunittestexceptiontype.nim @@ -0,0 +1,10 @@ +discard """ + exitcode: 1 + outputsub: '''exception type is [ValueError]''' +""" + +import unittest + +suite "exception from test": + test "show exception type": + raise newException(ValueError, "exception type is") diff --git a/tests/stdlib/twchartoutf8.nim b/tests/stdlib/twchartoutf8.nim index b2f68ee32..a6602e3e3 100644 --- a/tests/stdlib/twchartoutf8.nim +++ b/tests/stdlib/twchartoutf8.nim @@ -30,7 +30,6 @@ else: result = newString(size) let res = WideCharToMultiByte(CP_UTF8, 0'i32, cast[LPWCSTR](addr(wc[0])), wclen, cstring(result), size, cstring(nil), LPBOOL(nil)) - result[size] = chr(0) doAssert size == res proc testCP(wc: WideCString, lo, hi: int) = diff --git a/tests/system/alloc.nim b/tests/system/alloc.nim deleted file mode 100644 index 7abefec2a..000000000 --- a/tests/system/alloc.nim +++ /dev/null @@ -1,52 +0,0 @@ -var x: ptr int - -x = cast[ptr int](alloc(7)) -assert x != nil -x = cast[ptr int](x.realloc(2)) -assert x != nil -x.dealloc() - -x = createU(int, 3) -assert x != nil -x.free() - -x = create(int, 4) -assert cast[ptr array[4, int]](x)[0] == 0 -assert cast[ptr array[4, int]](x)[1] == 0 -assert cast[ptr array[4, int]](x)[2] == 0 -assert cast[ptr array[4, int]](x)[3] == 0 - -x = x.resize(4) -assert x != nil -x.free() - -x = cast[ptr int](allocShared(100)) -assert x != nil -deallocShared(x) - -x = createSharedU(int, 3) -assert x != nil -x.freeShared() - -x = createShared(int, 3) -assert x != nil -assert cast[ptr array[3, int]](x)[0] == 0 -assert cast[ptr array[3, int]](x)[1] == 0 -assert cast[ptr array[3, int]](x)[2] == 0 - -assert x != nil -x = cast[ptr int](x.resizeShared(2)) -assert x != nil -x.freeShared() - -x = create(int, 10) -assert x != nil -x = x.resize(12) -assert x != nil -x.dealloc() - -x = createShared(int, 1) -assert x != nil -x = x.resizeShared(1) -assert x != nil -x.freeShared() diff --git a/tests/system/params.nim b/tests/system/params.nim deleted file mode 100644 index 1358212f2..000000000 --- a/tests/system/params.nim +++ /dev/null @@ -1,18 +0,0 @@ -import os -import osproc -import parseopt2 -import sequtils - -let argv = commandLineParams() - -if argv == @[]: - # this won't work with spaces - assert execShellCmd(getAppFilename() & " \"foo bar\" --aa:bar=a --a=c:d --ab -c --a[baz]:doo") == 0 -else: - let f = toSeq(getopt()) - echo f.repr - assert f[0].kind == cmdArgument and f[0].key == "foo bar" and f[0].val == "" - assert f[1].kind == cmdLongOption and f[1].key == "aa" and f[1].val == "bar=a" - assert f[2].kind == cmdLongOption and f[2].key == "a=c" and f[2].val == "d" - assert f[3].kind == cmdLongOption and f[3].key == "ab" and f[3].val == "" - assert f[4].kind == cmdShortOption and f[4].key == "c" and f[4].val == "" diff --git a/tests/system/t7894.nim b/tests/system/t7894.nim new file mode 100644 index 000000000..2808e5020 --- /dev/null +++ b/tests/system/t7894.nim @@ -0,0 +1,18 @@ +discard """ +""" + +import os + +const size = 250000000 +var saved = newSeq[seq[int8]]() + +for i in 0..22: + # one of these is 0.25GB. + #echo i + var x = newSeq[int8](size) + sleep(10) + saved.add(x) + +for x in saved: + #echo x.len + doAssert x.len == size diff --git a/tests/system/talloc.nim b/tests/system/talloc.nim new file mode 100644 index 000000000..bf2cd97a8 --- /dev/null +++ b/tests/system/talloc.nim @@ -0,0 +1,60 @@ +discard """ +""" + +var x: ptr int + +x = cast[ptr int](alloc(7)) +doAssert x != nil +x = cast[ptr int](x.realloc(2)) +doAssert x != nil +x.dealloc() + +x = createU(int, 3) +doAssert x != nil +x.dealloc() + +x = create(int, 4) +doAssert cast[ptr array[4, int]](x)[0] == 0 +doAssert cast[ptr array[4, int]](x)[1] == 0 +doAssert cast[ptr array[4, int]](x)[2] == 0 +doAssert cast[ptr array[4, int]](x)[3] == 0 + +x = x.resize(4) +doAssert x != nil +x.dealloc() + +x = cast[ptr int](allocShared(100)) +doAssert x != nil +deallocShared(x) + +x = createSharedU(int, 3) +doAssert x != nil +x.deallocShared() + +x = createShared(int, 3) +doAssert x != nil +doAssert cast[ptr array[3, int]](x)[0] == 0 +doAssert cast[ptr array[3, int]](x)[1] == 0 +doAssert cast[ptr array[3, int]](x)[2] == 0 + +doAssert x != nil +x = cast[ptr int](x.resizeShared(2)) +doAssert x != nil +x.deallocShared() + +x = create(int, 10) +doAssert x != nil +x = x.resize(12) +doAssert x != nil +x.dealloc() + +x = createShared(int, 1) +doAssert x != nil +x = x.resizeShared(1) +doAssert x != nil +x.deallocShared() + +x = cast[ptr int](alloc0(125 shl 23)) +dealloc(x) +x = cast[ptr int](alloc0(126 shl 23)) +dealloc(x) diff --git a/tests/system/talloc2.nim b/tests/system/talloc2.nim new file mode 100644 index 000000000..0757c0724 --- /dev/null +++ b/tests/system/talloc2.nim @@ -0,0 +1,40 @@ +discard """ +""" + +const + nmax = 2*1024*1024*1024 + +proc test(n: int) = + var a = alloc0(9999) + var t = cast[ptr UncheckedArray[int8]](alloc(n)) + var b = alloc0(9999) + t[0] = 1 + t[1] = 2 + t[n-2] = 3 + t[n-1] = 4 + dealloc(a) + dealloc(t) + dealloc(b) + +# allocator adds 48 bytes to BigChunk +# BigChunk allocator edges at 2^n * (1 - s) for s = [1..32]/64 +proc test2(n: int) = + let d = n div 256 # cover edges and more + for i in countdown(128,1): + for j in [-4096, -64, -49, -48, -47, -32, 0, 4096]: + let b = n + j - i*d + if b>0 and b<=nmax: + test(b) + #echo b, ": ", getTotalMem(), " ", getOccupiedMem(), " ", getFreeMem() + +proc test3 = + var n = 1 + while n <= nmax: + test2(n) + n *= 2 + n = nmax + while n >= 1: + test2(n) + n = n div 2 + +test3() diff --git a/tests/system/tdeepcopy.nim b/tests/system/tdeepcopy.nim index f7a6e87fa..383d2e8d1 100644 --- a/tests/system/tdeepcopy.nim +++ b/tests/system/tdeepcopy.nim @@ -65,7 +65,7 @@ proc main() = for val in table.values(): if myObj2.isNil: myObj2 = val - assert(myObj == myObj2) # passes + doAssert(myObj == myObj2) # passes var tableCopy: ListTableRef[int, SomeObj] deepCopy(tableCopy, table) @@ -80,7 +80,7 @@ proc main() = #echo cast[int](myObjCopy) #echo cast[int](myObjCopy2) - assert(myObjCopy == myObjCopy2) # fails + doAssert(myObjCopy == myObjCopy2) # fails type @@ -88,7 +88,7 @@ type counter, max: int data: array[0..99, (pointer, pointer)] -assert(sizeof(PtrTable) == 2*sizeof(int)+sizeof(pointer)*2*100) +doAssert(sizeof(PtrTable) == 2*sizeof(int)+sizeof(pointer)*2*100) main() echo "ok" diff --git a/tests/system/io.nim b/tests/system/tio.nim index 3d4df806b..7e9e18950 100644 --- a/tests/system/io.nim +++ b/tests/system/tio.nim @@ -1,3 +1,6 @@ +discard """ +""" + import unittest, osproc, streams, os, strformat const STRING_DATA = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." @@ -36,7 +39,7 @@ proc verifyFileSize(sz: int64) = discard execProcess(&"dd if=/dev/zero of={fn} bs=1000000 count={size_in_mb}") doAssert os.getFileSize(fn) == sz # Verify OS filesize by string - + var f = open(fn) doAssert f.getFileSize() == sz # Verify file handle filesize f.close() diff --git a/tests/system/tnilconcats.nim b/tests/system/tnilconcats.nim new file mode 100644 index 000000000..5e4a1b317 --- /dev/null +++ b/tests/system/tnilconcats.nim @@ -0,0 +1,25 @@ +discard """ + output: '''@["", "", "", "", "", "", "", "meh"]''' + exitcode: "0" +""" + +when true: + var ab: string + ab &= "more" + + doAssert ab == "more" + + var x: seq[string] + + setLen(x, 7) + + x.add "meh" + + var s: string + var z = "abc" + var zz: string + s &= "foo" & z & zz + + doAssert s == "fooabc" + + echo x diff --git a/tests/system/toString.nim b/tests/system/toString.nim deleted file mode 100644 index ea9d6b05b..000000000 --- a/tests/system/toString.nim +++ /dev/null @@ -1,53 +0,0 @@ -discard """ - output:"" -""" - -doAssert "@[23, 45]" == $(@[23, 45]) -doAssert "[32, 45]" == $([32, 45]) -doAssert """@["", "foo", "bar"]""" == $(@["", "foo", "bar"]) -doAssert """["", "foo", "bar"]""" == $(["", "foo", "bar"]) - -# bug #2395 -let alphaSet: set[char] = {'a'..'c'} -doAssert "{'a', 'b', 'c'}" == $alphaSet -doAssert "2.3242" == $(2.3242) -doAssert "2.982" == $(2.982) -doAssert "123912.1" == $(123912.1) -doAssert "123912.1823" == $(123912.1823) -doAssert "5.0" == $(5.0) -doAssert "1e+100" == $(1e100) -doAssert "inf" == $(1e1000000) -doAssert "-inf" == $(-1e1000000) -doAssert "nan" == $(0.0/0.0) - -# nil tests -# maybe a bit inconsistent in types -var x: seq[string] -doAssert "nil" == $(x) - -var y: string = nil -doAssert nil == $(y) - -type - Foo = object - a: int - b: string - -var foo1: Foo - -# nil string should be an some point in time equal to the empty string -doAssert(($foo1)[0..9] == "(a: 0, b: ") - -const - data = @['a','b', '\0', 'c','d'] - dataStr = $data - -# ensure same result when on VM or when at program execution -doAssert dataStr == $data - -import strutils -# array test - -let arr = ['H','e','l','l','o',' ','W','o','r','l','d','!','\0'] -doAssert $arr == "['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\\x00']" -doAssert $cstring(unsafeAddr arr) == "Hello World!" diff --git a/tests/system/tostring.nim b/tests/system/tostring.nim new file mode 100644 index 000000000..04b37f133 --- /dev/null +++ b/tests/system/tostring.nim @@ -0,0 +1,117 @@ +discard """ + output: "" +""" + +doAssert "@[23, 45]" == $(@[23, 45]) +doAssert "[32, 45]" == $([32, 45]) +doAssert """@["", "foo", "bar"]""" == $(@["", "foo", "bar"]) +doAssert """["", "foo", "bar"]""" == $(["", "foo", "bar"]) +doAssert """["", "foo", "bar"]""" == $(@["", "foo", "bar"].toOpenArray(0, 2)) + +# bug #2395 +let alphaSet: set[char] = {'a'..'c'} +doAssert "{'a', 'b', 'c'}" == $alphaSet +doAssert "2.3242" == $(2.3242) +doAssert "2.982" == $(2.982) +doAssert "123912.1" == $(123912.1) +doAssert "123912.1823" == $(123912.1823) +doAssert "5.0" == $(5.0) +doAssert "1e+100" == $(1e100) +doAssert "inf" == $(1e1000000) +doAssert "-inf" == $(-1e1000000) +doAssert "nan" == $(0.0/0.0) + +# nil tests +# maybe a bit inconsistent in types +var x: seq[string] +doAssert "@[]" == $(x) + +var y: string +doAssert "" == $(y) + +type + Foo = object + a: int + b: string + +var foo1: Foo + +# nil string should be an some point in time equal to the empty string +doAssert(($foo1)[0..9] == "(a: 0, b: ") + +const + data = @['a','b', '\0', 'c','d'] + dataStr = $data + +# ensure same result when on VM or when at program execution +doAssert dataStr == $data + +import strutils +# array test + +let arr = ['H','e','l','l','o',' ','W','o','r','l','d','!','\0'] +doAssert $arr == "['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\\x00']" +doAssert $cstring(unsafeAddr arr) == "Hello World!" + +proc takes(c: cstring) = + doAssert c == cstring"" + +proc testm() = + var x: string + # nil is mapped to "": + takes(x) + +testm() + +# nil tests +var xx: seq[string] +var yy: string +doAssert xx == @[] +doAssert yy == "" + +proc bar(arg: cstring) = + doAssert arg[0] == '\0' + +proc baz(arg: openarray[char]) = + doAssert arg.len == 0 + +proc stringCompare() = + var a,b,c,d,e,f,g: string + a.add 'a' + doAssert a == "a" + b.add "bee" + doAssert b == "bee" + b.add g + doAssert b == "bee" + c.add 123.456 + doAssert c == "123.456" + d.add 123456 + doAssert d == "123456" + + doAssert e == "" + doAssert "" == e + doAssert f == g + doAssert "" == "" + + g.setLen(10) + doAssert g == "\0\0\0\0\0\0\0\0\0\0" + doAssert "" != "\0\0\0\0\0\0\0\0\0\0" + + var nilstring: string + #bar(nilstring) + baz(nilstring) + +stringCompare() +var nilstring: string +bar(nilstring) + +static: + stringCompare() + +# bug 8847 +var a2: cstring = "fo\"o2" + +block: + var s: string + s.addQuoted a2 + doAssert s == "\"fo\\\"o2\"" diff --git a/tests/system/tparams.nim b/tests/system/tparams.nim new file mode 100644 index 000000000..dd5511b8f --- /dev/null +++ b/tests/system/tparams.nim @@ -0,0 +1,21 @@ +discard """ +""" + +import os +import osproc +import parseopt2 +import sequtils + +let argv = commandLineParams() + +if argv == @[]: + # this won't work with spaces + doAssert execShellCmd(getAppFilename() & " \"foo bar\" --aa:bar=a --a=c:d --ab -c --a[baz]:doo") == 0 +else: + let f = toSeq(getopt()) + echo f.repr + doAssert f[0].kind == cmdArgument and f[0].key == "foo bar" and f[0].val == "" + doAssert f[1].kind == cmdLongOption and f[1].key == "aa" and f[1].val == "bar=a" + doAssert f[2].kind == cmdLongOption and f[2].key == "a=c" and f[2].val == "d" + doAssert f[3].kind == cmdLongOption and f[3].key == "ab" and f[3].val == "" + doAssert f[4].kind == cmdShortOption and f[4].key == "c" and f[4].val == "" diff --git a/tests/system/tsystem_misc.nim b/tests/system/tsystem_misc.nim index ce36895a1..ce36bc9ef 100644 --- a/tests/system/tsystem_misc.nim +++ b/tests/system/tsystem_misc.nim @@ -1,5 +1,31 @@ discard """ - output:"" + output:'''1 +1 +2 +3 +11 +12 +13 +14 +15 +2 +3 +4 +2 +1 +2 +3 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +''' """ # check high/low implementations @@ -20,3 +46,59 @@ doAssert high(float64) > low(float64) # bug #6710 var s = @[1] s.delete(0) + + +proc foo(a: openArray[int]) = + for x in a: echo x + +foo(toOpenArray([1, 2, 3], 0, 0)) + +foo(toOpenArray([1, 2, 3], 0, 2)) + +var arr: array[8..12, int] = [11, 12, 13, 14, 15] + +foo(toOpenArray(arr, 8, 12)) + +var seqq = @[1, 2, 3, 4, 5] +foo(toOpenArray(seqq, 1, 3)) + +# empty openArray issue #7904 +foo(toOpenArray(seqq, 0, -1)) +foo(toOpenArray(seqq, 1, 0)) +doAssertRaises(IndexError): + foo(toOpenArray(seqq, 0, -2)) + +foo(toOpenArray(arr, 9, 8)) +foo(toOpenArray(arr, 0, -1)) +foo(toOpenArray(arr, 1, 0)) +doAssertRaises(IndexError): + foo(toOpenArray(arr, 10, 8)) + +# test openArray of openArray +proc oaEmpty(a: openArray[int]) = + foo(toOpenArray(a, 0, -1)) + +proc oaFirstElm(a: openArray[int]) = + foo(toOpenArray(a, 0, 0)) + +oaEmpty(toOpenArray(seqq, 0, -1)) +oaEmpty(toOpenArray(seqq, 1, 0)) +oaEmpty(toOpenArray(seqq, 1, 2)) +oaFirstElm(toOpenArray(seqq, 1, seqq.len-1)) + +var arrNeg: array[-3 .. -1, int] = [1, 2, 3] +foo(toOpenArray(arrNeg, -3, -1)) +foo(toOpenArray(arrNeg, 0, -1)) +foo(toOpenArray(arrNeg, -3, -4)) +doAssertRaises(IndexError): + foo(toOpenArray(arrNeg, -4, -1)) +doAssertRaises(IndexError): + foo(toOpenArray(arrNeg, -1, 0)) +doAssertRaises(IndexError): + foo(toOpenArray(arrNeg, -1, -3)) + +proc foo(a: openArray[byte]) = + for x in a: echo x + +let str = "0123456789" +foo(toOpenArrayByte(str, 0, str.high)) diff --git a/tests/template/i2416.nim b/tests/template/i2416.nim new file mode 100644 index 000000000..4b53cd0ca --- /dev/null +++ b/tests/template/i2416.nim @@ -0,0 +1 @@ +template i2416*() = echo "i2416" diff --git a/tests/template/mgensym_generic_cross_module.nim b/tests/template/mgensym_generic_cross_module.nim index 80b681db4..ea88f67e6 100644 --- a/tests/template/mgensym_generic_cross_module.nim +++ b/tests/template/mgensym_generic_cross_module.nim @@ -1,6 +1,6 @@ -template makeDomElement(x: untyped, name: string = nil) = - const tag {.gensym.} = if name == nil: astToStr(x) else: name +template makeDomElement(x: untyped, name: string = "") = + const tag {.gensym.} = if name.len == 0: astToStr(x) else: name proc x*(p: int|float) = echo tag, p diff --git a/tests/template/t2416.nim b/tests/template/t2416.nim new file mode 100644 index 000000000..f73880718 --- /dev/null +++ b/tests/template/t2416.nim @@ -0,0 +1,2 @@ +import i2416 +i2416() diff --git a/tests/template/tdefault_nil.nim b/tests/template/tdefault_nil.nim index c5c372d9e..783f77388 100644 --- a/tests/template/tdefault_nil.nim +++ b/tests/template/tdefault_nil.nim @@ -2,8 +2,8 @@ # bug #2629 import sequtils, os -template glob_rst(basedir: string = nil): untyped = - if baseDir.isNil: +template glob_rst(basedir: string = ""): untyped = + if baseDir.len == 0: to_seq(walk_files("*.rst")) else: to_seq(walk_files(basedir/"*.rst")) diff --git a/tests/template/tdefined_overload.nim b/tests/template/tdefined_overload.nim new file mode 100644 index 000000000..bcc83e414 --- /dev/null +++ b/tests/template/tdefined_overload.nim @@ -0,0 +1,46 @@ +discard """ + output: "Valid and not defined" +""" +# test for issue #7997 +# checking for `when not defined` in a template for some compile time symbol +# results in a compilation error of: +# Error: obsolete usage of 'defined', use 'declared' instead +# if the symbol is 'overloaded' by some variable or procedure, because in +# that case the argument of `defined` is of kind `nkSym` instead of `nkIdent` +# (for which was checked in `semexprs.semDefined`). + +block: + # check whether a proc with the same name as the argument to `defined` + # compiles + proc overloaded() = + discard + + template definedCheck(): untyped = + when not defined(overloaded): true + else: false + doAssert definedCheck == true + +block: + # check whether a variable with the same name as the argument to `defined` + # compiles + var overloaded: int + + template definedCheck(): untyped = + when not defined(overloaded): true + else: false + doAssert definedCheck == true + +block: + # check whether a non overloaded when check still works properly + when not defined(validIdentifier): + echo "Valid and not defined" + +block: + # now check that invalid identifiers cause a compilation error + # by using reject template. + template reject(b) = + static: doAssert(not compiles(b)) + + reject: + when defined(123): + echo "Invalid identifier! Will not be echoed" diff --git a/tests/template/tgenerictemplates.nim b/tests/template/tgenerictemplates.nim index 2c83bc0ec..142505b1a 100644 --- a/tests/template/tgenerictemplates.nim +++ b/tests/template/tgenerictemplates.nim @@ -11,3 +11,27 @@ template someTemplate[T](): tuple[id: int32, obj: T] = let ret = someTemplate[SomeObj]() +# https://github.com/nim-lang/Nim/issues/7829 +proc inner*[T](): int = + discard + +template outer*[A](): untyped = + inner[A]() + +template outer*[B](x: int): untyped = + inner[B]() + +var i1 = outer[int]() +var i2 = outer[int](i1) + +# https://github.com/nim-lang/Nim/issues/7883 +template t1[T: int|int64](s: string): T = + var t: T + t + +template t1[T: int|int64](x: int, s: string): T = + var t: T + t + +var i3: int = t1[int]("xx") + diff --git a/tests/template/tnested_template.nim b/tests/template/tnested_template.nim new file mode 100644 index 000000000..37166009d --- /dev/null +++ b/tests/template/tnested_template.nim @@ -0,0 +1,23 @@ +# bug #8052 + +type + UintImpl*[N: static[int], T: SomeUnsignedInt] = object + raw_data*: array[N, T] + +template genLoHi(TypeImpl: untyped): untyped = + template loImpl[N: static[int], T: SomeUnsignedInt](dst: TypeImpl[N div 2, T], src: TypeImpl[N, T]) = + let halfSize = N div 2 + for i in 0 ..< halfSize: + dst.raw_data[i] = src.raw_data[i] + + proc lo*[N: static[int], T: SomeUnsignedInt](x: TypeImpl[N,T]): TypeImpl[N div 2, T] {.inline.}= + loImpl(result, x) + +genLoHi(UintImpl) + +var a: UintImpl[4, uint32] + +a.raw_data = [1'u32, 2'u32, 3'u32, 4'u32] +assert a.lo.raw_data.len == 2 +assert a.lo.raw_data[0] == 1 +assert a.lo.raw_data[1] == 2 diff --git a/tests/template/tpattern_with_converter.nim b/tests/template/tpattern_with_converter.nim new file mode 100644 index 000000000..e0632552b --- /dev/null +++ b/tests/template/tpattern_with_converter.nim @@ -0,0 +1,27 @@ +discard """ + output: 10.0 +""" + +type + MyFloat = object + val: float + +converter to_myfloat*(x: float): MyFloat {.inline.} = + MyFloat(val: x) + +proc `+`(x1, x2: MyFloat): MyFloat = + MyFloat(val: x1.val + x2.val) + +proc `*`(x1, x2: MyFloat): MyFloat = + MyFloat(val: x1.val * x2.val) + +template optMul{`*`(a, 2.0)}(a: MyFloat): MyFloat = + a + a + +func floatMyFloat(x: MyFloat): MyFloat = + result = x * 2.0 + +func floatDouble(x: float): float = + result = x * 2.0 + +echo floatDouble(5) \ No newline at end of file diff --git a/tests/template/tprocparshadow.nim b/tests/template/tprocparshadow.nim index b99cd0b6c..de1c2d941 100644 --- a/tests/template/tprocparshadow.nim +++ b/tests/template/tprocparshadow.nim @@ -9,3 +9,22 @@ template something(name: untyped) = something(what) what(10) + +# bug #4750 + +type + O = object + i: int + + OP = ptr O + +template alf(p: pointer): untyped = + cast[OP](p) + + +proc t1(al: pointer) = + var o = alf(al) + +proc t2(alf: pointer) = + var x = alf + var o = alf(x) diff --git a/tests/template/ttempl.nim b/tests/template/ttempl.nim index 5f1341990..c022201f2 100644 --- a/tests/template/ttempl.nim +++ b/tests/template/ttempl.nim @@ -17,7 +17,7 @@ const var i = 0 for item in items(tabs): var content = $i - var file: TFile + var file: File if open(file, changeFileExt(item[1], "html"), fmWrite): write(file, sunsetTemplate(current=item[1], ticker="", content=content, tabs=tabs)) diff --git a/tests/template/ttempl3.nim b/tests/template/ttempl3.nim index 91d416c48..d6a369d32 100644 --- a/tests/template/ttempl3.nim +++ b/tests/template/ttempl3.nim @@ -1,9 +1,9 @@ -template withOpenFile(f: untyped, filename: string, mode: TFileMode, +template withOpenFile(f: untyped, filename: string, mode: FileMode, actions: untyped): untyped = block: # test that 'f' is implicitly 'injecting': - var f: TFile + var f: File if open(f, filename, mode): try: actions diff --git a/tests/testament/backend.nim b/tests/testament/backend.nim index 4acef9ca4..385f1171c 100644 --- a/tests/testament/backend.nim +++ b/tests/testament/backend.nim @@ -13,7 +13,7 @@ type CommitId = distinct string proc `$`*(id: MachineId): string {.borrow.} -proc `$`(id: CommitId): string {.borrow.} +#proc `$`(id: CommitId): string {.borrow.} # not used var thisMachine: MachineId diff --git a/tests/testament/categories.nim b/tests/testament/categories.nim index 2f9198759..2c55bd0bd 100644 --- a/tests/testament/categories.nim +++ b/tests/testament/categories.nim @@ -63,6 +63,26 @@ proc compileRodFiles(r: var TResults, cat: Category, options: string) = test "gtkex1", true test "gtkex2" +# --------------------- flags tests ------------------------------------------- + +proc flagTests(r: var TResults, cat: Category, options: string) = + # --genscript + const filename = "tests"/"flags"/"tgenscript" + const genopts = " --genscript" + let nimcache = nimcacheDir(filename, genopts, targetC) + testSpec r, makeTest(filename, genopts, cat) + + when defined(windows): + testExec r, makeTest(filename, " cmd /c cd " & nimcache & + " && compile_tgenscript.bat", cat) + + elif defined(posix): + testExec r, makeTest(filename, " sh -c \"cd " & nimcache & + " && sh compile_tgenscript.sh\"", cat) + + # Run + testExec r, makeTest(filename, " " & nimcache / "tgenscript", cat) + # --------------------- DLL generation tests ---------------------------------- proc safeCopyFile(src, dest: string) = @@ -89,10 +109,14 @@ proc runBasicDLLTest(c, r: var TResults, cat: Category, options: string) = safeCopyFile("lib" / nimrtlDll, "tests/dll" / nimrtlDll) else: # posix relies on crappy LD_LIBRARY_PATH (ugh!): - var libpath = getEnv"LD_LIBRARY_PATH".string + const libpathenv = when defined(haiku): + "LIBRARY_PATH" + else: + "LD_LIBRARY_PATH" + var libpath = getEnv(libpathenv).string # Temporarily add the lib directory to LD_LIBRARY_PATH: - putEnv("LD_LIBRARY_PATH", "tests/dll" & (if libpath.len > 0: ":" & libpath else: "")) - defer: putEnv("LD_LIBRARY_PATH", libpath) + putEnv(libpathenv, "tests/dll" & (if libpath.len > 0: ":" & libpath else: "")) + defer: putEnv(libpathenv, libpath) var nimrtlDll = DynlibFormat % "nimrtl" safeCopyFile("lib" / nimrtlDll, "tests/dll" / nimrtlDll) @@ -236,6 +260,8 @@ proc jsTests(r: var TResults, cat: Category, options: string) = # ------------------------- nim in action ----------- proc testNimInAction(r: var TResults, cat: Category, options: string) = + let options = options & " --nilseqs:on" + template test(filename: untyped, action: untyped) = testSpec r, makeTest(filename, options, cat, action) @@ -246,8 +272,17 @@ proc testNimInAction(r: var TResults, cat: Category, options: string) = testSpec r, makeTest(filename, options, cat, actionCompile), targetCPP let tests = [ + "niminaction/Chapter1/various1", + "niminaction/Chapter2/various2", + "niminaction/Chapter2/resultaccept", + "niminaction/Chapter2/resultreject", + "niminaction/Chapter2/explicit_discard", + "niminaction/Chapter2/no_def_eq", + "niminaction/Chapter2/no_iterator", + "niminaction/Chapter2/no_seq_type", "niminaction/Chapter3/ChatApp/src/server", "niminaction/Chapter3/ChatApp/src/client", + "niminaction/Chapter3/various3", "niminaction/Chapter6/WikipediaStats/concurrency_regex", "niminaction/Chapter6/WikipediaStats/concurrency", "niminaction/Chapter6/WikipediaStats/naive", @@ -258,8 +293,34 @@ proc testNimInAction(r: var TResults, cat: Category, options: string) = "niminaction/Chapter7/Tweeter/src/tweeter", "niminaction/Chapter7/Tweeter/src/createDatabase", "niminaction/Chapter7/Tweeter/tests/database_test", - "niminaction/Chapter8/sdl/sdl_test", + "niminaction/Chapter8/sdl/sdl_test" ] + + # Verify that the files have not been modified. Death shall fall upon + # whoever edits these hashes without dom96's permission, j/k. But please only + # edit when making a conscious breaking change, also please try to make your + # commit message clear and notify me so I can easily compile an errata later. + var testHashes: seq[string] = @[] + + for test in tests: + testHashes.add(getMD5(readFile("tests" / test.addFileExt("nim")).string)) + + const refHashes = @[ + "51afdfa84b3ca3d810809d6c4e5037ba", "30f07e4cd5eaec981f67868d4e91cfcf", + "d14e7c032de36d219c9548066a97e846", "2e40bfd5daadb268268727da91bb4e81", + "c5d3853ed0aba04bf6d35ba28a98dca0", "058603145ff92d46c009006b06e5b228", + "7b94a029b94ddb7efafddd546c965ff6", "586d74514394e49f2370dfc01dd9e830", + "e1901837b757c9357dc8d259fd0ef0f6", "097670c7ae12e825debaf8ec3995227b", + "a8cb7b78cc78d28535ab467361db5d6e", "bfaec2816a1848991b530c1ad17a0184", + "47cb71bb4c1198d6d29cdbee05aa10b9", "87e4436809f9d73324cfc4f57f116770", + "7b7db5cddc8cf8fa9b6776eef1d0a31d", "e6e40219f0f2b877869b738737b7685e", + "6532ee87d819f2605a443d5e94f9422a", "9a8fe78c588d08018843b64b57409a02", + "03a801275b8b76b4170c870cd0da079d", "20bb7d3e2d38d43b0cb5fcff4909a4a8", + "af6844598f534fab6942abfa4dfe9ab2", "2a7a17f84f6503d9bc89a5ab8feea127" + ] + doAssert testHashes == refHashes, "Nim in Action tests were changed." + + # Run the tests. for testfile in tests: test "tests/" & testfile & ".nim", actionCompile @@ -271,6 +332,7 @@ proc testNimInAction(r: var TResults, cat: Category, options: string) = + # ------------------------- manyloc ------------------------------------------- #proc runSpecialTests(r: var TResults, options: string) = # for t in ["lib/packages/docutils/highlite"]: @@ -295,6 +357,7 @@ proc manyLoc(r: var TResults, cat: Category, options: string) = if kind == pcDir: when defined(windows): if dir.endsWith"nake": continue + if dir.endsWith"named_argument_bug": continue let mainfile = findMainFile(dir) if mainfile != "": testNoSpec r, makeTest(mainfile, options, cat) @@ -323,7 +386,7 @@ var nimbleDir = getEnv("NIMBLE_DIR").string if nimbleDir.len == 0: nimbleDir = getHomeDir() / ".nimble" let nimbleExe = findExe("nimble") - packageDir = nimbleDir / "pkgs" + #packageDir = nimbleDir / "pkgs" # not used packageIndex = nimbleDir / "packages.json" proc waitForExitEx(p: Process): int = @@ -407,9 +470,9 @@ proc `&.?`(a, b: string): string = # candidate for the stdlib? result = if b.startswith(a): b else: a & b -proc `&?.`(a, b: string): string = +#proc `&?.`(a, b: string): string = # not used # candidate for the stdlib? - result = if a.endswith(b): a else: a & b + #result = if a.endswith(b): a else: a & b proc processSingleTest(r: var TResults, cat: Category, options, test: string) = let test = "tests" & DirSep &.? cat.string / test @@ -425,10 +488,16 @@ proc processCategory(r: var TResults, cat: Category, options: string) = compileRodFiles(r, cat, options) runRodFiles(r, cat, options) of "js": - # XXX JS doesn't need to be special anymore - jsTests(r, cat, options) + # only run the JS tests on Windows or Linux because Travis is bad + # and other OSes like Haiku might lack nodejs: + if not defined(linux) and isTravis: + discard + else: + jsTests(r, cat, options) of "dll": dllTests(r, cat, options) + of "flags": + flagTests(r, cat, options) of "gc": gcTests(r, cat, options) of "longgc": @@ -462,5 +531,9 @@ proc processCategory(r: var TResults, cat: Category, options: string) = # We can't test it because it depends on a third party. discard # TODO: Move untestable tests to someplace else, i.e. nimble repo. else: + var testsRun = 0 for name in os.walkFiles("tests" & DirSep &.? cat.string / "t*.nim"): testSpec r, makeTest(name, options, cat) + inc testsRun + if testsRun == 0: + echo "[Warning] - Invalid category specified \"", cat.string, "\", no tests were run" diff --git a/tests/testament/htmlgen.nim b/tests/testament/htmlgen.nim index bf26a956d..4a10fe00c 100644 --- a/tests/testament/htmlgen.nim +++ b/tests/testament/htmlgen.nim @@ -22,8 +22,8 @@ proc generateTestResultPanelPartial(outfile: File, testResultRow: JsonNode) = target = testResultRow["target"].str.htmlQuote() action = testResultRow["action"].str.htmlQuote() result = htmlQuote testResultRow["result"].str - expected = testResultRow["expected"].str - gotten = testResultRow["given"].str + expected = testResultRow["expected"].getStr + gotten = testResultRow["given"].getStr timestamp = "unknown" var panelCtxClass, textCtxClass, bgCtxClass: string @@ -121,7 +121,7 @@ proc generateAllTestsContent(outfile: File, allResults: AllTests, proc generateHtml*(filename: string, onlyFailing: bool) = let - currentTime = getTime().getLocalTime() + currentTime = getTime().local() timestring = htmlQuote format(currentTime, "yyyy-MM-dd HH:mm:ss 'UTC'zzz") var outfile = open(filename, fmWrite) diff --git a/tests/testament/specs.nim b/tests/testament/specs.nim index ac79e3942..fbe930a4f 100644 --- a/tests/testament/specs.nim +++ b/tests/testament/specs.nim @@ -12,8 +12,8 @@ import parseutils, strutils, os, osproc, streams, parsecfg var compilerPrefix* = "compiler" / "nim " -let isTravis = existsEnv("TRAVIS") -let isAppVeyor = existsEnv("APPVEYOR") +let isTravis* = existsEnv("TRAVIS") +let isAppVeyor* = existsEnv("APPVEYOR") proc cmdTemplate*(): string = compilerPrefix & "$target --lib:lib --hints:on -d:testing $options $file" diff --git a/tests/testament/tester.nim b/tests/testament/tester.nim index 06f5fec7d..566338559 100644 --- a/tests/testament/tester.nim +++ b/tests/testament/tester.nim @@ -16,13 +16,13 @@ import const resultsFile = "testresults.html" - jsonFile = "testresults.json" + #jsonFile = "testresults.json" # not used Usage = """Usage: tester [options] command [arguments] Command: all run all tests - c|category <category> run all the tests of a certain category + c|cat|category <category> run all the tests of a certain category r|run <test> run single test file html generate $1 from the database Arguments: @@ -79,7 +79,7 @@ proc nimcacheDir(filename, options: string, target: TTarget): string = proc callCompiler(cmdTemplate, filename, options: string, target: TTarget, extraOptions=""): TSpec = let nimcache = nimcacheDir(filename, options, target) - let options = options & " --nimCache:" & nimcache.quoteShell & extraOptions + let options = options & " " & ("--nimCache:" & nimcache).quoteShell & extraOptions let c = parseCmdLine(cmdTemplate % ["target", targetToCmd[target], "options", options, "file", filename.quoteShell, "filedir", filename.getFileDir()]) @@ -129,7 +129,7 @@ proc callCCompiler(cmdTemplate, filename, options: string, let c = parseCmdLine(cmdTemplate % ["target", targetToCmd[target], "options", options, "file", filename.quoteShell, "filedir", filename.getFileDir()]) - var p = startProcess(command="gcc", args=c[5.. ^1], + var p = startProcess(command="gcc", args=c[5 .. ^1], options={poStdErrToStdOut, poUsePath}) let outp = p.outputStream var x = newStringOfCap(120) @@ -150,11 +150,11 @@ proc initResults: TResults = result.skipped = 0 result.data = "" -proc readResults(filename: string): TResults = - result = marshal.to[TResults](readFile(filename).string) +#proc readResults(filename: string): TResults = # not used +# result = marshal.to[TResults](readFile(filename).string) -proc writeResults(filename: string, r: TResults) = - writeFile(filename, $$r) +#proc writeResults(filename: string, r: TResults) = # not used +# writeFile(filename, $$r) proc `$`(x: TResults): string = result = ("Tests passed: $1 / $3 <br />\n" & @@ -212,18 +212,18 @@ proc cmpMsgs(r: var TResults, expected, given: TSpec, test: TTest, target: TTarg elif expected.tfile == "" and extractFilename(expected.file) != extractFilename(given.file) and "internal error:" notin expected.msg: r.addResult(test, target, expected.file, given.file, reFilesDiffer) - elif expected.line != given.line and expected.line != 0 or + elif expected.line != given.line and expected.line != 0 or expected.column != given.column and expected.column != 0: r.addResult(test, target, $expected.line & ':' & $expected.column, - $given.line & ':' & $given.column, + $given.line & ':' & $given.column, reLinesDiffer) elif expected.tfile != "" and extractFilename(expected.tfile) != extractFilename(given.tfile) and "internal error:" notin expected.msg: r.addResult(test, target, expected.tfile, given.tfile, reFilesDiffer) - elif expected.tline != given.tline and expected.tline != 0 or + elif expected.tline != given.tline and expected.tline != 0 or expected.tcolumn != given.tcolumn and expected.tcolumn != 0: r.addResult(test, target, $expected.tline & ':' & $expected.tcolumn, - $given.tline & ':' & $given.tcolumn, + $given.tline & ':' & $given.tcolumn, reLinesDiffer) else: r.addResult(test, target, expected.msg, given.msg, reSuccess) @@ -262,6 +262,7 @@ proc codegenCheck(test: TTest, target: TTarget, spec: TSpec, expectedMsg: var st echo getCurrentExceptionMsg() except IOError: given.err = reCodeNotFound + echo getCurrentExceptionMsg() proc nimoutCheck(test: TTest; expectedNimout: string; given: var TSpec) = let exp = expectedNimout.strip.replace("\C\L", "\L") @@ -297,6 +298,8 @@ proc testSpec(r: var TResults, test: TTest, target = targetC) = var expected: TSpec if test.action != actionRunNoSpec: expected = parseSpec(tname) + if test.action == actionRun and expected.action == actionCompile: + expected.action = actionRun else: specDefaults expected expected.action = actionRunNoSpec @@ -404,6 +407,21 @@ proc testC(r: var TResults, test: TTest) = if exitCode != 0: given.err = reExitCodesDiffer if given.err == reSuccess: inc(r.passed) +proc testExec(r: var TResults, test: TTest) = + # runs executable or script, just goes by exit code + inc(r.total) + let (outp, errC) = execCmdEx(test.options.strip()) + var given: TSpec + specDefaults(given) + if errC == 0: + given.err = reSuccess + else: + given.err = reExitCodesDiffer + given.msg = outp.string + + if given.err == reSuccess: inc(r.passed) + r.addResult(test, targetC, "", given.msg, given.err) + proc makeTest(test, options: string, cat: Category, action = actionCompile, env: string = ""): TTest = # start with 'actionCompile', will be overwritten in the spec: @@ -427,7 +445,7 @@ include categories # if status: reSuccess else: reOutputsDiffer) proc main() = - os.putenv "NIMTEST_NO_COLOR", "1" + os.putenv "NIMTEST_COLOR", "never" os.putenv "NIMTEST_OUTPUT_LVL", "PRINT_FAILURES" backend.open() @@ -492,7 +510,8 @@ proc main() = backend.close() var failed = r.total - r.passed - r.skipped if failed != 0: - echo "FAILURE! total: ", r.total, " passed: ", r.passed, " skipped: ", r.skipped + echo "FAILURE! total: ", r.total, " passed: ", r.passed, " skipped: ", + r.skipped, " failed: ", failed quit(QuitFailure) if paramCount() == 0: diff --git a/tests/threads/t8535.nim b/tests/threads/t8535.nim new file mode 100644 index 000000000..a8d69657b --- /dev/null +++ b/tests/threads/t8535.nim @@ -0,0 +1,16 @@ +discard """ + output: "0" +""" + +type + CircAlloc* [Size: static[int] , T] = tuple + baseArray : array[Size,T] + index : uint16 + +type + Job = object of RootObj + +var foo {.threadvar.}: CircAlloc[1,Job] + +when isMainModule: + echo foo.index diff --git a/tests/threads/tactors.nim b/tests/threads/tactors.nim index 45a03ebb7..ea052b9bd 100644 --- a/tests/threads/tactors.nim +++ b/tests/threads/tactors.nim @@ -5,9 +5,9 @@ discard """ import actors var - pool: TActorPool[int, void] + pool: ActorPool[int, void] createActorPool(pool) -for i in 0 .. < 300: +for i in 0 ..< 300: pool.spawn(i, proc (x: int) {.thread.} = echo x) pool.join() diff --git a/tests/threads/threadex.nim b/tests/threads/threadex.nim index fb03cbfa8..679bfcb12 100644 --- a/tests/threads/threadex.nim +++ b/tests/threads/threadex.nim @@ -5,9 +5,9 @@ discard """ type TMsgKind = enum mLine, mEof - TMsg = object {.pure, final.} + TMsg = object case k: TMsgKind - of mEof: nil + of mEof: discard of mLine: data: string var diff --git a/tests/threads/tthreadvars.nim b/tests/threads/tthreadvars.nim new file mode 100644 index 000000000..81aa2e5ec --- /dev/null +++ b/tests/threads/tthreadvars.nim @@ -0,0 +1,78 @@ +discard """ +output: ''' +10 +1111 +1222 +3030303 +3060606 +6060606 +6121212 +3030903 +3061206 +3031503 +3061806 +5050505 +5101010 +''' +""" + +import typetraits + +var tls1 {.threadvar.}: int +var g0: int +var g1 {.global.}: int + +proc customInc(x: var int, delta: int) = + x += delta + +customInc(tls1, 10) +echo tls1 + +proc nonGenericProc: int = + var local: int + var nonGenericTls {.threadvar.}: int + var nonGenericGlobal {.global.}: int + var nonGenericMixedPragmas {.global, threadvar.}: int + + customInc local, 1000 + customInc nonGenericTls, 1 + customInc nonGenericGlobal, 10 + customInc nonGenericMixedPragmas, 100 + + return local + nonGenericTls + nonGenericGlobal + nonGenericMixedPragmas + +proc genericProc(T: typedesc): int = + var local: int + var genericTls {.threadvar.}: int + var genericGlobal {.global.}: int + var genericMixedPragmas {.global, threadvar.}: int + + customInc local, T.name.len * 1000000 + customInc genericTls, T.name.len * 1 + customInc genericGlobal, T.name.len * 100 + customInc genericMixedPragmas, T.name.len * 10000 + + return local + genericTls + genericGlobal + genericMixedPragmas + +echo nonGenericProc() +echo nonGenericProc() + +echo genericProc(int) +echo genericProc(int) + +echo genericProc(string) +echo genericProc(string) + +proc echoInThread[T]() {.thread.} = + echo genericProc(T) + echo genericProc(T) + +proc newEchoThread(T: typedesc) = + var t: Thread[void] + createThread(t, echoInThread[T]) + joinThreads(t) + +newEchoThread int +newEchoThread int +newEchoThread float + diff --git a/tests/trmacros/thoist.nim b/tests/trmacros/thoist.nim index 7d14c0abf..657f210a1 100644 --- a/tests/trmacros/thoist.nim +++ b/tests/trmacros/thoist.nim @@ -5,7 +5,7 @@ true''' import pegs -template optPeg{peg(pattern)}(pattern: string{lit}): TPeg = +template optPeg{peg(pattern)}(pattern: string{lit}): Peg = var gl {.global, gensym.} = peg(pattern) gl diff --git a/tests/tuples/tanontuples.nim b/tests/tuples/tanontuples.nim index 49803e5ac..f514670d3 100644 --- a/tests/tuples/tanontuples.nim +++ b/tests/tuples/tanontuples.nim @@ -1,7 +1,10 @@ discard """ - output: '''61, 125''' + output: '''61, 125 +(Field0: 0) (Field0: 13)''' """ +import macros + proc `^` (a, b: int): int = result = 1 for i in 1..b: result = result * a @@ -12,3 +15,12 @@ var n = (56, 3) m = (n[0] + m[1], m[1] ^ n[1]) echo m[0], ", ", m[1] + +# also test we can produce unary anon tuples in a macro: +macro mm(): untyped = + result = newTree(nnkTupleConstr, newLit(13)) + +proc nowTuple(): (int,) = + result = (0,) + +echo nowTuple(), " ", mm() diff --git a/tests/tuples/tuple_with_nil.nim b/tests/tuples/tuple_with_nil.nim index eb265f420..e09c53407 100644 --- a/tests/tuples/tuple_with_nil.nim +++ b/tests/tuples/tuple_with_nil.nim @@ -73,7 +73,7 @@ type const DefaultPrec = 6 ## Default precision for floating point numbers. - DefaultFmt: Format = (ftDefault, -1, -1, nil, faDefault, fsMinus, false, false, false, nil) + DefaultFmt: Format = (ftDefault, -1, -1, "", faDefault, fsMinus, false, false, false, "") ## Default format corresponding to the empty format string, i.e. ## `x.format("") == x.format(DefaultFmt)`. round_nums = [0.5, 0.05, 0.005, 0.0005, 0.00005, 0.000005, 0.0000005, 0.00000005] @@ -124,7 +124,7 @@ proc parse(fmt: string): Format {.nosideeffect.} = if fmt.rawmatch(p, 0, caps) < 0: raise newException(FormatError, "Invalid format string") - result.fill = fmt.get(caps, 0, nil) + result.fill = fmt.get(caps, 0, "") case fmt.get(caps, 1, 0.char) of '<': result.align = faLeft @@ -144,7 +144,7 @@ proc parse(fmt: string): Format {.nosideeffect.} = result.width = fmt.get(caps, 4, -1) if caps.has(4) and fmt[caps.bounds(4).first] == '0': - if result.fill != nil: + if result.fill != "": raise newException(FormatError, "Leading 0 in with not allowed with explicit fill character") if result.align != faDefault: raise newException(FormatError, "Leading 0 in with not allowed with explicit alignment") @@ -171,7 +171,7 @@ proc parse(fmt: string): Format {.nosideeffect.} = of '%': result.typ = ftPercent else: result.typ = ftDefault - result.arysep = fmt.get(caps, 8, nil, 1) + result.arysep = fmt.get(caps, 8, "", 1) proc getalign(fmt: Format; defalign: FmtAlign; slen: int) : tuple[left, right:int] {.nosideeffect.} = ## Returns the number of left and right padding characters for a @@ -218,7 +218,7 @@ proc writefill(o: var Writer; fmt: Format; n: int; signum: int = 0) = elif fmt.sign == fsPlus: write(o, '+') elif fmt.sign == fsSpace: write(o, ' ') - if fmt.fill == nil: + if fmt.fill.len == 0: for i in 1..n: write(o, ' ') else: for i in 1..n: @@ -353,7 +353,7 @@ proc writeformat(o: var Writer; p: pointer; fmt: Format) = f.baseprefix = true writeformat(o, add, cast[uint](p), f) -proc writeformat(o: var Writer; x: SomeReal; fmt: Format) = +proc writeformat(o: var Writer; x: SomeFloat; fmt: Format) = ## Write real number `x` according to format `fmt` using output ## object `o` and output function `add`. var fmt = fmt @@ -401,8 +401,8 @@ proc writeformat(o: var Writer; x: SomeReal; fmt: Format) = else: len += 4 # exponent # shift y so that 1 <= abs(y) < 2 - if exp > 0: y /= pow(10.SomeReal, abs(exp).SomeReal) - elif exp < 0: y *= pow(10.SomeReal, abs(exp).SomeReal) + if exp > 0: y /= pow(10.SomeFloat, abs(exp).SomeFloat) + elif exp < 0: y *= pow(10.SomeFloat, abs(exp).SomeFloat) elif fmt.typ == ftPercent: len += 1 # percent sign @@ -413,7 +413,7 @@ proc writeformat(o: var Writer; x: SomeReal; fmt: Format) = var mult = 1'i64 for i in 1..prec: mult *= 10 var num = y.int64 - var fr = ((y - num.SomeReal) * mult.SomeReal).int64 + var fr = ((y - num.SomeFloat) * mult.SomeFloat).int64 # build integer part string while num != 0: numstr[numlen] = ('0'.int + (num mod 10)).char @@ -626,19 +626,19 @@ proc splitfmt(s: string): seq[Part] {.compiletime, nosideeffect.} = else: lvl.dec let clpos = pos - var fmtpart = Part(kind: pkFmt, arg: -1, fmt: s.substr(oppos+1, clpos-1), field: nil, index: int.high, nested: nested) + var fmtpart = Part(kind: pkFmt, arg: -1, fmt: s.substr(oppos+1, clpos-1), field: "", index: int.high, nested: nested) if fmtpart.fmt.len > 0: var m: array[0..3, string] if not fmtpart.fmt.match(subpeg, m): error("invalid format string") - if m[1] != nil and m[1].len > 0: + if m[1].len > 0: fmtpart.field = m[1].substr(1) - if m[2] != nil and m[2].len > 0: + if m[2].len > 0: discard parseInt(m[2].substr(1, m[2].len-2), fmtpart.index) if m[0].len > 0: discard parseInt(m[0], fmtpart.arg) - if m[3] == nil or m[3].len == 0: + if m[3].len == 0: fmtpart.fmt = "" elif m[3][0] == ':': fmtpart.fmt = m[3].substr(1) @@ -650,7 +650,7 @@ proc splitfmt(s: string): seq[Part] {.compiletime, nosideeffect.} = proc literal(s: string): NimNode {.compiletime, nosideeffect.} = ## Return the nim literal of string `s`. This handles the case if ## `s` is nil. - result = if s == nil: newNilLit() else: newLit(s) + result = newLit(s) proc literal(b: bool): NimNode {.compiletime, nosideeffect.} = ## Return the nim literal of boolean `b`. This is either `true` @@ -713,7 +713,7 @@ proc generatefmt(fmtstr: string; args[arg].cnt = args[arg].cnt + 1 arg.inc # possible field access - if part.field != nil and part.field.len > 0: + if part.field.len > 0: argexpr = newDotExpr(argexpr, part.field.ident) # possible array access if part.index < int.high: @@ -724,7 +724,7 @@ proc generatefmt(fmtstr: string; # nested format string. Compute the format string by # concatenating the parts of the substring. for e in generatefmt(part.fmt, args, arg): - var newexpr = if part.fmt == nil: e.val else: newCall(bindsym"format", e.val, e.fmt) + var newexpr = if part.fmt.len == 0: e.val else: newCall(bindsym"format", e.val, e.fmt) if fmtexpr != nil and fmtexpr.kind != nnkNilLit: fmtexpr = infix(fmtexpr, "&", newexpr) else: diff --git a/tests/tuples/tuple_with_seq.nim b/tests/tuples/tuple_with_seq.nim index 39edb500f..00b07dd2c 100644 --- a/tests/tuples/tuple_with_seq.nim +++ b/tests/tuples/tuple_with_seq.nim @@ -3,8 +3,8 @@ discard """ @[1, 2, 3]''' """ -template foo(s: string = nil) = - if isNil(s): +template foo(s: string = "") = + if s.len == 0: echo "it's nil" else: echo s diff --git a/tests/typerel/t4799.nim b/tests/typerel/t4799.nim new file mode 100644 index 000000000..075893476 --- /dev/null +++ b/tests/typerel/t4799.nim @@ -0,0 +1,245 @@ +discard """ + output: "OK" +""" + +type + GRBase[T] = ref object of RootObj + val: T + GRC[T] = ref object of GRBase[T] + GRD[T] = ref object of GRBase[T] + +proc testGR[T](x: varargs[GRBase[T]]): string = + result = "" + for c in x: + result.add $c.val + +block test_t4799_1: + var rgv = GRBase[int](val: 3) + var rgc = GRC[int](val: 4) + var rgb = GRD[int](val: 2) + doAssert(testGR(rgb, rgc, rgv) == "243") + doAssert(testGR(rgc, rgv, rgb) == "432") + doAssert(testGR(rgv, rgb, rgc) == "324") + doAssert(testGR([rgb, rgc, rgv]) == "243") + doAssert(testGR([rgc, rgv, rgb]) == "432") + doAssert(testGR([rgv, rgb, rgc]) == "324") + +type + PRBase[T] = object of RootObj + val: T + PRC[T] = object of PRBase[T] + PRD[T] = object of PRBase[T] + +proc testPR[T](x: varargs[ptr PRBase[T]]): string = + result = "" + for c in x: + result.add $c.val + +block test_t4799_2: + var pgv = PRBase[int](val: 3) + var pgc = PRC[int](val: 4) + var pgb = PRD[int](val: 2) + doAssert(testPR(pgb.addr, pgc.addr, pgv.addr) == "243") + doAssert(testPR(pgc.addr, pgv.addr, pgb.addr) == "432") + doAssert(testPR(pgv.addr, pgb.addr, pgc.addr) == "324") + doAssert(testPR([pgb.addr, pgc.addr, pgv.addr]) == "243") + doAssert(testPR([pgc.addr, pgv.addr, pgb.addr]) == "432") + doAssert(testPR([pgv.addr, pgb.addr, pgc.addr]) == "324") + +type + RBase = ref object of RootObj + val: int + RC = ref object of RBase + RD = ref object of RBase + +proc testR(x: varargs[RBase]): string = + result = "" + for c in x: + result.add $c.val + +block test_t4799_3: + var rv = RBase(val: 3) + var rc = RC(val: 4) + var rb = RD(val: 2) + doAssert(testR(rb, rc, rv) == "243") + doAssert(testR(rc, rv, rb) == "432") + doAssert(testR(rv, rb, rc) == "324") + doAssert(testR([rb, rc, rv]) == "243") + doAssert(testR([rc, rv, rb]) == "432") + doAssert(testR([rv, rb, rc]) == "324") + +type + PBase = object of RootObj + val: int + PC = object of PBase + PD = object of PBase + +proc testP(x: varargs[ptr PBase]): string = + result = "" + for c in x: + result.add $c.val + +block test_t4799_4: + var pv = PBase(val: 3) + var pc = PC(val: 4) + var pb = PD(val: 2) + doAssert(testP(pb.addr, pc.addr, pv.addr) == "243") + doAssert(testP(pc.addr, pv.addr, pb.addr) == "432") + doAssert(testP(pv.addr, pb.addr, pc.addr) == "324") + doAssert(testP([pb.addr, pc.addr, pv.addr]) == "243") + doAssert(testP([pc.addr, pv.addr, pb.addr]) == "432") + doAssert(testP([pv.addr, pb.addr, pc.addr]) == "324") + +type + PSBase[T, V] = ref object of RootObj + val: T + color: V + PSRC[T] = ref object of PSBase[T, int] + PSRD[T] = ref object of PSBase[T, int] + +proc testPS[T, V](x: varargs[PSBase[T, V]]): string = + result = "" + for c in x: + result.add c.val + result.add $c.color + +block test_t4799_5: + var a = PSBase[string, int](val: "base", color: 1) + var b = PSRC[string](val: "rc", color: 2) + var c = PSRD[string](val: "rd", color: 3) + + doAssert(testPS(a, b, c) == "base1rc2rd3") + doAssert(testPS(b, a, c) == "rc2base1rd3") + doAssert(testPS(c, b, a) == "rd3rc2base1") + doAssert(testPS([a, b, c]) == "base1rc2rd3") + doAssert(testPS([b, a, c]) == "rc2base1rd3") + doAssert(testPS([c, b, a]) == "rd3rc2base1") + +type + SBase[T, V] = ref object of RootObj + val: T + color: V + SRC = ref object of SBase[string, int] + SRD = ref object of SBase[string, int] + +proc testS[T, V](x: varargs[SBase[T, V]]): string = + result = "" + for c in x: + result.add c.val + result.add $c.color + +block test_t4799_6: + var a = SBase[string, int](val: "base", color: 1) + var b = SRC(val: "rc", color: 2) + var c = SRD(val: "rd", color: 3) + + doAssert(testS(a, b, c) == "base1rc2rd3") + doAssert(testS(b, a, c) == "rc2base1rd3") + doAssert(testS(c, b, a) == "rd3rc2base1") + doAssert(testS([a, b, c]) == "base1rc2rd3") + # this is not varargs bug, but array construction bug + # see #7955 + #doAssert(testS([b, c, a]) == "rc2rd3base1") + #doAssert(testS([c, b, a]) == "rd3rc2base1") + +proc test_inproc() = + block test_inproc_1: + var rgv = GRBase[int](val: 3) + var rgc = GRC[int](val: 4) + var rgb = GRD[int](val: 2) + doAssert(testGR(rgb, rgc, rgv) == "243") + doAssert(testGR(rgc, rgv, rgb) == "432") + doAssert(testGR(rgv, rgb, rgc) == "324") + doAssert(testGR([rgb, rgc, rgv]) == "243") + doAssert(testGR([rgc, rgv, rgb]) == "432") + doAssert(testGR([rgv, rgb, rgc]) == "324") + + block test_inproc_2: + var pgv = PRBase[int](val: 3) + var pgc = PRC[int](val: 4) + var pgb = PRD[int](val: 2) + doAssert(testPR(pgb.addr, pgc.addr, pgv.addr) == "243") + doAssert(testPR(pgc.addr, pgv.addr, pgb.addr) == "432") + doAssert(testPR(pgv.addr, pgb.addr, pgc.addr) == "324") + doAssert(testPR([pgb.addr, pgc.addr, pgv.addr]) == "243") + doAssert(testPR([pgc.addr, pgv.addr, pgb.addr]) == "432") + doAssert(testPR([pgv.addr, pgb.addr, pgc.addr]) == "324") + +test_inproc() + +template reject(x) = + static: assert(not compiles(x)) + +block test_t4799_7: + type + Vehicle[T] = ref object of RootObj + tire: T + Car[T] = object of Vehicle[T] + Bike[T] = object of Vehicle[T] + + proc testVehicle[T](x: varargs[Vehicle[T]]): string {.used.} = + result = "" + for c in x: + result.add $c.tire + + var v = Vehicle[int](tire: 3) + var c = Car[int](tire: 4) + var b = Bike[int](tire: 2) + + reject: + echo testVehicle(b, c, v) + +block test_t4799_8: + type + Vehicle = ref object of RootObj + tire: int + Car = object of Vehicle + Bike = object of Vehicle + + proc testVehicle(x: varargs[Vehicle]): string {.used.} = + result = "" + for c in x: + result.add $c.tire + + var v = Vehicle(tire: 3) + var c = Car(tire: 4) + var b = Bike(tire: 2) + + reject: + echo testVehicle(b, c, v) + +type + PGVehicle[T] = ptr object of RootObj + tire: T + PGCar[T] = object of PGVehicle[T] + PGBike[T] = object of PGVehicle[T] + +proc testVehicle[T](x: varargs[PGVehicle[T]]): string {.used.} = + result = "" + for c in x: + result.add $c.tire + +var pgc = PGCar[int](tire: 4) +var pgb = PGBike[int](tire: 2) + +reject: + echo testVehicle(pgb, pgc) + +type + RVehicle = ptr object of RootObj + tire: int + RCar = object of RVehicle + RBike = object of RVehicle + +proc testVehicle(x: varargs[RVehicle]): string {.used.} = + result = "" + for c in x: + result.add $c.tire + +var rc = RCar(tire: 4) +var rb = RBike(tire: 2) + +reject: + echo testVehicle(rb, rc) + +echo "OK" diff --git a/tests/typerel/t4799_1.nim b/tests/typerel/t4799_1.nim new file mode 100644 index 000000000..549b6bf3c --- /dev/null +++ b/tests/typerel/t4799_1.nim @@ -0,0 +1,20 @@ +discard """ + outputsub: '''ObjectAssignmentError''' + exitcode: "1" +""" + +type + Vehicle[T] = object of RootObj + tire: T + Car[T] = object of Vehicle[T] + Bike[T] = object of Vehicle[T] + +proc testVehicle[T](x: varargs[Vehicle[T]]): string = + result = "" + for c in x: + result.add $c.tire + +var v = Vehicle[int](tire: 3) +var c = Car[int](tire: 4) +var b = Bike[int](tire: 2) +echo testVehicle b, c, v diff --git a/tests/typerel/t4799_2.nim b/tests/typerel/t4799_2.nim new file mode 100644 index 000000000..cfd399a6e --- /dev/null +++ b/tests/typerel/t4799_2.nim @@ -0,0 +1,20 @@ +discard """ + outputsub: '''ObjectAssignmentError''' + exitcode: "1" +""" + +type + Vehicle[T] = object of RootObj + tire: T + Car[T] = object of Vehicle[T] + Bike[T] = object of Vehicle[T] + +proc testVehicle[T](x: varargs[Vehicle[T]]): string = + result = "" + for c in x: + result.add $c.tire + +var v = Vehicle[int](tire: 3) +var c = Car[int](tire: 4) +var b = Bike[int](tire: 2) +echo testVehicle([b, c, v]) \ No newline at end of file diff --git a/tests/typerel/t4799_3.nim b/tests/typerel/t4799_3.nim new file mode 100644 index 000000000..784eee8fc --- /dev/null +++ b/tests/typerel/t4799_3.nim @@ -0,0 +1,20 @@ +discard """ + outputsub: '''ObjectAssignmentError''' + exitcode: "1" +""" + +type + Vehicle = object of RootObj + tire: int + Car = object of Vehicle + Bike = object of Vehicle + +proc testVehicle(x: varargs[Vehicle]): string = + result = "" + for c in x: + result.add $c.tire + +var v = Vehicle(tire: 3) +var c = Car(tire: 4) +var b = Bike(tire: 2) +echo testVehicle([b, c, v]) \ No newline at end of file diff --git a/tests/typerel/t7600_1.nim b/tests/typerel/t7600_1.nim new file mode 100644 index 000000000..e3a5fefa2 --- /dev/null +++ b/tests/typerel/t7600_1.nim @@ -0,0 +1,18 @@ +discard """ +errormsg: "type mismatch: got <Thin[system.int]>" +nimout: '''t7600_1.nim(18, 6) Error: type mismatch: got <Thin[system.int]> +but expected one of: +proc test[T](x: Paper[T]) + +expression: test tn''' +""" + +type + Paper[T] = ref object of RootObj + thickness: T + Thin[T] = object of Paper[T] + +proc test[T](x: Paper[T]) = discard + +var tn = Thin[int]() +test tn diff --git a/tests/typerel/t7600_2.nim b/tests/typerel/t7600_2.nim new file mode 100644 index 000000000..7badb69cf --- /dev/null +++ b/tests/typerel/t7600_2.nim @@ -0,0 +1,17 @@ +discard """ +errormsg: "type mismatch: got <Thin>" +nimout: '''t7600_2.nim(17, 6) Error: type mismatch: got <Thin> +but expected one of: +proc test(x: Paper) + +expression: test tn''' +""" + +type + Paper = ref object of RootObj + Thin = object of Paper + +proc test(x: Paper) = discard + +var tn = Thin() +test tn diff --git a/tests/typerel/t7734.nim b/tests/typerel/t7734.nim new file mode 100644 index 000000000..1e8df2cf1 --- /dev/null +++ b/tests/typerel/t7734.nim @@ -0,0 +1,19 @@ +type + Foo[T: SomeFloat] = object + learning_rate: T + + Bar[T: SomeFloat] = object + learning_rate: T + momentum: T + + Model = object + weight: int + + FooClass = Foo or Bar + + +proc optimizer[M; T: SomeFloat](model: M, _: typedesc[Foo], learning_rate: T): Foo[T] = + result.learning_rate = learning_rate + +let a = Model(weight: 1) +let opt = a.optimizer(Foo, 10.0) diff --git a/tests/typerel/t8172.nim b/tests/typerel/t8172.nim new file mode 100644 index 000000000..8e0b32932 --- /dev/null +++ b/tests/typerel/t8172.nim @@ -0,0 +1,11 @@ +discard """ + line: 11 + errormsg: "cannot convert array[0..0, string] to varargs[string]" +""" + +proc f(v: varargs[string]) = + echo(v) + +f("b", "c") # Works +f(["b", "c"]) # Works +f("b", ["c"]) # Fails diff --git a/tests/typerel/t8905.nim b/tests/typerel/t8905.nim new file mode 100644 index 000000000..9383962cf --- /dev/null +++ b/tests/typerel/t8905.nim @@ -0,0 +1,7 @@ +type + Foo[T] = distinct seq[T] + Bar[T] = object + +proc newFoo[T](): Foo[T] = Foo[T](newSeq[T]()) + +var x = newFoo[Bar[int]]() diff --git a/tests/typerel/texplicitcmp.nim b/tests/typerel/texplicitcmp.nim index 8aec9885a..e91ac2ffe 100644 --- a/tests/typerel/texplicitcmp.nim +++ b/tests/typerel/texplicitcmp.nim @@ -18,7 +18,7 @@ proc works() = sort(f, system.cmp[int]) outp(f) -proc weird(json_params: TTable) = +proc weird(json_params: Table) = var f = @[3, 2, 1] # The following line doesn't compile: type mismatch. Why? sort(f, system.cmp[int]) @@ -29,4 +29,4 @@ when isMainModule: sort(t, system.cmp[int]) outp(t) works() - weird(initTable[string, TJsonNode]()) + weird(initTable[string, JsonNode]()) diff --git a/tests/typerel/ttypelessemptyset.nim b/tests/typerel/ttypelessemptyset.nim index 3e171387b..5f49c33fd 100644 --- a/tests/typerel/ttypelessemptyset.nim +++ b/tests/typerel/ttypelessemptyset.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "internal error: invalid kind for last(tyEmpty)" + errormsg: "internal error: invalid kind for lastOrd(tyEmpty)" """ var q = false discard (if q: {} else: {}) diff --git a/tests/types/t6456.nim b/tests/types/t6456.nim new file mode 100644 index 000000000..2d2aad370 --- /dev/null +++ b/tests/types/t6456.nim @@ -0,0 +1,7 @@ +discard """ + line: 6 + errormsg: "type \'ptr void\' is not allowed" +""" + +proc foo(x: ptr void) = + discard diff --git a/tests/types/t7905.nim b/tests/types/t7905.nim new file mode 100644 index 000000000..ef75bb86c --- /dev/null +++ b/tests/types/t7905.nim @@ -0,0 +1,33 @@ +discard """ + output: ''' +(member: "hello world") +(member: 123.456) +(member: "hello world", x: ...) +(member: 123.456, x: ...) +''' +""" + +template foobar(arg: typed): untyped = + type + MyType = object + member: type(arg) + + var myVar: MyType + myVar.member = arg + echo myVar + +foobar("hello world") +foobar(123.456'f64) + +template foobarRec(arg: typed): untyped = + type + MyType = object + member: type(arg) + x: ref MyType + + var myVar: MyType + myVar.member = arg + echo myVar + +foobarRec("hello world") +foobarRec(123.456'f64) diff --git a/tests/types/temptyseqs.nim b/tests/types/temptyseqs.nim index 834f63729..28c3b87b8 100644 --- a/tests/types/temptyseqs.nim +++ b/tests/types/temptyseqs.nim @@ -22,7 +22,7 @@ when true: import os type - In_out = tuple[src, dest, options: string] + In_out = tuple[src, dest: string, options: ref int] let nil_var: In_out = ("hey"/"there", "something", nil) diff --git a/tests/types/tisopr.nim b/tests/types/tisopr.nim index 2f9dbf245..f05f443de 100644 --- a/tests/types/tisopr.nim +++ b/tests/types/tisopr.nim @@ -5,7 +5,7 @@ false false true true -no''' +yes''' """ proc IsVoid[T](): string = diff --git a/tests/types/tparameterizedparent3.nim b/tests/types/tparameterizedparent3.nim index 3fc83cb4d..58aaf80ea 100644 --- a/tests/types/tparameterizedparent3.nim +++ b/tests/types/tparameterizedparent3.nim @@ -1,7 +1,7 @@ discard """ file: "tparameterizedparent3.nim" line: 13 - errormsg: "redefinition of 'color'" + errormsg: "attempt to redefine: 'color'" """ # bug #5264 type diff --git a/tests/types/tparameterizedparent4.nim b/tests/types/tparameterizedparent4.nim index fa8b525c1..a37461bb4 100644 --- a/tests/types/tparameterizedparent4.nim +++ b/tests/types/tparameterizedparent4.nim @@ -1,7 +1,7 @@ discard """ file: "tparameterizedparent4.nim" line: 23 - errormsg: "redefinition of 'grain'" + errormsg: "attempt to redefine: 'grain'" """ # bug #5264 type diff --git a/tests/untestable/gdb/gdb_pretty_printer_test.py b/tests/untestable/gdb/gdb_pretty_printer_test.py new file mode 100644 index 000000000..54af65d9a --- /dev/null +++ b/tests/untestable/gdb/gdb_pretty_printer_test.py @@ -0,0 +1,33 @@ +import gdb +# this test should test the gdb pretty printers of the nim +# library. But be aware this test is not complete. It only tests the +# command line version of gdb. It does not test anything for the +# machine interface of gdb. This means if if this test passes gdb +# frontends might still be broken. + +gdb.execute("source ../../../tools/nim-gdb.py") +# debug all instances of the generic function `myDebug`, should be 8 +gdb.execute("rbreak myDebug") +gdb.execute("run") + +outputs = [ + 'meTwo', + '"meTwo"', + '{meOne, meThree}', + 'MyOtherEnum(1)', + '5', + 'array = {1, 2, 3, 4, 5}', + 'seq(3, 3) = {"one", "two", "three"}', + 'Table(3, 64) = {["two"] = 2, ["three"] = 3, ["one"] = 1}', +] + +for i, expected in enumerate(outputs): + if i == 5: + # myArray is passed as pointer to int to myDebug. I look up myArray up in the stack + gdb.execute("up") + output = str(gdb.parse_and_eval("myArray")) + else: + output = str(gdb.parse_and_eval("arg")) + + assert output == expected, output + " != " + expected + gdb.execute("continue") diff --git a/tests/untestable/gdb/gdb_pretty_printer_test_output.txt b/tests/untestable/gdb/gdb_pretty_printer_test_output.txt new file mode 100644 index 000000000..cbc9bde8d --- /dev/null +++ b/tests/untestable/gdb/gdb_pretty_printer_test_output.txt @@ -0,0 +1,3 @@ +Loading Nim Runtime support. +NimEnumPrinter: lookup global symbol 'NTI_z9cu80OJCfNgw9bUdzn5ZEzw_ failed for tyEnum_MyOtherEnum_z9cu80OJCfNgw9bUdzn5ZEzw. +8 diff --git a/tests/untestable/gdb/gdb_pretty_printer_test_program.nim b/tests/untestable/gdb/gdb_pretty_printer_test_program.nim new file mode 100644 index 000000000..458435c1a --- /dev/null +++ b/tests/untestable/gdb/gdb_pretty_printer_test_program.nim @@ -0,0 +1,53 @@ + + +import tables + +type + MyEnum = enum + meOne, + meTwo, + meThree, + meFour, + + MyOtherEnum = enum + moOne, + moTwo, + moThree, + moFoure, + + +var counter = 0 + +proc myDebug[T](arg: T): void = + counter += 1 + +proc testProc(): void = + var myEnum = meTwo + myDebug(myEnum) + # create a string object but also make the NTI for MyEnum is generated + var myString = $myEnum + myDebug(myString) + var mySet = {meOne,meThree} + myDebug(mySet) + + # for MyOtherEnum there is no NTI. This tests the fallback for the pretty printer. + var moEnum = moTwo + myDebug(moEnum) + var moSet = {moOne,moThree} + myDebug(moSet) + + let myArray = [1,2,3,4,5] + myDebug(myArray) + let mySeq = @["one","two","three"] + myDebug(mySeq) + + var myTable = initTable[string, int]() + myTable["one"] = 1 + myTable["two"] = 2 + myTable["three"] = 3 + myDebug(myTable) + + echo(counter) + + +testProc() diff --git a/tests/untestable/gdb/gdb_pretty_printer_test_run.sh b/tests/untestable/gdb/gdb_pretty_printer_test_run.sh new file mode 100644 index 000000000..525f54705 --- /dev/null +++ b/tests/untestable/gdb/gdb_pretty_printer_test_run.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Exit if anything fails +set -e +#!/usr/bin/env bash +# Compile the test project with fresh debug information. +nim c --debugger:native gdb_pretty_printer_test_program.nim &> /dev/null +# 2>&1 redirects stderr to stdout (all output in stdout) +# <(...) is a bash feature that makes the output of a command into a +# file handle. +# diff compares the two files, the expected output, and the file +# handle that is created by the execution of gdb. +diff ./gdb_pretty_printer_test_output.txt <(gdb -x gdb_pretty_printer_test.py --batch-silent --args gdb_pretty_printer_test_program 2>&1) +# The exit code of diff is forwarded as the exit code of this +# script. So when the comparison fails, the exit code of this script +# won't be 0. So this script should be embeddable in a test suite. diff --git a/tests/varres/tnewseq_on_result_vart.nim b/tests/varres/tnewseq_on_result_vart.nim new file mode 100644 index 000000000..18935a1d1 --- /dev/null +++ b/tests/varres/tnewseq_on_result_vart.nim @@ -0,0 +1,9 @@ + +discard """ + line: 9 + errormsg: "address of 'result' may not escape its stack frame" +""" +# bug #5113 + +proc makeSeqVar(size: Natural): var seq[int] = + newSeq(result, size) diff --git a/tests/varres/tprevent_forloopvar_mutations.nim b/tests/varres/tprevent_forloopvar_mutations.nim new file mode 100644 index 000000000..15938bb77 --- /dev/null +++ b/tests/varres/tprevent_forloopvar_mutations.nim @@ -0,0 +1,15 @@ +discard """ + line: 15 + errmsg: "type mismatch: got <int>" + nimout: '''type mismatch: got <int> +but expected one of: +proc inc[T: Ordinal | uint | uint64](x: var T; y = 1) + for a 'var' type a variable needs to be passed, but 'i' is immutable + +expression: inc i +''' +""" + +for i in 0..10: + echo i + inc i diff --git a/tests/varres/tvarres1.nim b/tests/varres/tvarres1.nim index 849805768..5a5247142 100644 --- a/tests/varres/tvarres1.nim +++ b/tests/varres/tvarres1.nim @@ -1,7 +1,7 @@ discard """ file: "tvarres1.nim" line: 12 - errormsg: "address of 'bla' may not escape its stack frame" + errormsg: "'bla' escapes its stack frame; context: 'bla'" """ var diff --git a/tests/varres/tvarres_via_forwarding.nim b/tests/varres/tvarres_via_forwarding.nim new file mode 100644 index 000000000..8fd3dfcfd --- /dev/null +++ b/tests/varres/tvarres_via_forwarding.nim @@ -0,0 +1,12 @@ +discard """ + line: 10 + errormsg: "'y' escapes its stack frame; context: 'forward(y)'" +""" + +proc forward(x: var int): var int = result = x + +proc foo(): var int = + var y = 9 + result = forward(y) + +echo foo() diff --git a/tests/varres/twrong_parameter.nim b/tests/varres/twrong_parameter.nim new file mode 100644 index 000000000..8a363dd19 --- /dev/null +++ b/tests/varres/twrong_parameter.nim @@ -0,0 +1,16 @@ +discard """ + line: 10 + errormsg: "'x' is not the first parameter; context: 'x.field[0]'" +""" + +type + MyObject = object + field: array[2, int] + +proc forward(abc: int; x: var MyObject): var int = result = x.field[0] + +proc foo(): var int = + var y: MyObject + result = forward(45, y) + +echo foo() diff --git a/tests/vm/meta.nim b/tests/vm/meta.nim index 2aa01b5b3..32a441f27 100644 --- a/tests/vm/meta.nim +++ b/tests/vm/meta.nim @@ -39,7 +39,7 @@ proc newIdent*(node: NimNode): Ident = raise newException(ValueError, msg) proc render*(i: Ident): NimNode {.compileTime.} = - if i.name == nil: + if i.name == "": return newNimNode(nnkEmpty) if i.exported: @@ -67,7 +67,7 @@ proc newBracket*(node: NimNode): Bracket = raise newException(ValueError, msg) # Field procs -proc newField*(identifier: Ident, type_name: string, default: string = nil): Field = +proc newField*(identifier: Ident, type_name: string, default: string = ""): Field = result.identifier = identifier result.type_name = type_name result.default = default @@ -84,7 +84,7 @@ proc newField*(node: NimNode): Field = of nnkIdent: result.default = $(node[2]) else: - result.default = nil + result.default = "" else: let msg = "newField cannot initialize from node kind: " & $(node.kind) raise newException(ValueError, msg) @@ -102,7 +102,7 @@ proc newFieldSeq*(node: NimNode): FieldSeq = of nnkIdent: default = $(default_node) else: - default = nil + default = "" for i in 0..node.len - 3: let name = newIdent(node[i]) result.add(newField(name, type_name, default)) @@ -115,8 +115,8 @@ proc newFieldSeq*(node: NimNode): FieldSeq = proc render*(f: Field): NimNode {.compileTime.} = let identifier = f.identifier.render() - let type_name = if f.type_name != nil: ident(f.type_name) else: newEmptyNode() - let default = if f.default != nil: ident(f.default) else: newEmptyNode() + let type_name = if f.type_name != "": ident(f.type_name) else: newEmptyNode() + let default = if f.default != "": ident(f.default) else: newEmptyNode() newIdentDefs(identifier, type_name, default) proc render*(fs: FieldSeq): NimNode {.compileTime.} = @@ -127,7 +127,7 @@ proc render*(fs: FieldSeq): NimNode {.compileTime.} = # TypeDef procs proc newTypeDef*(identifier: Ident, is_ref = false, object_type = "object", - base_type: string = nil): TypeDef {.compileTime.} = + base_type: string = ""): TypeDef {.compileTime.} = result.identifier = identifier result.fields = @[] result.is_ref = is_ref @@ -165,7 +165,7 @@ proc render*(typedef: TypeDef): NimNode {.compileTime.} = result.add(newEmptyNode()) let object_node = newNimNode(nnkObjectTy) object_node.add(newEmptyNode()) - if typedef.base_type == nil: + if typedef.base_type == "": object_node.add(newEmptyNode()) else: var base_type = newNimNode(nnkOfInherit) @@ -195,8 +195,8 @@ proc render*(typeseq: TypeDefSeq): NimNode {.compileTime.} = for typedef in typeseq: result.add(typedef.render()) -proc newProc*(identifier: Ident, params: FieldSeq = nil, - returns: Ident, generics: FieldSeq = nil): Proc = +proc newProc*(identifier: Ident, params: FieldSeq = @[], + returns: Ident, generics: FieldSeq = @[]): Proc = result.identifier = identifier result.params = params result.returns = returns @@ -209,7 +209,7 @@ proc newProc*(node: NimNode): Proc = case node[2].kind: of nnkGenericParams: result.generics = newFieldSeq(node[2]) - else: result.generics = nil + else: result.generics = @[] let formal_params = node[3] case formal_params[0].kind: of nnkIdent: diff --git a/tests/vm/tcomponent.nim b/tests/vm/tcomponent.nim index e7962e7ab..b509bc5d7 100644 --- a/tests/vm/tcomponent.nim +++ b/tests/vm/tcomponent.nim @@ -11,7 +11,7 @@ FOO: blah''' import macros, sequtils, tables import strutils -import future, meta +import sugar, meta type Component = object diff --git a/tests/vm/trgba.nim b/tests/vm/tmisc_vm.nim index 923ea1b2e..6eb3dd627 100644 --- a/tests/vm/trgba.nim +++ b/tests/vm/tmisc_vm.nim @@ -2,6 +2,8 @@ discard """ output: '''[127, 127, 0, 255] [127, 127, 0, 255] ''' + + nimout: '''caught Exception''' """ #bug #1009 @@ -34,3 +36,16 @@ proc ABGR*(val: int| int64): TAggRgba8 = const c1 = ABGR(0xFF007F7F) echo ABGR(0xFF007F7F).repr, c1.repr + + +# bug 8740 + +static: + try: + raise newException(ValueError, "foo") + except Exception: + echo "caught Exception" + except Defect: + echo "caught Defect" + except ValueError: + echo "caught ValueError" diff --git a/tests/vm/tnilref.nim b/tests/vm/tnilref.nim new file mode 100644 index 000000000..5e27cf0cb --- /dev/null +++ b/tests/vm/tnilref.nim @@ -0,0 +1,7 @@ +discard """ + errormsg: "attempt to access a nil address" +""" + +static: + var s: ref int + s[] = 1 \ No newline at end of file diff --git a/tests/vm/tnimnode.nim b/tests/vm/tnimnode.nim index 0614b9807..4210ab41d 100644 --- a/tests/vm/tnimnode.nim +++ b/tests/vm/tnimnode.nim @@ -4,14 +4,12 @@ proc assertEq(arg0,arg1: string): void = if arg0 != arg1: raiseAssert("strings not equal:\n" & arg0 & "\n" & arg1) -static: - # a simple assignment of stmtList to another variable - var node: NimNode - # an assignment of stmtList into an array - var nodeArray: array[1, NimNode] - # an assignment of stmtList into a seq - var nodeSeq = newSeq[NimNode](2) - +# a simple assignment of stmtList to another variable +var node {.compileTime.}: NimNode +# an assignment of stmtList into an array +var nodeArray {.compileTime.}: array[1, NimNode] +# an assignment of stmtList into a seq +var nodeSeq {.compileTime.} = newSeq[NimNode](2) proc checkNode(arg: NimNode; name: string): void {. compileTime .} = echo "checking ", name @@ -26,19 +24,19 @@ proc checkNode(arg: NimNode; name: string): void {. compileTime .} = seqAppend.add(arg) # bit this creates a copy arg.add newCall(ident"echo", newLit("Hello World")) - assertEq arg.lispRepr , """StmtList(DiscardStmt(Empty()), Call(Ident(ident"echo"), StrLit(Hello World)))""" - assertEq node.lispRepr , """StmtList(DiscardStmt(Empty()), Call(Ident(ident"echo"), StrLit(Hello World)))""" - assertEq nodeArray[0].lispRepr , """StmtList(DiscardStmt(Empty()), Call(Ident(ident"echo"), StrLit(Hello World)))""" - assertEq nodeSeq[0].lispRepr , """StmtList(DiscardStmt(Empty()), Call(Ident(ident"echo"), StrLit(Hello World)))""" - assertEq seqAppend[0].lispRepr , """StmtList(DiscardStmt(Empty()), Call(Ident(ident"echo"), StrLit(Hello World)))""" - assertEq seqAppend[1].lispRepr , """StmtList(DiscardStmt(Empty()), Call(Ident(ident"echo"), StrLit(Hello World)))""" + assertEq arg.lispRepr , """StmtList(DiscardStmt(Empty()), Call(Ident("echo"), StrLit("Hello World")))""" + assertEq node.lispRepr , """StmtList(DiscardStmt(Empty()), Call(Ident("echo"), StrLit("Hello World")))""" + assertEq nodeArray[0].lispRepr , """StmtList(DiscardStmt(Empty()), Call(Ident("echo"), StrLit("Hello World")))""" + assertEq nodeSeq[0].lispRepr , """StmtList(DiscardStmt(Empty()), Call(Ident("echo"), StrLit("Hello World")))""" + assertEq seqAppend[0].lispRepr , """StmtList(DiscardStmt(Empty()), Call(Ident("echo"), StrLit("Hello World")))""" + assertEq seqAppend[1].lispRepr , """StmtList(DiscardStmt(Empty()), Call(Ident("echo"), StrLit("Hello World")))""" echo "OK" -static: - # the root node that is used to generate the Ast - var stmtList: NimNode +# the root node that is used to generate the Ast +var stmtList {.compileTime.}: NimNode +static: stmtList = newStmtList(nnkDiscardStmt.newTree(newEmptyNode())) checkNode(stmtList, "direct construction") diff --git a/tests/vm/tref.nim b/tests/vm/tref.nim new file mode 100644 index 000000000..f5cd23da5 --- /dev/null +++ b/tests/vm/tref.nim @@ -0,0 +1,62 @@ +static: + var + a: ref string + b: ref string + new a + + a[] = "Hello world" + b = a + + b[5] = 'c' + doAssert a[] == "Hellocworld" + doAssert b[] == "Hellocworld" + + proc notGlobal() = + var + a: ref string + b: ref string + new a + + a[] = "Hello world" + b = a + + b[5] = 'c' + doAssert a[] == "Hellocworld" + doAssert b[] == "Hellocworld" + notGlobal() + +static: # bug 6081 + block: + type Obj = object + field: ref int + var i: ref int + new(i) + var r = Obj(field: i) + var rr = r + r.field = nil + doAssert rr.field != nil + + proc foo() = # Proc to avoid special global logic + var s: seq[ref int] + var i: ref int + new(i) + s.add(i) + var head = s[0] + s[0] = nil + doAssert head != nil + + foo() + +static: + + block: # global alias + var s: ref int + new(s) + var ss = s + s[] = 1 + doAssert ss[] == 1 + +static: # bug #8402 + type R = ref object + var empty: R + let otherEmpty = empty \ No newline at end of file diff --git a/tests/vm/tseq_badinit.nim b/tests/vm/tseq_badinit.nim index 15889d60e..5fa223c85 100644 --- a/tests/vm/tseq_badinit.nim +++ b/tests/vm/tseq_badinit.nim @@ -22,14 +22,14 @@ template test(typename, default: untyped) = result = newSeq[typename]() result.add(default) result.setLen(3) - for i in 0 .. <2: + for i in 0 ..< 2: result[i] = default const constval = `abc typename`() doAssert(constval == `abc typename`()) proc `arr typename`(): array[4, typename] = - for i in 0 .. <2: + for i in 0 ..< 2: result[i] = default const constarr = `arr typename`() doAssert(constarr == `arr typename`()) diff --git a/tests/vm/tstringnil.nim b/tests/vm/tstringnil.nim index 39e4040dc..df408910e 100644 --- a/tests/vm/tstringnil.nim +++ b/tests/vm/tstringnil.nim @@ -20,16 +20,16 @@ proc buildSuiteContents(suiteName, suiteDesc, suiteBloc: NimNode): tuple[tests: var testObj = SuiteTest() if suiteName.kind == nnkNilLit: - testObj.suiteName = nil + testObj.suiteName = "" else: testObj.suiteName = $suiteName if suiteDesc.kind == nnkNilLit: - testObj.suiteDesc = nil + testObj.suiteDesc = "" else: testObj.suiteDesc = suiteDesc.strVal testObj.testName = $child[1] # should not ever be nil if child[2].kind == nnkNilLit: - testObj.testDesc = nil + testObj.testDesc = "" else: testObj.testDesc = child[2].strVal testObj.testBlock = child[1] @@ -46,5 +46,5 @@ macro suite(suiteName, suiteDesc, suiteBloc: untyped): typed = # Test above suite basics, "Description of such": - test(t5, nil): + test(t5, ""): assert false diff --git a/tests/vm/tvmmisc.nim b/tests/vm/tvmmisc.nim index 42a58fa8f..6a8084647 100644 --- a/tests/vm/tvmmisc.nim +++ b/tests/vm/tvmmisc.nim @@ -1,5 +1,8 @@ # bug #4462 import macros +import os +import ospaths +import strutils block: proc foo(t: typedesc) {.compileTime.} = @@ -16,9 +19,9 @@ block: var x = default(type(0)) # #6379 +import algorithm + static: - import algorithm - var numArray = [1, 2, 3, 4, -1] numArray.sort(cmp) assert numArray == [-1, 1, 2, 3, 4] @@ -57,10 +60,91 @@ block: result = @[0] result.setLen(2) var tmp: int - - for i in 0 .. <2: + + for i in 0 ..< 2: inc tmp result[i] = tmp const fact1000 = abc() assert fact1000 == @[1, 2] + +# Tests for VM ops +block: + static: + assert "vm" in getProjectPath() + + let b = getEnv("UNSETENVVAR") + assert b == "" + assert existsEnv("UNSERENVVAR") == false + putEnv("UNSETENVVAR", "VALUE") + assert getEnv("UNSETENVVAR") == "VALUE" + assert existsEnv("UNSETENVVAR") == true + + assert fileExists("MISSINGFILE") == false + assert dirExists("MISSINGDIR") == false + +# #7210 +block: + static: + proc f(size: int): int = + var some = newStringOfCap(size) + result = size + doAssert f(4) == 4 + +# #6689 +block: + static: + proc foo(): int = 0 + var f: proc(): int + doAssert f.isNil + f = foo + doAssert(not f.isNil) + +block: + static: + var x: ref ref int + new(x) + doAssert(not x.isNil) + +# #7871 +static: + type Obj = object + field: int + var s = newSeq[Obj](1) + var o = Obj() + s[0] = o + o.field = 2 + doAssert s[0].field == 0 + +# #8125 +static: + let def_iter_var = ident("it") + +# #8142 +static: + type Obj = object + names: string + + proc pushName(o: var Obj) = + var s = "" + s.add("FOOBAR") + o.names.add(s) + + var o = Obj() + o.names = "" + o.pushName() + o.pushName() + doAssert o.names == "FOOBARFOOBAR" + +# #8154 +import parseutils + +static: + type Obj = object + i: int + + proc foo(): Obj = + discard parseInt("1", result.i, 0) + + static: + doAssert foo().i == 1 diff --git a/todo.txt b/todo.txt deleted file mode 100644 index e06ddf555..000000000 --- a/todo.txt +++ /dev/null @@ -1,72 +0,0 @@ -version 1.0 battle plan -======================= - -- let 'doAssert' analyse the expressions and produce more helpful output -- fix "high priority" bugs -- try to fix as many compiler crashes as reasonable - - -Not critical for 1.0 -==================== - -- introduce ``nkStmtListExpr`` for template/macro invokations to produce - better stack traces -- make 'break' not leave named blocks -- make FlowVar compatible to Futures -- make 'not nil' the default (produce warnings instead of errors for - a smooth migration path) -- case objects needs to be safe and need to support pattern matching - -- find a solution for the x.f[T](y) gotcha -- implement ``.delegate`` for .experimental -- annotation support for getType() -- make '--implicitStatic:on' the default; then we can also clean up the - 'static[T]' mess in the compiler! -- ``not`` or ``~`` for the effects system -- figure out why C++ bootstrapping is so much slower -- make 'nil' work for 'add': - - resizeString - - incrSeq - - addChar -- pragmas need 'bindSym' support -- pragmas need re-work: 'push' is dangerous, 'hasPragma' does not work - reliably with user-defined pragmas -- we need a magic thisModule symbol -- optimize 'genericReset'; 'newException' leads to code bloat - -- prevent 'alloc(TypeWithGCedMemory)'? -- map ``string`` and ``seq`` to ``std::string`` and ``std::vector`` -- macro support for '='; bind '=' to a memory region -- macros as type pragmas - - -Bugs -==== - -- VM: Pegs do not work at compile-time -- blocks can "export" an identifier but the CCG generates {} for them ... -- ConcreteTypes in a 'case' means we don't check for duplicated case branches - - -GC -== - -- resizing of strings/sequences could take into account the memory that - is allocated - - -Concurrency -=========== - -- implement 'foo[1..4] = spawn(f[4..7])' - -Low priority: -- support for exception propagation? (hard to implement) -- the copying of the 'ref Promise' into the thead local storage only - happens to work due to the write barrier's implementation - - -CGEN -==== -- codegen should use "NIM_CAST" macro and respect aliasing rules for GCC -- ``restrict`` pragma + backend support diff --git a/tools/detect/detect.nim b/tools/detect/detect.nim index 1b016cef9..5b4fdc99e 100644 --- a/tools/detect/detect.nim +++ b/tools/detect/detect.nim @@ -586,6 +586,9 @@ v("POSIX_TYPED_MEM_ALLOCATE_CONTIG") v("POSIX_TYPED_MEM_MAP_ALLOCATABLE") v("MAP_POPULATE", no_other = true) +header("<sys/resource.h>") +v("RLIMIT_NOFILE") + header("<sys/select.h>") v("FD_SETSIZE") diff --git a/tools/dochack/dochack.nim b/tools/dochack/dochack.nim index 79a0e7482..61c61225d 100644 --- a/tools/dochack/dochack.nim +++ b/tools/dochack/dochack.nim @@ -1,6 +1,5 @@ - - import karax +import fuzzysearch proc findNodeWith(x: Element; tag, content: cstring): Element = if x.nodeName == tag and x.textContent == content: @@ -29,7 +28,7 @@ proc parentWith(x: Element; tag: cstring): Element = proc extractItems(x: Element; items: var seq[Element]) = if x == nil: return - if x.nodeName == "A": + if x.nodeName == cstring"A": items.add x else: for i in 0..<x.len: @@ -88,11 +87,11 @@ proc toHtml(x: TocEntry; isRoot=false): Element = if ul.len != 0: result.add ul if result.len == 0: result = nil -proc containsWord(a, b: cstring): bool {.asmNoStackFrame.} = - {.emit: """ - var escaped = `b`.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - return new RegExp("\\b" + escaped + "\\b").test(`a`); - """.} +#proc containsWord(a, b: cstring): bool {.asmNoStackFrame.} = + #{.emit: """ + #var escaped = `b`.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + #return new RegExp("\\b" + escaped + "\\b").test(`a`); + #""".} proc isWhitespace(text: cstring): bool {.asmNoStackFrame.} = {.emit: """ @@ -100,19 +99,19 @@ proc isWhitespace(text: cstring): bool {.asmNoStackFrame.} = """.} proc isWhitespace(x: Element): bool = - x.nodeName == "#text" and x.textContent.isWhitespace or - x.nodeName == "#comment" + x.nodeName == cstring"#text" and x.textContent.isWhitespace or + x.nodeName == cstring"#comment" proc toToc(x: Element; father: TocEntry) = - if x.nodeName == "UL": + if x.nodeName == cstring"UL": let f = TocEntry(heading: nil, kids: @[], sortId: father.kids.len) var i = 0 while i < x.len: var nxt = i+1 while nxt < x.len and x[nxt].isWhitespace: inc nxt - if nxt < x.len and x[i].nodeName == "LI" and x[i].len == 1 and - x[nxt].nodeName == "UL": + if nxt < x.len and x[i].nodeName == cstring"LI" and x[i].len == 1 and + x[nxt].nodeName == cstring"UL": let e = TocEntry(heading: x[i][0], kids: @[], sortId: f.kids.len) let it = x[nxt] for j in 0..<it.len: @@ -125,11 +124,11 @@ proc toToc(x: Element; father: TocEntry) = father.kids.add f elif isWhitespace(x): discard - elif x.nodeName == "LI": + elif x.nodeName == cstring"LI": var idx: seq[int] = @[] for i in 0 ..< x.len: if not x[i].isWhitespace: idx.add i - if idx.len == 2 and x[idx[1]].nodeName == "UL": + if idx.len == 2 and x[idx[1]].nodeName == cstring"UL": let e = TocEntry(heading: x[idx[0]], kids: @[], sortId: father.kids.len) let it = x[idx[1]] @@ -148,9 +147,9 @@ proc tocul(x: Element): Element = result = tree("UL") for i in 0..<x.len: let it = x[i] - if it.nodeName == "LI": + if it.nodeName == cstring"LI": result.add it.clone - elif it.nodeName == "UL": + elif it.nodeName == cstring"UL": result.add tocul(it) proc getSection(toc: Element; name: cstring): Element = @@ -224,7 +223,7 @@ proc groupBy*(value: cstring) {.exportc.} = let ntoc = buildToc(tt, types, procs) let x = toHtml(ntoc, isRoot=true) alternative = tree("DIV", x) - if value == "type": + if value == cstring"type": replaceById("tocRoot", alternative) else: replaceById("tocRoot", tree("DIV")) @@ -237,7 +236,7 @@ var template normalize(x: cstring): cstring = x.toLower.replace("_", "") proc dosearch(value: cstring): Element = - if db.isNil: + if db.len == 0: var stuff: Element {.emit: """ var request = new XMLHttpRequest(); @@ -252,24 +251,29 @@ proc dosearch(value: cstring): Element = `stuff` = doc.documentElement; """.} - db = stuff.getElementsByClass"reference external" + db = stuff.getElementsByClass"reference" contents = @[] for ahref in db: - contents.add ahref.textContent.normalize + contents.add ahref.getAttribute("data-doc-search-tag") let ul = tree("UL") result = tree("DIV") result.setClass"search_results" var matches: seq[(Element, int)] = @[] - let key = value.normalize for i in 0..<db.len: let c = contents[i] - if c.containsWord(key): - matches.add((db[i], -(30_000 - c.len))) - elif c.contains(key): - matches.add((db[i], c.len)) + if c == cstring"Examples" or c == cstring"PEG construction": + # Some manual exclusions. + # Ideally these should be fixed in the index to be more + # descriptive of what they are. + continue + let (score, matched) = fuzzymatch(value, c) + if matched: + matches.add((db[i], score)) + matches.sort do (a, b: auto) -> int: - a[1] - b[1] - for i in 0..min(<matches.len, 19): + b[1] - a[1] + for i in 0 ..< min(matches.len, 19): + matches[i][0].innerHTML = matches[i][0].getAttribute("data-doc-search-tag") ul.add(tree("LI", matches[i][0])) if ul.len == 0: result.add tree("B", text"no search results") @@ -284,7 +288,7 @@ proc search*() {.exportc.} = proc wrapper() = let elem = getElementById("searchInput") let value = elem.value - if value != "": + if value.len != 0: if oldtoc.isNil: oldtoc = getElementById("tocRoot") let results = dosearch(value) diff --git a/tools/dochack/fuzzysearch.nim b/tools/dochack/fuzzysearch.nim new file mode 100644 index 000000000..e6b1ea3cd --- /dev/null +++ b/tools/dochack/fuzzysearch.nim @@ -0,0 +1,139 @@ +# A Fuzzy Match implementation inspired by the sublime text fuzzy match algorithm +# as described here: https://blog.forrestthewoods.com/reverse-engineering-sublime-text-s-fuzzy-match-4cffeed33fdb +# Heavily modified to provide more subjectively useful results +# for on the Nim manual. +# +import strutils +import math +import macros + + +const + MaxUnmatchedLeadingChar = 3 + ## Maximum number of times the penalty for unmatched leading chars is applied. + + HeadingScaleFactor = 0.5 + ## The score from before the colon Char is multiplied by this. + ## This is to weight function signatures and descriptions over module titles. + + +type + ScoreCard = enum + StartMatch = -100 ## Start matching. + LeadingCharDiff = -3 ## An unmatched, leading character was found. + CharDiff = -1 ## An unmatched character was found. + CharMatch = 0 ## A matched character was found. + ConsecutiveMatch = 5 ## A consecutive match was found. + LeadingCharMatch = 10 ## The character matches the begining of the + ## string or the first character of a word + ## or camel case boundry. + WordBoundryMatch = 20 ## The last ConsecutiveCharMatch that + ## immediately precedes the end of the string, + ## end of the pattern, or a LeadingCharMatch. + + +proc fuzzyMatch*(pattern, str: cstring) : tuple[score: int, matched: bool] = + var + scoreState = StartMatch + headerMatched = false + unmatchedLeadingCharCount = 0 + consecutiveMatchCount = 0 + strIndex = 0 + patIndex = 0 + score = 0 + + template transition(nextState) = + scoreState = nextState + score += ord(scoreState) + + while (strIndex < str.len) and (patIndex < pattern.len): + var + patternChar = pattern[patIndex].toLowerAscii + strChar = str[strIndex].toLowerAscii + + # Ignore certain characters + if patternChar in {'_', ' ', '.'}: + patIndex += 1 + continue + if strChar in {'_', ' ', '.'}: + strIndex += 1 + continue + + # Since this algorithm will be used to search against Nim documentation, + # the below logic prioritizes headers. + if not headerMatched and strChar == ':': + headerMatched = true + scoreState = StartMatch + score = toInt(floor(HeadingScaleFactor * toFloat(score))) + patIndex = 0 + strIndex += 1 + continue + + if strChar == patternChar: + case scoreState + of StartMatch, WordBoundryMatch: + scoreState = LeadingCharMatch + + of CharMatch: + transition(ConsecutiveMatch) + + of LeadingCharMatch, ConsecutiveMatch: + consecutiveMatchCount += 1 + scoreState = ConsecutiveMatch + score += ord(ConsecutiveMatch) * consecutiveMatchCount + + if scoreState == LeadingCharMatch: + score += ord(LeadingCharMatch) + + var onBoundary = (patIndex == high(pattern)) + if not onBoundary: + let + nextPatternChar = toLowerAscii(pattern[patIndex + 1]) + nextStrChar = toLowerAscii(str[strIndex + 1]) + + onBoundary = ( + nextStrChar notin {'a'..'z'} and + nextStrChar != nextPatternChar + ) + + if onBoundary: + transition(WordBoundryMatch) + + of CharDiff, LeadingCharDiff: + var isLeadingChar = ( + str[strIndex - 1] notin Letters or + str[strIndex - 1] in {'a'..'z'} and + str[strIndex] in {'A'..'Z'} + ) + + if isLeadingChar: + scoreState = LeadingCharMatch + #a non alpha or a camel case transition counts as a leading char. + # Transition the state, but don't give the bonus yet; wait until we verify a consecutive match. + else: + transition(CharMatch) + patIndex += 1 + + else: + case scoreState + of StartMatch: + transition(LeadingCharDiff) + + of ConsecutiveMatch: + transition(CharDiff) + consecutiveMatchCount = 0 + + of LeadingCharDiff: + if unmatchedLeadingCharCount < MaxUnmatchedLeadingChar: + transition(LeadingCharDiff) + unmatchedLeadingCharCount += 1 + + else: + transition(CharDiff) + + strIndex += 1 + + result = ( + score: max(0, score), + matched: (score > 0), + ) diff --git a/tools/dochack/karax.nim b/tools/dochack/karax.nim index d9619992b..020fd37d1 100644 --- a/tools/dochack/karax.nim +++ b/tools/dochack/karax.nim @@ -112,7 +112,8 @@ proc tree*(tag: string; attrs: openarray[(string, string)]; proc text*(s: string): Element = cast[Element](document.createTextNode(s)) proc text*(s: cstring): Element = cast[Element](document.createTextNode(s)) proc add*(parent, kid: Element) = - if parent.nodeName == "TR" and (kid.nodeName == "TD" or kid.nodeName == "TH"): + if parent.nodeName == cstring"TR" and ( + kid.nodeName == cstring"TD" or kid.nodeName == cstring"TH"): let k = document.createElement("TD") appendChild(k, kid) appendChild(parent, k) @@ -260,7 +261,7 @@ proc table*(class="", kids: varargs[Element]): Element = proc tr*(kids: varargs[Element]): Element = result = tag("tr") for k in kids: - if k.nodeName == "TD" or k.nodeName == "TH": + if k.nodeName == cstring"TD" or k.nodeName == cstring"TH": result.add k else: result.add td(k) diff --git a/tools/finish.nim b/tools/finish.nim index 207f15f76..4f2c72595 100644 --- a/tools/finish.nim +++ b/tools/finish.nim @@ -34,7 +34,7 @@ proc downloadMingw(): DownloadResult = if curl.len > 0: cmd = quoteShell(curl) & " --out " & "dist" / mingw & " " & url elif fileExists"bin/nimgrab.exe": - cmd = "bin/nimgrab.exe " & url & " dist" / mingw + cmd = r"bin\nimgrab.exe " & url & " dist" / mingw if cmd.len > 0: if execShellCmd(cmd) != 0: echo "download failed! ", cmd @@ -52,14 +52,17 @@ when defined(windows): proc askBool(m: string): bool = stdout.write m while true: - let answer = stdin.readLine().normalize - case answer - of "y", "yes": - return true - of "n", "no": - return false - else: - echo "Please type 'y' or 'n'" + try: + let answer = stdin.readLine().normalize + case answer + of "y", "yes": + return true + of "n", "no": + return false + else: + echo "Please type 'y' or 'n'" + except EOFError: + quit(1) proc askNumber(m: string; a, b: int): int = stdout.write m @@ -99,10 +102,6 @@ when defined(windows): proc addToPathEnv*(e: string) = var p = tryGetUnicodeValue(r"Environment", "Path", HKEY_CURRENT_USER) - if p.len == 0: - p = tryGetUnicodeValue( - r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", - "Path", HKEY_LOCAL_MACHINE) let x = if e.contains(Whitespace): "\"" & e & "\"" else: e if p.len > 0: p.add ";" @@ -188,10 +187,14 @@ when defined(windows): proc main() = when defined(windows): - let desiredPath = expandFilename(getCurrentDir() / "bin") - let p = getUnicodeValue(r"Environment", "Path", - HKEY_CURRENT_USER) - var alreadyInPath = false + let nimDesiredPath = expandFilename(getCurrentDir() / "bin") + let nimbleDesiredPath = expandFilename(getEnv("USERPROFILE") / ".nimble" / "bin") + let p = tryGetUnicodeValue(r"Environment", "Path", + HKEY_CURRENT_USER) & ";" & tryGetUnicodeValue( + r"System\CurrentControlSet\Control\Session Manager\Environment", "Path", + HKEY_LOCAL_MACHINE) + var nimAlreadyInPath = false + var nimbleAlreadyInPath = false var mingWchoices: seq[string] = @[] var incompat: seq[string] = @[] for x in p.split(';'): @@ -199,18 +202,29 @@ proc main() = let y = try: expandFilename(if x[0] == '"' and x[^1] == '"': substr(x, 1, x.len-2) else: x) except: "" - if y == desiredPath: alreadyInPath = true - if y.toLowerAscii.contains("mingw"): + if y.cmpIgnoreCase(nimDesiredPath) == 0: + nimAlreadyInPath = true + elif y.cmpIgnoreCase(nimbleDesiredPath) == 0: + nimbleAlreadyInPath = true + elif y.toLowerAscii.contains("mingw"): if dirExists(y): if checkGccArch(y): mingWchoices.add y else: incompat.add y - if alreadyInPath: - echo "bin/nim.exe is already in your PATH [Skipping]" + if nimAlreadyInPath: + echo "bin\\nim.exe is already in your PATH [Skipping]" else: if askBool("nim.exe is not in your PATH environment variable.\n" & "Should it be added permanently? (y/n) "): - addToPathEnv(desiredPath) + addToPathEnv(nimDesiredPath) + + if nimbleAlreadyInPath: + echo nimbleDesiredPath & " is already in your PATH [Skipping]" + else: + if askBool(nimbleDesiredPath & " is not in your PATH environment variable.\n" & + "Should it be added permanently? (y/n) "): + addToPathEnv(nimbleDesiredPath) + if mingWchoices.len == 0: # No mingw in path, so try a few locations: let alternative = tryDirs(incompat, defaultMingwLocations()) diff --git a/tools/nim-gdb.py b/tools/nim-gdb.py new file mode 100644 index 000000000..b98dc96fe --- /dev/null +++ b/tools/nim-gdb.py @@ -0,0 +1,513 @@ + +import gdb +import re +import sys + +# some feedback that the nim runtime support is loading, isn't a bad +# thing at all. +gdb.write("Loading Nim Runtime support.\n", gdb.STDERR) + +# When error occure they occur regularly. This 'caches' known errors +# and prevents them from being reprinted over and over again. +errorSet = set() +def printErrorOnce(id, message): + global errorSet + if id not in errorSet: + errorSet.add(id) + gdb.write(message, gdb.STDERR) + +nimobjfile = gdb.current_objfile() or gdb.objfiles()[0] +nimobjfile.type_printers = [] + +################################################################################ +##### Type pretty printers +################################################################################ + +type_hash_regex = re.compile("^\w*_([A-Za-z0-9]*)$") + +def getNimRti(type_name): + """ Return a ``gdb.Value`` object for the Nim Runtime Information of ``type_name``. """ + + # Get static const TNimType variable. This should be available for + # every non trivial Nim type. + m = type_hash_regex.match(type_name) + if m: + try: + return gdb.parse_and_eval("NTI_" + m.group(1) + "_") + except: + return None + +class NimTypeRecognizer: + # this type map maps from types that are generated in the C files to + # how they are called in nim. To not mix up the name ``int`` from + # system.nim with the name ``int`` that could still appear in + # generated code, ``NI`` is mapped to ``system.int`` and not just + # ``int``. + + type_map_static = { + 'NI': 'system.int', 'NI8': 'int8', 'NI16': 'int16', 'NI32': 'int32', 'NI64': 'int64', + 'NU': 'uint', 'NU8': 'uint8','NU16': 'uint16', 'NU32': 'uint32', 'NU64': 'uint64', + 'NF': 'float', 'NF32': 'float32', 'NF64': 'float64', + 'NIM_BOOL': 'bool', 'NIM_CHAR': 'char', 'NCSTRING': 'cstring', + 'NimStringDesc': 'string' + } + + # Normally gdb distinguishes between the command `ptype` and + # `whatis`. `ptype` prints a very detailed view of the type, and + # `whatis` a very brief representation of the type. I haven't + # figured out a way to know from the type printer that is + # implemented here how to know if a type printer should print the + # short representation or the long representation. As a hacky + # workaround I just say I am not resposible for printing pointer + # types (seq and string are exception as they are semantically + # values). this way the default type printer will handle pointer + # types and dive into the members of that type. So I can still + # control with `ptype myval` and `ptype *myval` if I want to have + # detail or not. I this this method stinks but I could not figure + # out a better solution. + + object_type_pattern = re.compile("^(\w*):ObjectType$") + + def recognize(self, type_obj): + + tname = None + if type_obj.tag is not None: + tname = type_obj.tag + elif type_obj.name is not None: + tname = type_obj.name + + # handle pointer types + if not tname: + if type_obj.code == gdb.TYPE_CODE_PTR: + target_type = type_obj.target() + target_type_name = target_type.name + if target_type_name: + # visualize 'string' as non pointer type (unpack pointer type). + if target_type_name == "NimStringDesc": + tname = target_type_name # could also just return 'string' + # visualize 'seq[T]' as non pointer type. + if target_type_name.find('tySequence_') == 0: + tname = target_type_name + + if not tname: + # We are not resposible for this type printing. + # Basically this means we don't print pointer types. + return None + + result = self.type_map_static.get(tname, None) + if result: + return result + + rti = getNimRti(tname) + if rti: + return rti['name'].string("utf-8", "ignore") + else: + return None + +class NimTypePrinter: + """Nim type printer. One printer for all Nim types.""" + + + # enabling and disabling of type printers can be done with the + # following gdb commands: + # + # enable type-printer NimTypePrinter + # disable type-printer NimTypePrinter + + name = "NimTypePrinter" + def __init__ (self): + self.enabled = True + + def instantiate(self): + return NimTypeRecognizer() + + +nimobjfile.type_printers = [NimTypePrinter()] + +################################################################################ +##### GDB Function, equivalent of Nim's $ operator +################################################################################ + +class DollarPrintFunction (gdb.Function): + "Nim's equivalent of $ operator as a gdb function, available in expressions `print $dollar(myvalue)" + + _gdb_dollar_functions = gdb.execute("info functions dollar__", True, True) + dollar_functions = re.findall('NimStringDesc \*(dollar__[A-z0-9_]+?)\(([^,)]*)\);', _gdb_dollar_functions) + + def __init__ (self): + super (DollarPrintFunction, self).__init__("dollar") + + @staticmethod + def invoke_static(arg): + + for func, arg_typ in DollarPrintFunction.dollar_functions: + + if arg.type.name == arg_typ: + func_value = gdb.lookup_global_symbol(func, gdb.SYMBOL_FUNCTIONS_DOMAIN).value() + return func_value(arg) + + if arg.type.name + " *" == arg_typ: + func_value = gdb.lookup_global_symbol(func, gdb.SYMBOL_FUNCTIONS_DOMAIN).value() + return func_value(arg.address) + + typeName = arg.type.name + printErrorOnce(typeName, "No suitable Nim $ operator found for type: " + typeName + ".\n") + + def invoke(self, arg): + return self.invoke_static(arg) + +DollarPrintFunction() + +################################################################################ +##### GDB Command, equivalent of Nim's $ operator +################################################################################ + +class DollarPrintCmd (gdb.Command): + """Dollar print command for Nim, `$ expr` will invoke Nim's $ operator""" + + def __init__ (self): + super (DollarPrintCmd, self).__init__ ("$", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION) + + def invoke (self, arg, from_tty): + param = gdb.parse_and_eval(arg) + gdb.write(str(DollarPrintFunction.invoke_static(param)) + "\n", gdb.STDOUT) + +DollarPrintCmd() + +################################################################################ +##### Value pretty printers +################################################################################ + +class NimBoolPrinter: + + pattern = re.compile(r'^NIM_BOOL$') + + def __init__(self, val): + self.val = val + + def to_string(self): + if self.val == 0: + return "false" + else: + return "true" + +################################################################################ + +class NimStringPrinter: + pattern = re.compile(r'^NimStringDesc \*$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'string' + + def to_string(self): + if self.val: + l = int(self.val['Sup']['len']) + return self.val['data'][0].address.string("utf-8", "ignore", l) + else: + return "" + +################################################################################ + +# proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} = +# ## Return string representation for enumeration values +# var n = typ.node +# if ntfEnumHole notin typ.flags: +# let o = e - n.sons[0].offset +# if o >= 0 and o <% typ.node.len: +# return $n.sons[o].name +# else: +# # ugh we need a slow linear search: +# var s = n.sons +# for i in 0 .. n.len-1: +# if s[i].offset == e: +# return $s[i].name +# result = $e & " (invalid data!)" + +def reprEnum(e, typ): + """ this is a port of the nim runtime function `reprEnum` to python """ + e = int(e) + n = typ["node"] + flags = int(typ["flags"]) + # 1 << 2 is {ntfEnumHole} + if ((1 << 2) & flags) == 0: + o = e - int(n["sons"][0]["offset"]) + if o >= 0 and 0 < int(n["len"]): + return n["sons"][o]["name"].string("utf-8", "ignore") + else: + # ugh we need a slow linear search: + s = n["sons"] + for i in range(0, int(n["len"])): + if int(s[i]["offset"]) == e: + return s[i]["name"].string("utf-8", "ignore") + + return str(e) + " (invalid data!)" + +class NimEnumPrinter: + pattern = re.compile(r'^tyEnum_(\w*)_([A-Za-z0-9]*)$') + + def __init__(self, val): + self.val = val + match = self.pattern.match(self.val.type.name) + self.typeNimName = match.group(1) + typeInfoName = "NTI_" + match.group(2) + "_" + self.nti = gdb.lookup_global_symbol(typeInfoName) + + if self.nti is None: + printErrorOnce(typeInfoName, "NimEnumPrinter: lookup global symbol '" + typeInfoName + " failed for " + self.val.type.name + ".\n") + + def to_string(self): + if self.nti: + arg0 = self.val + arg1 = self.nti.value(gdb.newest_frame()) + return reprEnum(arg0, arg1) + else: + return self.typeNimName + "(" + str(int(self.val)) + ")" + +################################################################################ + +class NimSetPrinter: + ## the set printer is limited to sets that fit in an integer. Other + ## sets are compiled to `NU8 *` (ptr uint8) and are invisible to + ## gdb (currently). + pattern = re.compile(r'^tySet_tyEnum_(\w*)_([A-Za-z0-9]*)$') + + def __init__(self, val): + self.val = val + match = self.pattern.match(self.val.type.name) + self.typeNimName = match.group(1) + + typeInfoName = "NTI_" + match.group(2) + "_" + self.nti = gdb.lookup_global_symbol(typeInfoName) + + if self.nti is None: + printErrorOnce(typeInfoName, "NimSetPrinter: lookup global symbol '"+ typeInfoName +" failed for " + self.val.type.name + ".\n") + + def to_string(self): + if self.nti: + nti = self.nti.value(gdb.newest_frame()) + enumStrings = [] + val = int(self.val) + i = 0 + while val > 0: + if (val & 1) == 1: + enumStrings.append(reprEnum(i, nti)) + val = val >> 1 + i += 1 + + return '{' + ', '.join(enumStrings) + '}' + else: + return str(int(self.val)) + +################################################################################ + +class NimHashSetPrinter: + pattern = re.compile(r'^tyObject_(HashSet)_([A-Za-z0-9]*)$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'array' + + def to_string(self): + counter = 0 + capacity = 0 + if self.val: + counter = int(self.val['counter']) + if self.val['data']: + capacity = int(self.val['data']['Sup']['len']) + + return 'HashSet({0}, {1})'.format(counter, capacity) + + def children(self): + if self.val: + data = NimSeqPrinter(self.val['data']) + for idxStr, entry in data.children(): + if int(entry['Field0']) > 0: + yield ("data." + idxStr + ".Field1", str(entry['Field1'])) + +################################################################################ + +class NimSeqPrinter: + # the pointer is explicity part of the type. So it is part of + # ``pattern``. + pattern = re.compile(r'^tySequence_\w* \*$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'array' + + def to_string(self): + len = 0 + cap = 0 + if self.val: + len = int(self.val['Sup']['len']) + cap = int(self.val['Sup']['reserved']) + + return 'seq({0}, {1})'.format(len, cap) + + def children(self): + if self.val: + length = int(self.val['Sup']['len']) + #align = len(str(length - 1)) + for i in range(length): + yield ("data[{0}]".format(i), self.val["data"][i]) + +################################################################################ + +class NimArrayPrinter: + pattern = re.compile(r'^tyArray_\w*$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'array' + + def to_string(self): + return 'array' + + def children(self): + length = self.val.type.sizeof // self.val[0].type.sizeof + align = len(str(length-1)) + for i in range(length): + yield ("[{0:>{1}}]".format(i, align), self.val[i]) + +################################################################################ + +class NimStringTablePrinter: + pattern = re.compile(r'^tyObject_(StringTableObj)_([A-Za-z0-9]*)(:? \*)?$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'map' + + def to_string(self): + counter = 0 + capacity = 0 + if self.val: + counter = int(self.val['counter']) + if self.val['data']: + capacity = int(self.val['data']['Sup']['len']) + + return 'StringTableObj({0}, {1})'.format(counter, capacity) + + def children(self): + if self.val: + data = NimSeqPrinter(self.val['data']) + for idxStr, entry in data.children(): + if int(entry['Field2']) > 0: + yield (idxStr + ".Field0", entry['Field0']) + yield (idxStr + ".Field1", entry['Field1']) + +################################################################ + +class NimTablePrinter: + pattern = re.compile(r'^tyObject_(Table)_([A-Za-z0-9]*)(:? \*)?$') + + def __init__(self, val): + self.val = val + # match = self.pattern.match(self.val.type.name) + + def display_hint(self): + return 'map' + + def to_string(self): + counter = 0 + capacity = 0 + if self.val: + counter = int(self.val['counter']) + if self.val['data']: + capacity = int(self.val['data']['Sup']['len']) + + return 'Table({0}, {1})'.format(counter, capacity) + + def children(self): + if self.val: + data = NimSeqPrinter(self.val['data']) + for idxStr, entry in data.children(): + if int(entry['Field0']) > 0: + yield (idxStr + '.Field1', entry['Field1']) + yield (idxStr + '.Field2', entry['Field2']) + + +################################################################ + +# this is untested, therefore disabled + +# class NimObjectPrinter: +# pattern = re.compile(r'^tyObject_.*$') + +# def __init__(self, val): +# self.val = val + +# def display_hint(self): +# return 'object' + +# def to_string(self): +# return str(self.val.type) + +# def children(self): +# if not self.val: +# yield "object", "<nil>" +# raise StopIteration + +# for (i, field) in enumerate(self.val.type.fields()): +# if field.type.code == gdb.TYPE_CODE_UNION: +# yield _union_field +# else: +# yield (field.name, self.val[field]) + +# def _union_field(self, i, field): +# rti = getNimRti(self.val.type.name) +# if rti is None: +# return (field.name, "UNION field can't be displayed without RTI") + +# node_sons = rti['node'].dereference()['sons'] +# prev_field = self.val.type.fields()[i - 1] + +# descriminant_node = None +# for i in range(int(node['len'])): +# son = node_sons[i].dereference() +# if son['name'].string("utf-8", "ignore") == str(prev_field.name): +# descriminant_node = son +# break +# if descriminant_node is None: +# raise ValueError("Can't find union descriminant field in object RTI") + +# if descriminant_node is None: raise ValueError("Can't find union field in object RTI") +# union_node = descriminant_node['sons'][int(self.val[prev_field])].dereference() +# union_val = self.val[field] + +# for f1 in union_val.type.fields(): +# for f2 in union_val[f1].type.fields(): +# if str(f2.name) == union_node['name'].string("utf-8", "ignore"): +# return (str(f2.name), union_val[f1][f2]) + +# raise ValueError("RTI is absent or incomplete, can't find union definition in RTI") + + +################################################################################ + +def makematcher(klass): + def matcher(val): + typeName = str(val.type) + try: + if hasattr(klass, 'pattern') and hasattr(klass, '__name__'): + # print(typeName + " <> " + klass.__name__) + if klass.pattern.match(typeName): + return klass(val) + except Exception as e: + print(klass) + printErrorOnce(typeName, "No matcher for type '" + typeName + "': " + str(e) + "\n") + return matcher + +nimobjfile.pretty_printers = [] +nimobjfile.pretty_printers.extend([makematcher(var) for var in list(vars().values()) if hasattr(var, 'pattern')]) diff --git a/tools/nim.bash-completion b/tools/nim.bash-completion index 4f62da986..8e569079a 100644 --- a/tools/nim.bash-completion +++ b/tools/nim.bash-completion @@ -10,12 +10,12 @@ _nim() if [ $COMP_CWORD -eq 1 ] ; then # first item - suggest commands - kw="compile c doc doc2 compileToC cc compileToCpp cpp compileToOC objc js e rst2html rst2tex jsondoc jsondoc2 buildIndex genDepend dump check" + kw="compile c doc compileToC cc compileToCpp cpp compileToOC objc js e rst2html rst2tex jsondoc buildIndex genDepend dump check" COMPREPLY=( $( compgen -W "${kw}" -- $cur ) ) return 0 fi case $prev in - --stackTrace|--lineTrace|--threads|-x|--checks|--objChecks|--fieldChecks|--rangeChecks|--boundChecks|--overflowChecks|-a|--assertions|--floatChecks|--nanChecks|--infChecks|--deadCodeElim) + --stackTrace|--lineTrace|--threads|-x|--checks|--objChecks|--fieldChecks|--rangeChecks|--boundChecks|--overflowChecks|-a|--assertions|--floatChecks|--nanChecks|--infChecks) # Options that require on/off [[ "$cur" == "=" ]] && cur="" COMPREPLY=( $(compgen -W 'on off' -- "$cur") ) @@ -32,7 +32,7 @@ _nim() return 0 ;; *) - kw="-r -p= --path= -d= --define= -u= --undef= -f --forceBuild --opt= --app= --stackTrace= --lineTrace= --threads= -x= --checks= --objChecks= --fieldChecks= --rangeChecks= --boundChecks= --overflowChecks= -a= --assertions= --floatChecks= --nanChecks= --infChecks= --deadCodeElim=" + kw="-r -p= --path= -d= --define= -u= --undef= -f --forceBuild --opt= --app= --stackTrace= --lineTrace= --threads= -x= --checks= --objChecks= --fieldChecks= --rangeChecks= --boundChecks= --overflowChecks= -a= --assertions= --floatChecks= --nanChecks= --infChecks=" COMPREPLY=( $( compgen -W "${kw}" -- $cur ) ) _filedir '@(nim)' #$split diff --git a/tools/nim.zsh-completion b/tools/nim.zsh-completion index 135649c90..e9b9002fb 100644 --- a/tools/nim.zsh-completion +++ b/tools/nim.zsh-completion @@ -5,7 +5,6 @@ _nim() { ':command:(( {compile,c}\:compile\ project\ with\ default\ code\ generator\ C doc\:generate\ the\ documentation\ for\ inputfile - doc2\:generate\ the\ documentation\ for\ inputfile {compileToC,cc}\:compile\ project\ with\ C\ code\ generator {compileToCpp,cpp}\:compile\ project\ to\ C++\ code {compileToOC,objc}\:compile\ project\ to\ Objective\ C\ code @@ -14,7 +13,6 @@ _nim() { rst2html\:convert\ a\ reStructuredText\ file\ to\ HTML rst2tex\:convert\ a\ reStructuredText\ file\ to\ TeX jsondoc\:extract\ the\ documentation\ to\ a\ json\ file - jsondoc2\:extract\ documentation\ to\ a\ json\ file\ using\ doc2 buildIndex\:build\ an\ index\ for\ the\ whole\ documentation genDepend\:generate\ a\ DOT\ file\ containing\ the\ module\ dependency\ graph dump\:dump\ all\ defined\ conditionals\ and\ search\ paths @@ -62,8 +60,6 @@ _nim() { '*--infChecks=off[turn Inf checks off]' \ '*--nilChecks=on[turn nil checks on]' \ '*--nilChecks=off[turn nil checks off]' \ - '*--deadCodeElim=on[whole program dead code elimination on]' \ - '*--deadCodeElim=off[whole program dead code elimination off]' \ '*--opt=none[do not optimize]' \ '*--opt=speed[optimize for speed|size - use -d:release for a release build]' \ '*--opt=size[optimize for size]' \ diff --git a/tools/nimgrep.nim b/tools/nimgrep.nim index e9c1b26fa..8c6353e31 100644 --- a/tools/nimgrep.nim +++ b/tools/nimgrep.nim @@ -11,7 +11,7 @@ import os, strutils, parseopt, pegs, re, terminal const - Version = "1.1" + Version = "1.2" Usage = "nimgrep - Nim Grep Utility Version " & Version & """ (c) 2012 Andreas Rumpf @@ -33,7 +33,10 @@ Options: --ignoreStyle, -y be style insensitive --ext:EX1|EX2|... only search the files with the given extension(s) --nocolor output will be given without any colours. + --oneline show file on each matched line --verbose be verbose: list every processed file + --filenames find the pattern in the filenames, not in the contents + of the file --help, -h shows this help --version, -v shows the version """ @@ -41,7 +44,7 @@ Options: type TOption = enum optFind, optReplace, optPeg, optRegex, optRecursive, optConfirm, optStdin, - optWord, optIgnoreCase, optIgnoreStyle, optVerbose + optWord, optIgnoreCase, optIgnoreStyle, optVerbose, optFilenames TOptions = set[TOption] TConfirmEnum = enum ceAbort, ceYes, ceAll, ceNo, ceNone @@ -56,6 +59,7 @@ var extensions: seq[string] = @[] options: TOptions = {optRegex} useWriteStyled = true + oneline = false proc ask(msg: string): string = stdout.write(msg) @@ -103,9 +107,12 @@ proc writeColored(s: string) = stdout.write(s) proc highlight(s, match, repl: string, t: tuple[first, last: int], - line: int, showRepl: bool) = + filename:string, line: int, showRepl: bool) = const alignment = 6 - stdout.write(line.`$`.align(alignment), ": ") + if oneline: + stdout.write(filename, ":", line, ": ") + else: + stdout.write(line.`$`.align(alignment), ": ") var x = beforePattern(s, t.first) var y = afterPattern(s, t.last) for i in x .. t.first-1: stdout.write(s[i]) @@ -121,20 +128,23 @@ proc highlight(s, match, repl: string, t: tuple[first, last: int], stdout.write("\n") stdout.flushFile() -proc processFile(pattern; filename: string) = +proc processFile(pattern; filename: string; counter: var int) = var filenameShown = false template beforeHighlight = - if not filenameShown and optVerbose notin options: + if not filenameShown and optVerbose notin options and not oneline: stdout.writeLine(filename) stdout.flushFile() filenameShown = true var buffer: string - try: - buffer = system.readFile(filename) - except IOError: - echo "cannot open file: ", filename - return + if optFilenames in options: + buffer = filename + else: + try: + buffer = system.readFile(filename) + except IOError: + echo "cannot open file: ", filename + return if optVerbose in options: stdout.writeLine(filename) stdout.flushFile() @@ -156,12 +166,13 @@ proc processFile(pattern; filename: string) = var wholeMatch = buffer.substr(t.first, t.last) beforeHighlight() + inc counter if optReplace notin options: - highlight(buffer, wholeMatch, "", t, line, showRepl=false) + highlight(buffer, wholeMatch, "", t, filename, line, showRepl=false) else: let r = replace(wholeMatch, pattern, replacement % matches) if optConfirm in options: - highlight(buffer, wholeMatch, r, t, line, showRepl=true) + highlight(buffer, wholeMatch, r, t, filename, line, showRepl=true) case confirm() of ceAbort: quit(0) of ceYes: reallyReplace = true @@ -174,7 +185,7 @@ proc processFile(pattern; filename: string) = reallyReplace = false options.excl(optConfirm) else: - highlight(buffer, wholeMatch, r, t, line, showRepl=reallyReplace) + highlight(buffer, wholeMatch, r, t, filename, line, showRepl=reallyReplace) if reallyReplace: result.add(buffer.substr(i, t.first-1)) result.add(r) @@ -231,17 +242,17 @@ proc styleInsensitive(s: string): string = addx() else: addx() -proc walker(pattern; dir: string) = +proc walker(pattern; dir: string; counter: var int) = for kind, path in walkDir(dir): case kind of pcFile: if extensions.len == 0 or path.hasRightExt(extensions): - processFile(pattern, path) + processFile(pattern, path, counter) of pcDir: if optRecursive in options: - walker(pattern, path) + walker(pattern, path, counter) else: discard - if existsFile(dir): processFile(pattern, dir) + if existsFile(dir): processFile(pattern, dir, counter) proc writeHelp() = stdout.write(Usage) @@ -286,7 +297,9 @@ for kind, key, val in getopt(): of "ignorestyle", "y": incl(options, optIgnoreStyle) of "ext": extensions.add val.split('|') of "nocolor": useWriteStyled = false + of "oneline": oneline = true of "verbose": incl(options, optVerbose) + of "filenames": incl(options, optFilenames) of "help", "h": writeHelp() of "version", "v": writeVersion() else: writeHelp() @@ -298,16 +311,18 @@ when defined(posix): checkOptions({optFind, optReplace}, "find", "replace") checkOptions({optPeg, optRegex}, "peg", "re") checkOptions({optIgnoreCase, optIgnoreStyle}, "ignore_case", "ignore_style") +checkOptions({optFilenames, optReplace}, "filenames", "replace") if optStdin in options: pattern = ask("pattern [ENTER to exit]: ") - if isNil(pattern) or pattern.len == 0: quit(0) + if pattern.len == 0: quit(0) if optReplace in options: replacement = ask("replacement [supports $1, $# notations]: ") if pattern.len == 0: writeHelp() else: + var counter = 0 if filenames.len == 0: filenames.add(os.getCurrentDir()) if optRegex notin options: @@ -319,7 +334,7 @@ else: pattern = "\\i " & pattern let pegp = peg(pattern) for f in items(filenames): - walker(pegp, f) + walker(pegp, f, counter) else: var reflags = {reStudy, reExtended} if optIgnoreStyle in options: @@ -330,5 +345,6 @@ else: reflags.incl reIgnoreCase let rep = re(pattern, reflags) for f in items(filenames): - walker(rep, f) - + walker(rep, f, counter) + if not oneline: + stdout.write($counter & " matches\n") diff --git a/tools/niminst/buildbat.tmpl b/tools/niminst/buildbat.tmpl index 278b6caea..6767461e3 100644 --- a/tools/niminst/buildbat.tmpl +++ b/tools/niminst/buildbat.tmpl @@ -1,5 +1,5 @@ #? stdtmpl(subsChar='?') | standard -#proc generateBuildBatchScript(c: ConfigData, winIndex, cpuIndex: int): string = +#proc generateBuildBatchScript(c: ConfigData, winIndex, cpuIndex32, cpuIndex64: int): string = # result = "@echo off\nREM Generated by niminst\n" SET CC=gcc SET LINKER=gcc @@ -7,26 +7,62 @@ SET COMP_FLAGS=?{c.ccompiler.flags} SET LINK_FLAGS=?{c.linker.flags} SET BIN_DIR=?{firstBinPath(c).toWin} +REM Detect gcc arch +IF DEFINED ARCH ( + ECHO Forcing %CC% arch +) ELSE ( + ECHO Detecting %CC% arch + ECHO int main^(^) { return sizeof^(void *^); } | gcc -xc - -o archtest && archtest + IF ERRORLEVEL 4 SET ARCH=32 + IF ERRORLEVEL 8 SET ARCH=64 + del archtest.* +) +ECHO Building with %ARCH% bit %CC% + if EXIST ..\koch.nim SET BIN_DIR=..\bin if NOT EXIST %BIN_DIR%\nul mkdir %BIN_DIR% REM call the compiler: +IF %ARCH% EQU 32 ( + # block win32: # var linkCmd = "" -# for ff in items(c.cfiles[winIndex][cpuIndex]): -# let f = ff.toWin -ECHO %CC% %COMP_FLAGS% -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")} -CALL %CC% %COMP_FLAGS% -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")} -# linkCmd.add(" " & changeFileExt(f, "o")) -IF ERRORLEVEL 1 (GOTO:END) -# end for +# if cpuIndex32 != -1: +# for ff in items(c.cfiles[winIndex][cpuIndex32]): +# let f = ff.toWin + ECHO %CC% %COMP_FLAGS% -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")} + CALL %CC% %COMP_FLAGS% -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")} +# linkCmd.add(" " & changeFileExt(f, "o")) + IF ERRORLEVEL 1 (GOTO:END) +# end for +# end if + + ECHO %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS% + CALL %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS% + +# end block + +) ELSE IF %ARCH% EQU 64 ( + +# block win64: +# var linkCmd = "" +# if cpuIndex64 != -1: +# for ff in items(c.cfiles[winIndex][cpuIndex64]): +# let f = ff.toWin + ECHO %CC% %COMP_FLAGS% -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")} + CALL %CC% %COMP_FLAGS% -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")} +# linkCmd.add(" " & changeFileExt(f, "o")) + IF ERRORLEVEL 1 (GOTO:END) +# end for +# end if -ECHO %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS% -CALL %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS% + ECHO %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS% + CALL %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS% # end block +) :END IF ERRORLEVEL 1 ( @@ -35,6 +71,8 @@ IF ERRORLEVEL 1 ( ECHO CSource compilation failed. Please check that the gcc compiler is in ECHO the PATH environment variable, and that you are calling the batch script ECHO that matches the target architecture of the compiler. + ECHO. + ECHO Use build.bat to autodetect the compiler architecture. ) ELSE ( ECHO SUCCESS ) diff --git a/tools/niminst/buildsh.tmpl b/tools/niminst/buildsh.tmpl index 3e7d8ae6e..be6d43754 100644 --- a/tools/niminst/buildsh.tmpl +++ b/tools/niminst/buildsh.tmpl @@ -39,8 +39,8 @@ do esac done -CC="gcc" -LINKER="gcc" +CC="${CC:-gcc}" +LINKER="${LD:-gcc}" COMP_FLAGS="${CPPFLAGS:-} ${CFLAGS:-} ?{c.ccompiler.flags}$extraBuildArgs" LINK_FLAGS="${LDFLAGS:-} ?{c.linker.flags}" PS4="" diff --git a/tools/niminst/deinstall.tmpl b/tools/niminst/deinstall.tmpl index bba310f76..8b4477369 100644 --- a/tools/niminst/deinstall.tmpl +++ b/tools/niminst/deinstall.tmpl @@ -17,7 +17,7 @@ if [ $# -eq 1 ] ; then ;; "/usr/bin") bindir=/usr/bin - configdir=/etc + configdir=/etc/?proj libdir=/usr/lib/?proj docdir=/usr/share/?proj/doc datadir=/usr/share/?proj/data @@ -25,7 +25,7 @@ if [ $# -eq 1 ] ; then ;; "/usr/local/bin") bindir=/usr/local/bin - configdir=/etc + configdir=/etc/?proj libdir=/usr/local/lib/?proj docdir=/usr/local/share/?proj/doc datadir=/usr/local/share/?proj/data diff --git a/tools/niminst/install.tmpl b/tools/niminst/install.tmpl index 91504891d..a78914ecd 100644 --- a/tools/niminst/install.tmpl +++ b/tools/niminst/install.tmpl @@ -30,7 +30,7 @@ if [ $# -eq 1 ] ; then ;; "/usr/bin") bindir=/usr/bin - configdir=/etc + configdir=/etc/?proj libdir=/usr/lib/?proj docdir=/usr/share/?proj/doc datadir=/usr/share/?proj/data @@ -38,7 +38,7 @@ if [ $# -eq 1 ] ; then ;; "/usr/local/bin") bindir=/usr/local/bin - configdir=/etc + configdir=/etc/?proj libdir=/usr/local/lib/?proj docdir=/usr/local/share/?proj/doc datadir=/usr/local/share/?proj/data @@ -70,6 +70,7 @@ if [ $# -eq 1 ] ; then mkdir -p $libdir mkdir -p $docdir + mkdir -p $configdir mkdir -p $nimbleDir/ echo "copying files..." #var createdDirs = newStringTable() diff --git a/tools/niminst/niminst.nim b/tools/niminst/niminst.nim index c2816a0ef..84653f86f 100644 --- a/tools/niminst/niminst.nim +++ b/tools/niminst/niminst.nim @@ -21,7 +21,8 @@ const maxOS = 20 # max number of OSes maxCPU = 20 # max number of CPUs buildShFile = "build.sh" - buildBatFile32 = "build.bat" + buildBatFile = "build.bat" + buildBatFile32 = "build32.bat" buildBatFile64 = "build64.bat" makeFile = "makefile" installShFile = "install.sh" @@ -482,7 +483,8 @@ proc deduplicateFiles(c: var ConfigData) = let build = getOutputDir(c) for osA in countup(1, c.oses.len): for cpuA in countup(1, c.cpus.len): - if c.cfiles[osA][cpuA].isNil: c.cfiles[osA][cpuA] = @[] + when not defined(nimNoNilSeqs): + if c.cfiles[osA][cpuA].isNil: c.cfiles[osA][cpuA] = @[] if c.explicitPlatforms and not c.platforms[osA][cpuA]: continue for dup in mitems(c.cfiles[osA][cpuA]): let key = $secureHashFile(build / dup) @@ -542,12 +544,13 @@ proc srcdist(c: var ConfigData) = inclFilePermissions(getOutputDir(c) / buildShFile, {fpUserExec, fpGroupExec, fpOthersExec}) writeFile(getOutputDir(c) / makeFile, generateMakefile(c), "\10") if winIndex >= 0: + if intel32Index >= 0 or intel64Index >= 0: + writeFile(getOutputDir(c) / buildBatFile, + generateBuildBatchScript(c, winIndex, intel32Index, intel64Index), "\13\10") if intel32Index >= 0: - writeFile(getOutputDir(c) / buildBatFile32, - generateBuildBatchScript(c, winIndex, intel32Index), "\13\10") + writeFile(getOutputDir(c) / buildBatFile32, "SET ARCH=32\nCALL build.bat\n") if intel64Index >= 0: - writeFile(getOutputDir(c) / buildBatFile64, - generateBuildBatchScript(c, winIndex, intel64Index), "\13\10") + writeFile(getOutputDir(c) / buildBatFile64, "SET ARCH=64\nCALL build.bat\n") writeInstallScripts(c) # --------------------- generate inno setup ----------------------------------- @@ -593,6 +596,7 @@ when haveZipLib: else: n = c.outdir / n var z: ZipArchive if open(z, n, fmWrite): + addFile(z, proj / buildBatFile, "build" / buildBatFile) addFile(z, proj / buildBatFile32, "build" / buildBatFile32) addFile(z, proj / buildBatFile64, "build" / buildBatFile64) addFile(z, proj / buildShFile, "build" / buildShFile) @@ -631,11 +635,12 @@ proc xzDist(c: var ConfigData; windowsZip=false) = if not dirExists(destDir): createDir(destDir) copyFileWithPermissions(src, dest) - if not windowsZip and not existsFile("build" / buildBatFile32): + if not windowsZip and not existsFile("build" / buildBatFile): quit("No C sources found in ./build/, please build by running " & "./koch csource -d:release.") if not windowsZip: + processFile(proj / buildBatFile, "build" / buildBatFile) processFile(proj / buildBatFile32, "build" / buildBatFile32) processFile(proj / buildBatFile64, "build" / buildBatFile64) processFile(proj / buildShFile, "build" / buildShFile) diff --git a/tools/nimweb.nim b/tools/nimweb.nim index c8b87c1f2..61cae5170 100644 --- a/tools/nimweb.nim +++ b/tools/nimweb.nim @@ -13,16 +13,18 @@ import from xmltree import escape -const gitRepo = "https://github.com/nim-lang/Nim" - type TKeyValPair = tuple[key, id, val: string] TConfigData = object of RootObj tabs, links: seq[TKeyValPair] doc, srcdoc, srcdoc2, webdoc, pdf: seq[string] - authors, projectName, projectTitle, logo, infile, outdir, ticker: string + authors, projectName, projectTitle, logo, infile, ticker: string vars: StringTableRef + nimCompiler: string nimArgs: string + gitURL: string + docHTMLOutput: string + webUploadOutput: string quotations: Table[string, tuple[quote, author: string]] numProcessors: int # Set by parallelBuild:n, only works for values > 0. gaId: string # google analytics ID, nil means analytics are disabled @@ -51,8 +53,10 @@ proc initConfigData(c: var TConfigData) = c.webdoc = @[] c.pdf = @[] c.infile = "" - c.outdir = "" c.nimArgs = "--hint[Conf]:off --hint[Path]:off --hint[Processing]:off -d:boot " + c.gitURL = "https://github.com/nim-lang/Nim" + c.docHTMLOutput = "doc/html" + c.webUploadOutput = "web/upload" c.authors = "" c.projectTitle = "" c.projectName = "" @@ -79,14 +83,19 @@ const Usage: nimweb [options] ini-file[.ini] [compile_options] Options: - -o, --output:dir set the output directory (default: same as ini-file) - --var:name=value set the value of a variable -h, --help shows this help -v, --version shows the version + -o, --output overrides output directory instead of default + web/upload and doc/html + --nimCompiler overrides nim compiler; default = bin/nim + --var:name=value set the value of a variable --website only build the website, not the full documentation --pdf build the PDF version of the documentation --json2 build JSON of the documentation --onlyDocs build only the documentation + --git.url override base url in generated doc links + --git.commit override commit/branch in generated doc links 'source' + --git.devel override devel branch in generated doc links 'edit' Compile_options: will be passed to the Nim compiler """ @@ -145,7 +154,11 @@ proc parseCmdLine(c: var TConfigData) = of "version", "v": stdout.write(version & "\n") quit(0) - of "o", "output": c.outdir = val + of "output", "o": + c.webUploadOutput = val + c.docHTMLOutput = val / "docs" + of "nimcompiler": + c.nimCompiler = val of "parallelbuild": try: let num = parseInt(val) @@ -163,8 +176,14 @@ proc parseCmdLine(c: var TConfigData) = of "googleanalytics": c.gaId = val c.nimArgs.add("--doc.googleAnalytics:" & val & " ") + of "git.url": + c.gitURL = val + of "git.commit": + c.nimArgs.add("--git.commit:" & val & " ") + of "git.devel": + c.nimArgs.add("--git.devel:" & val & " ") else: - echo("Invalid argument $1" % [key]) + echo("Invalid argument '$1'" % [key]) quit(usage) of cmdEnd: break if c.infile.len == 0: quit(usage) @@ -244,15 +263,14 @@ proc parseIniFile(c: var TConfigData) = close(p) if c.projectName.len == 0: c.projectName = changeFileExt(extractFilename(c.infile), "") - if c.outdir.len == 0: - c.outdir = splitFile(c.infile).dir # ------------------- main ---------------------------------------------------- proc exe(f: string): string = return addFileExt(f, ExeExt) -proc findNim(): string = +proc findNim(c: TConfigData): string = + if c.nimCompiler.len > 0: return c.nimCompiler var nim = "nim".exe result = "bin" / nim if existsFile(result): return @@ -285,14 +303,10 @@ proc mexec(cmds: openarray[string], processors: int) = proc buildDocSamples(c: var TConfigData, destPath: string) = ## Special case documentation sample proc. ## - ## The docgen sample needs to be generated twice with different commands, so - ## it didn't make much sense to integrate into the existing generic - ## documentation builders. - const src = "doc"/"docgen_sample.nim" - exec(findNim() & " doc $# -o:$# $#" % - [c.nimArgs, destPath / "docgen_sample.html", src]) - exec(findNim() & " doc2 $# -o:$# $#" % - [c.nimArgs, destPath / "docgen_sample2.html", src]) + ## TODO: consider integrating into the existing generic documentation builders + ## now that we have a single `doc` command. + exec(findNim(c) & " doc $# -o:$# $#" % + [c.nimArgs, destPath / "docgen_sample.html", "doc" / "docgen_sample.nim"]) proc pathPart(d: string): string = splitFile(d).dir.replace('\\', '/') @@ -302,23 +316,23 @@ proc buildDoc(c: var TConfigData, destPath: string) = commands = newSeq[string](len(c.doc) + len(c.srcdoc) + len(c.srcdoc2)) i = 0 for d in items(c.doc): - commands[i] = findNim() & " rst2html $# --git.url:$# -o:$# --index:on $#" % - [c.nimArgs, gitRepo, + commands[i] = findNim(c) & " rst2html $# --git.url:$# -o:$# --index:on $#" % + [c.nimArgs, c.gitURL, destPath / changeFileExt(splitFile(d).name, "html"), d] i.inc for d in items(c.srcdoc): - commands[i] = findNim() & " doc0 $# --git.url:$# -o:$# --index:on $#" % - [c.nimArgs, gitRepo, + commands[i] = findNim(c) & " doc0 $# --git.url:$# -o:$# --index:on $#" % + [c.nimArgs, c.gitURL, destPath / changeFileExt(splitFile(d).name, "html"), d] i.inc for d in items(c.srcdoc2): - commands[i] = findNim() & " doc2 $# --git.url:$# -o:$# --index:on $#" % - [c.nimArgs, gitRepo, + commands[i] = findNim(c) & " doc2 $# --git.url:$# -o:$# --index:on $#" % + [c.nimArgs, c.gitURL, destPath / changeFileExt(splitFile(d).name, "html"), d] i.inc mexec(commands, c.numProcessors) - exec(findNim() & " buildIndex -o:$1/theindex.html $1" % [destPath]) + exec(findNim(c) & " buildIndex -o:$1/theindex.html $1" % [destPath]) proc buildPdfDoc(c: var TConfigData, destPath: string) = createDir(destPath) @@ -327,7 +341,7 @@ proc buildPdfDoc(c: var TConfigData, destPath: string) = else: const pdflatexcmd = "pdflatex -interaction=nonstopmode " for d in items(c.pdf): - exec(findNim() & " rst2tex $# $#" % [c.nimArgs, d]) + exec(findNim(c) & " rst2tex $# $#" % [c.nimArgs, d]) # call LaTeX twice to get cross references right: exec(pdflatexcmd & changeFileExt(d, "tex")) exec(pdflatexcmd & changeFileExt(d, "tex")) @@ -347,8 +361,8 @@ proc buildAddDoc(c: var TConfigData, destPath: string) = # build additional documentation (without the index): var commands = newSeq[string](c.webdoc.len) for i, doc in pairs(c.webdoc): - commands[i] = findNim() & " doc2 $# --git.url:$# -o:$# $#" % - [c.nimArgs, gitRepo, + commands[i] = findNim(c) & " doc2 $# --git.url:$# -o:$# $#" % + [c.nimArgs, c.gitURL, destPath / changeFileExt(splitFile(doc).name, "html"), doc] mexec(commands, c.numProcessors) @@ -418,7 +432,7 @@ proc generateRss(outputFilename: string, news: seq[TRssItem]) = href = rss.url), updatedDate(rss.year, rss.month, rss.day), "<author><name>Nim</name></author>", - content(xmltree.escape(rss.content), `type` = "text"), + content(xmltree.escape(rss.content), `type` = "text") )) output.write("""</feed>""") @@ -431,9 +445,9 @@ proc buildNewsRss(c: var TConfigData, destPath: string) = generateRss(destFilename, parseNewsTitles(srcFilename)) -proc buildJS(destPath: string) = - exec(findNim() & " js -d:release --out:$1 web/nimblepkglist.nim" % - [destPath / "nimblepkglist.js"]) +proc buildJS(c: TConfigData) = + exec(findNim(c) & " js -d:release --out:$1 web/nimblepkglist.nim" % + [c.webUploadOutput / "nimblepkglist.js"]) proc readSponsors(sponsorsFile: string): seq[Sponsor] = result = @[] @@ -464,7 +478,7 @@ const cmdRst2Html = " rst2html --compileonly $1 -o:web/$2.temp web/$2.rst" proc buildPage(c: var TConfigData, file, title, rss: string, assetDir = "") = - exec(findNim() & cmdRst2Html % [c.nimArgs, file]) + exec(findNim(c) & cmdRst2Html % [c.nimArgs, file]) var temp = "web" / changeFileExt(file, "temp") var content: string try: @@ -472,7 +486,7 @@ proc buildPage(c: var TConfigData, file, title, rss: string, assetDir = "") = except IOError: quit("[Error] cannot open: " & temp) var f: File - var outfile = "web/upload/$#.html" % file + var outfile = c.webUploadOutput / "$#.html" % file if not existsDir(outfile.splitFile.dir): createDir(outfile.splitFile.dir) if open(f, outfile, fmWrite): @@ -502,27 +516,25 @@ proc buildWebsite(c: var TConfigData) = let rss = if file in ["news", "index"]: extractFilename(rssUrl) else: "" if '.' in file: continue buildPage(c, file, if file == "question": "FAQ" else: file, rss) - copyDir("web/assets", "web/upload/assets") - buildNewsRss(c, "web/upload") - buildSponsors(c, "web/upload") - buildNews(c, "web/news", "web/upload/news") + copyDir("web/assets", c.webUploadOutput / "assets") + buildNewsRss(c, c.webUploadOutput) + buildSponsors(c, c.webUploadOutput) + buildNews(c, "web/news", c.webUploadOutput / "news") + +proc onlyDocs(c: var TConfigData) = + createDir(c.docHTMLOutput) + buildDocSamples(c, c.docHTMLOutput) + buildDoc(c, c.docHTMLOutput) proc main(c: var TConfigData) = buildWebsite(c) - buildJS("web/upload") - const docup = "web/upload/" & NimVersion + buildJS(c) + let docup = c.webUploadOutput / NimVersion createDir(docup) buildAddDoc(c, docup) buildDocSamples(c, docup) buildDoc(c, docup) - createDir("doc/html") - buildDocSamples(c, "doc/html") - buildDoc(c, "doc/html") - -proc onlyDocs(c: var TConfigData) = - createDir("doc/html") - buildDocSamples(c, "doc/html") - buildDoc(c, "doc/html") + onlyDocs(c) proc json2(c: var TConfigData) = const destPath = "web/json2" @@ -530,8 +542,8 @@ proc json2(c: var TConfigData) = var i = 0 for d in items(c.srcdoc2): createDir(destPath / splitFile(d).dir) - commands[i] = findNim() & " jsondoc2 $# --git.url:$# -o:$# --index:on $#" % - [c.nimArgs, gitRepo, + commands[i] = findNim(c) & " jsondoc2 $# --git.url:$# -o:$# --index:on $#" % + [c.nimArgs, c.gitURL, destPath / changeFileExt(d, "json"), d] i.inc diff --git a/tools/vccenv/vccenv.nim b/tools/vccenv/vccenv.nim index a335efd10..a5af7994e 100644 --- a/tools/vccenv/vccenv.nim +++ b/tools/vccenv/vccenv.nim @@ -19,7 +19,7 @@ proc getVsComnToolsPath*(): TaintedString = return vsComnToolsEnvVal proc getVccEnv*(platform: string, windowsStoreSdk: bool = false, - sdkVersion: string = nil): StringTableRef = + sdkVersion: string = ""): StringTableRef = var comSpecCommandString = getEnv comSpecEnvKey if comSpecCommandString.len == 0: comSpecCommandString = "cmd" diff --git a/tools/vccenv/vccexe.nim b/tools/vccenv/vccexe.nim index 892246830..e86e14c0f 100644 --- a/tools/vccenv/vccexe.nim +++ b/tools/vccenv/vccexe.nim @@ -5,14 +5,14 @@ when defined(release): else: let vccOptions = {poEchoCmd, poParentStreams} -const +const platformPrefix = "--platform" winstorePrefix = "--winstore" sdkversionPrefix = "--sdkversion" platformSepIdx = platformPrefix.len sdkversionSepIdx = sdkversionPrefix.len - + HelpText = """ +-----------------------------------------------------------------+ | Microsoft C/C++ compiler wrapper for Nim | @@ -26,7 +26,7 @@ Options: <arch>: x86 | amd64 | arm | x86_amd64 | x86_arm | amd64_x86 | amd64_arm --winstore Use Windows Store (rather than desktop) development tools --sdkversion:<v> Use a specific Windows SDK version: - <v> is either the full Windows 10 SDK version number or + <v> is either the full Windows 10 SDK version number or "8.1" to use the windows 8.1 SDK Other command line arguments are passed on to the @@ -34,8 +34,8 @@ Microsoft C/C++ compiler for the specified SDK toolset """ when isMainModule: - var platformArg: string = nil - var sdkVersionArg: string = nil + var platformArg: string = "" + var sdkVersionArg: string = "" var storeArg: bool = false var clArgs: seq[TaintedString] = @[] @@ -54,7 +54,7 @@ when isMainModule: echo HelpText clArgs.add(wargv) - var vccEnvStrTab = getVccEnv(platformArg, storeArg, sdkVersionArg) + var vccEnvStrTab = getVccEnv(platformArg, storeArg, sdkVersionArg) if vccEnvStrTab != nil: for vccEnvKey, vccEnvVal in vccEnvStrTab: putEnv(vccEnvKey, vccEnvVal) diff --git a/tools/website.tmpl b/tools/website.tmpl index f9b1a219a..9e5eb2460 100644 --- a/tools/website.tmpl +++ b/tools/website.tmpl @@ -203,7 +203,7 @@ runForever() # if currentTab == "index": <script src="${rootDir}assets/index.js"></script> # end if -# if c.gaId != nil: +# if c.gaId.len != 0: <script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), diff --git a/web/website.ini b/web/website.ini index 9dc5949a0..18e7bf2cb 100644 --- a/web/website.ini +++ b/web/website.ini @@ -29,9 +29,9 @@ news: news file: ticker.html [Documentation] -doc: "endb;intern;apis;lib;manual.rst;tut1.rst;tut2.rst;nimc;overview;filters" -doc: "tools;niminst;nimgrep;gc;estp;idetools;docgen;koch;backends.rst" -doc: "nimfix.rst;nimsuggest.rst;nep1.rst;nims.rst;contributing.rst" +doc: "endb.rst;intern.txt;apis.txt;lib.rst;manual.rst;tut1.rst;tut2.rst;nimc.rst;overview.rst;filters.rst" +doc: "tools.txt;niminst.rst;nimgrep.rst;gc.rst;estp.rst;idetools.rst;docgen.rst;koch.rst;backends.txt" +doc: "nimfix.rst;nimsuggest.rst;nep1.rst;nims.rst;contributing.rst;manual/*.rst" pdf: "manual.rst;lib.rst;tut1.rst;tut2.rst;nimc.rst;niminst.rst;gc.rst" srcdoc2: "system.nim;system/nimscript;pure/ospaths" srcdoc2: "core/macros;pure/marshal;core/typeinfo" @@ -58,8 +58,8 @@ srcdoc2: "pure/memfiles;pure/subexes;pure/collections/critbits" srcdoc2: "deprecated/pure/asyncio;deprecated/pure/actors;core/locks;core/rlocks;pure/oids;pure/endians;pure/uri" srcdoc2: "pure/nimprof;pure/unittest;packages/docutils/highlite" srcdoc2: "packages/docutils/rst;packages/docutils/rstast" -srcdoc2: "packages/docutils/rstgen;pure/logging;pure/options;pure/asyncdispatch;pure/asyncnet" -srcdoc2: "pure/nativesockets;pure/asynchttpserver;pure/net;pure/selectors;pure/future" +srcdoc2: "packages/docutils/rstgen;pure/logging;pure/options;pure/asyncdispatch;pure/asyncnet;pure/asyncstreams;pure/asyncfutures" +srcdoc2: "pure/nativesockets;pure/asynchttpserver;pure/net;pure/selectors;pure/sugar" srcdoc2: "deprecated/pure/ftpclient;pure/collections/chains" srcdoc2: "pure/asyncfile;pure/asyncftpclient;pure/lenientops" srcdoc2: "pure/md5;pure/rationals" @@ -77,5 +77,6 @@ webdoc: "wrappers/mysql;wrappers/iup" webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc;wrappers/odbcsql" webdoc: "wrappers/pcre" webdoc: "wrappers/openssl" +webdoc: "js/jscore" webdoc: "posix/posix;wrappers/odbcsql" |