This new version turned out to be a major release, given the vast number of new features which made their way into it. Hope it was worth the waiting. ;-)
In preparation for the GUI support and the DSL that will come with it, a new range of datatypes has been implemented.
A pair is a couple of integer! values used to represent mainly dimensions and coordinates. Its literal representation separates the two integers by an x
character.
1x2 -56x1234 -3x-8
Pair! is a scalar datatype that supports math and bitwise operations.
1x2 + 2x3 == 3x5 3x4 * 2x3 == 6x12 10x10 * 2 == 20x20 60x10 % 4 == 0x2
To access pair elements, path syntax provides a handy way. The left value is referenced by x
or just 1, the right value is referenced by y
or just 2.
a: 5x6 a/x == 5 a/y == 6 b: a + 10x1 b/1 == 15 b/2 == 7
It also works for modifying pair elements.
a: 5x6 a/x: 0 a/2: -1 a == 0x-1
A pair! can be constructed using different approaches, depending on how pair elements are provided (as separate values or as a block).
as-pair 3 4 == 3x4 make pair! [4 5] == 4x5 make pair! 10 == 10x10
The following notable actions are supported on pairs: random, absolute, add, divide, multiply, negate, remainder, subtract, and, or, xor, pick, reverse.
Pair! will be extended in the future to handle float values, in addition to integers.
Percent! provides an elegant way to represent numbers as fractions of 100 using a natural syntax:
20% -45.285% 0.1% 100%
As it is implemented using a 64-bit float internally, it supports all the same math actions and operations as float! values, namely: random, absolute, add, divide, multiply, negate, power, remainder, round, subtract.
20 * 80% == 16 33,33% * 250 == 83.325
The tuple! datatype is a finite list of 3 up to 12 bytes. It offers a versatile way to represent different kind of values like:
Version numbers:
0.5.4 1.0.0 2.10.120
RGB colors:
red == 255.0.0 blue == 0.0.255 orange == 255.150.10
or IPv4 addresses:
127.0.0.1 192.168.1.1 255.255.0.0
Once a tuple! value is created, its size cannot be changed anymore (it is not a series!), but its elements can still be modified, using, for example, path syntax.
ip: 192.168.0.0 ip/1 == 192 ip/4: 1 ip == 192.168.0.1
The following actions are supported by tuple! values: random, add, divide, multiply, remainder, subtract, and, or, xor, length’, pick, poke, reverse.
1.2.3 + 100.10.1 == 101.12.4 255.0.0 or 0.128.0 == 255.128.0 192.168.2.1 and 255.255.0.0 == 192.168.0.0 green + blue = cyan == true red + green = yellow == true
Math operations are allowed with some other scalar datatypes, like integer!, float! and percent!.
1.2.3 * 2 == 2.4.6 red * 33% == 84.0.0 blue / 2 == 0.0.127
Tip: in order to get the list of all predefined color names from console, just use:
red>> help tuple!
We are considering for a future release, the option of extending the tuples with 3 or 4 elements to 16-bit values, as the internal storage space allow us to do so. This would make possible to support color values with up to 16-bit per component and version numbers with high build values.
This datatype provides a dictionary-like data structure, to make it easy to store key/value pairs while providing very fast lookups. Internally, keys are stored in an hashtable, using our existing MurmurHash3 implementation.
The map! datatype has its own literal format:
#(a: 3 b: "hello" c: 10) == #( a: 3 b: "hello" c: 10 )
Keys can be any value among the following datatypes: any-word!, any-string!, integer!, float!, char!.
Reading and setting keys and values in maps can be done using the familiar path syntax.
m: #(a: 3 b: "hello" c: 10) m/a == 3 m/b: 14 m == #( a: 3 b: 14 c: 10 )
In addition to that, some actions can be used when keys are not words, or when paths are not the best fit.
select m 'a == 3 put m 'a 42 put m "Monday" "pizza" m == #( a: 42 b: 14 c: 10 "Monday" "pizza" )
The new put action works as a counterpart of select, changing the value associated with a key. It will be extended to act on all series in the future.
Read more about map! in the specification document.
Red now provides a more complete exception system, allowing to throw and catch exceptions in a convenient way.
Syntax
throw <value> throw/name <value> <name> catch [...] catch/name [...] <names> <value> : any value <name> : a word for naming the exception <names> : a name or block of names
A throw will interrupt the code flow and go back up through the call stack, until a catch is reached, then resume execution just after it. By default, exceptions are anonymous, but for finer-grained control, they can be named through the /name refinement, targeting one or several specific catch statements.
catch [print "Hello" throw 1 print "John"] "Hello" == 1
Catch will catch all thrown exceptions and return the thrown value. If /name is used, only the matching named exceptions will be caught, all the others will pass through it.
If an exception is not caught by a catch call, it will result in a runtime error.
red>> throw 1 *** Throw error: no catch for throw: 1 *** Where: throw
In order to improve the ability to handle both errors and thrown exceptions at the same time, try has been extended with a new /all refinement, allowing it to catch all possible forms of exceptions, including return, exit, break and continue misuses. It is an ultimate barrier in your code, to handle runtime issues.
Set operations are now fully supported:
Those operations can be applied on following datatypes: block!, string!, bitset!, typeset!. (Hash! datatype support will be added in the next release)
In order to produce !Unable to parse tag!sets out of block! and string! values, a unique operation is provided:
union [a b c] [d a hello 123] == [a b c d hello 123] intersect [2 6 3 4 2] [5 6 9 4 3] == [6 3 4] unique "hello red world" == "helo rdw"
A /case refinement is provided for performing case-sensitive set operations (default is case-insensitive).
The /skip refinement allows to process series arguments in a record-oriented way.
Note: all these operations are implemented using hashtables internally, in order to ensure the best performances.
as-pair
Returns a pair! value made out of two integer arguments.
as-pair <x> <y> <x> : integer x value <y> : integer y value
Example:
as-pair 2 3 == 2x3
break
Exits a loop and resume evaluation after it. Can optionally return a value from the loop.
break break/return <value> <value> : returned value, any type.
Example:
loop 3 [print "hi!" break print "hidden"] hi!
continue
Interrupts a loop evaluation flow and resume at next loop iteration.
continue
Example:
loop 3 [print "hi!" continue print "hidden"] hi!hi!hi!
extend
Adds pairs of keys and values to an aggregate value. If the key is already defined, it will replace its value. Additions are done in a case-preserving way. Default lookups are case-insensitive, a /case refinement can optionally enforce case-sensitivity.
extend <aggregate> <spec> extend/case <aggredate> <spec> <aggregate> : an object! or a map! value <spec> : a block of key and values pairs
Note: support for object! is not implemented yet.
Example:
m: #(a: 3) extend m [b: 4 "c" 123] m == #( a: 3 b: 4 "c" 123 )
A new put action has been added in order to provide a modifying counterpart to the select action. Select offers a way to access series values as if they were associative data structures. Put and select are natural accessor actions for objects and maps. Default lookups are case-insensitive.
put <aggregate> key value put/case <aggregate> key value
Note: PUT support is only implemented for map! values for now, series and objects support will come in a future release.
Example:
m: #(a: 1) put m 'a 2 m == #( a: 2 )
cause-error
Causes an immediate error throw, with the provided information. Use it to generate standard or custom errors.
cause-error <type> <id> <arguments> <type> : word representing an error class <id> : word representing an error definition <arguments> : a block of optional arguments (can be empty)
Error classes and definitions can be inspected using:
help system/catalog/errors
Example:
red>> cause-error 'math 'zero-divide [] *** Math error: attempt to divide by zero *** Where: do
New natives were added in order to better support new Red features:
The global symbol table is now hashed, so lookups are now very fast and the startup time of Red runtime greatly improved. On fast modern hardware, there is no noticeable difference, but on low-end or old hardware, it can be very significant.
Set and get natives have been extended to allow path arguments, to access a word within an associative structure (an object or a map). Moreover, they also feature a /case refinement now, allowing to distinguish target words by case (useful for maps).
Collation tables that were visible in system/locale are temporarily hidden now, until we figure out a better way to expose that feature to users, without allowing nasty errors caused by race conditions (loading a script that changes the tables unexpectedly, concurrency issues,…)
More changes:
Also, thanks to PeterWAWood for the huge work on bringing a big number of new unit tests (especially for vector! datatype).
This was originally the main feature planned for the release: Compile the runtime library once and cache it on disk to speed-up compilation. Unfortunately, it has been more challenging than we expected, because the Red/System compiler constructs many word values that cannot be properly serialized by Rebol’s mold action, so cannot be load-ed back. Writing ad-hoc serializers and loaders was taking too much time, so this feature was postponed for a future release. We will probably implement shadow objects first, in the Red/System compiler, and reduce the use of non-serializable words before supporting the pre-compiled runtime. It is still a high priority, because it is the first step towards modular compilation.
We moved our chat rooms to Gitter. It’s not perfect, but nicley combines the features of Stackoverflow chat and AltME, where the Rebol and Red community meet. So far, so good. Gitter is still young, but very promising. You just need a Github account, which is a lower entrance barrier than Stackoverflow chat and its required 20 reputation points. Come there, say Hello, and ask questions about Red. :-)
This release marks the beginning of the re-integration of the android branch in master. That branch has grown up a lot in the last 12 months, to the point where merging back changes from master has become a long, complex and error-prone process. The pair! and tuple! datatype implementations were pulled from the android branch, and more code will be pulled in the next release in order to close the gap between the two branches.
Finally, the time has come to have official GUI support, and that is what the next release (0.6.0) will bring. Trying to build the GUI engine, the GUI DSL, and the Android back-end at the same time was not the best plan. The development cycle on Android is slow and debugging options limited. So Windows will be the first GUI target, where we can quickly complete the engine and DSL. Then we will merge the Android GUI back-end and toolchain in a 0.6.1 release. Those two releases should come quickly, so don’t go on holiday for too long this summer. ;-)
In the meantime, enjoy this new release! :-)
The Red Team.
This new release brings many new features, improvements and some bugfixes that will make Red more usable, especially for newcomers. The initial intent for this release was just to replace the existing console implementation, but it looked like the right time to finally implement also proper general error handling support.
The old console code we were using so far for the Red REPL was never meant to last that long, but as usual in software development, temporary solutions tend to become more permanent than planned. Though, the old console code really needed a replacement, mainly for:
libreadline
and libhistory
, they were creating too many issues on the different Unix platforms, so became troublesome for many newcomers.So, the new console code gets rid of third-party libraries and runs only on what the OS provides. The new features are:
system/console/history
system/console/prompt
Other notable console-related improvements:
about
function now returns also the build timestamp.what
function has now a more readable output.The console code is not in its final form yet, it needs to be even more modular and wrapped in a port! abstraction in the future.
Red now supports first class errors as the error!
datatype. They can be user-created or produced by the system. The error definitions are stored in the system/catalog/errors
object.
red>> help system/catalog/errors `system/catalog/errors` is an object! of value: throw object! [code type break return throw continue] note object! [code type no-load] syntax object! [code type invalid missing no-header no-rs-h... script object! [code type no-value need-value not-defined n... math object! [code type zero-divide overflow positive] access object! [code type] user object! [code type message] internal object! [code type bad-path not-here no-memory stack...
User errors can be created using make
action followed by an error integer code or a block containing the category and error name:
red>> make error! 402 *** Math error: attempt to divide by zero *** Where: ''' red>> make error! [math zero-divide] *** Math error: attempt to divide by zero *** Where: '''
These examples are displaying an error message because the error value is the returned value, we still need to implement a full exception handling mechanism using throw/catch
natives in order to enable raising user errors that can interrupt the code flow. The error throwing sub-system is implemented and used by the Red runtime and interpreter, just not exposed to the user yet.
Errors can be trapped using the try
native. An error! value will be returned if an error was generated and can be tested using the error'
function.
red>> a: 0 if error' err: try [1 / a][print "divide by zero"] divide by zero red>> probe err make error! [ code: none type: 'math id: 'zero-divide arg1: none arg2: none arg3: none near: none where: '/ stack: 3121680 ] *** Math error: attempt to divide by zero *** Where: /
Currently the console will display errors if they are the last value. That behavior will be improved once the exception system for Red will be in place.
Errors when displayed from compiled programs, provide calling stack information to make it easier to locate the source code where the error originated from. For example:
Red [] print mold 3 / 0
will produce the following error once compiled and run:
*** Math error: attempt to divide by zero *** Where: / *** Stack: print mold /
Sorting data is now supported in Red, in a polymorphic way as in Rebol. The sort
action is very versatile and useful. Let’s start from a basic example:
scores: [2 3 1 9 4 8] sort scores == [1 2 3 4 8 9]
As you can see, sort
modifies the argument series, you can keep the series unchanged by using copy
when passing it as argument:
str: "CgBbefacdA" sort copy str == "aABbCcdefg" sort/case copy str == "ABCabcdefg" str == "CgBbefacdA"
By default, sorting is not sensitive to character cases, but you can make it sensitive with the /case
refinement.
You can use /skip
refinement to specify how many elements to ignore, it’s handy when you need to sort records of a fixed size.
name-ages: [ "Larry" 45 "Curly" 50 "Mo" 42 ] sort/skip name-ages 2 == ["Curly" 50 "Larry" 45 "Mo" 42]
The /compare
refinement can be used to specify how to perform the comparison. (It does not yet support block! as argument)
names: [ "Larry" "Curly" "Mo" ] sort/compare names func [a b] [a > b] == ["Mo" "Larry" "Curly"]
Combining it with /skip refinement, you can do some complex sorting task.
name-ages: [ "Larry" 45 "Curly" 50 "Mo" 42 ] sort/skip/compare copy name-ages 2 2 ;-- sort by 2nd column == ["Mo" 42 "Larry" 45 "Curly" 50]
The /all
refinement will force the entire record to be passed to the compare function. This is useful if you need to compare one or more fields of a record while also doing a skip operation. In the following example, sorting is done by the second column, in descending order:
sort/skip/compare/all name-ages 2 func [a b][a/2 > b/2] == ["Curly" 50 "Larry" 45 "Mo" 42]
Sort uses Quicksort as its default sorting algorithm. Quicksort is very fast, but it is an unstable sorting algorithm. If you need stable sorting, just add /stable
refinement, it will then use Merge algorithm instead to perform the sort.
A couple of new datatypes were added in this release, mostly because of internal needs in Red runtime to support the new features.
The typeset!
datatype has been fully implemented, and is on par with the Rebol3 version. A typeset! value is a set of datatypes stored in a compact array of bits (up to 96 bits). Datatype lookups are very fast in typesets and they are mostly used internally for runtime type-checking support. The following actions are supported on typeset! values: make, form, mold, and, or, xor, complement, clear, find, insert, append, length’
. Comparison operators are also supported.
A preliminary implementation of the vector!
datatype is also part of this release. A vector! value is a series of number values of same datatype. The internal implementation uses a more compact memory storage format than a block! would do, while, on the surface, behaving the same way as other series. Only 32-bit integer values can be stored for now in vectors. The following actions are supported by vector! values: make, form, mold, at, back, head, head', index', insert, append, length', next, pick, skip, tail, tail'
. The implementation will be completed in future releases.
It has finally being implemented, as proper error handling support is now available. So from this release on, function arguments types will be check against the function specification and non-conforming cases will result in an error. Return value type-checking will be added later.
The type-checking might break some existing Red code around that was letting silently pass invalid arguments, so check your code with this new release before upgrading.
The compiler does not do any type checking yet, that will be added at a later stage (though, don’t expect too much from it, unless you annotate with types every function exhaustively).
Also notice that the runtime type-checking implementation is making the Red interpreter a little bit faster, thanks to a new optimized way to handle function specification blocks (an optimized spec block is cached after first call, resulting in much faster processing time afterwards).
Exceptions handling has been improved, introducing the catch
statement allowing to catch exceptions using an integer filtering value. Here is a simple example in the global context:
Red/System [] catch 100 [ print "hello" throw 10 print "<hidden>" ] print " world"
will output
hello world
The integer argument for catch intercepts only exceptions with a lower value, providing a simple, but efficient filtering system.
In addition to that, uncaught exceptions are now properly reporting a runtime error instead of passing silently. This new enhanced low-level exception system is supporting the new higher-level Red error handling system.
A couple of new compiler directives have been also added in order to strengthen the interfacing with Red layer:
#get <path>
The #get directive returns a red-value! pointer on a value referred by a Red object path. This is used internally in the runtime to conveniently access the Red system object content from Red/System code. This directive will be extended in the future to access also words from Red global context.
#in <path> <word>
The #in directive returns a red-word! pointer to a Red word bound to the object context referred by path.
In addition to many minor pending improvements, we will be working on a minor release that will introduce the Redbin format for accurately serialize Red values in binary form. Redbin format will be used to make the compilation process much faster, as it currently slows down pretty quickly as the Red-level environment code size grows up.
Enjoy this new release! :-)