N9I Overview
Written on Sun, 17 Jan 2010 22:07:24 +0000 - Last updated on Mon, 08 Feb 2010 17:19:22 +0000Conceptual programming language intended to blur the distinction between files and variables. N9I allows local and remote file accesses through a variety of protocols, as well as a simple namespace, module, and class system. Objects may be strictly typed or typeless. Functions and methods are first-class objects.
N9I Principles and Goal
Abstraction of I/O
The foremost principle of N9I is to abstract away what needs abstraction. Current mechanisms for I/O require specialized interfaces that differ from platform to platform. Stream classes may implement a plethora of methods, depending on whether you want to write characters or bytes. In some languages you are required to wrap several objects around one another just to read a line of text. The author of N9I once ran into a situation where he was required to wrap a string around a "string reader" object - an object whose purpose is to read a string. It is then that he realized that, instead of using a mess of reader objects and input classes to provide a general I/O interface, one could devise a mechanism by which I/O is effectively hidden from both the developer and from other functions. Functions will not have to mess with "readers" or "writers" but simply with strings, numbers, arrays, and objects (in other words, what would be a "XYZ stream reader" is seen as a normal string). Consider:
- A variable is a name tied to some value, which is represented by a series of bytes.
- Files contain a series of bytes and also have a name.
- The basic operations that can be done on any variable - read its value, assign it a new value - can also be done on the contents of a file.
- Files may be written to or read.
- Just like variables, files can contain many types of data.
- Therefore, it should be possible to refer to a file (on the local filesystem or on a remote server) anywhere one can refer to a variable.
- Example: reading text from a file should be as easy as getting the value of a string variable
- We can determine, using the extension, what kind of file we are working with. We can also determine what kind of value we are attempting to write to it
A good example of this already in use is Texas Instruments' TI-89/92 BASIC environment. The BASIC language on that calculator has literally no distinction between a "file" and a "variable" (everything on the TI-89/92 filesystem is referred to as a variable) although it does restrict which types of files/variables may be manipulated through BASIC (strings, numbers, lists, matrices, functions, and expressions may; text, data, and other types cannot).
Older languages require you to manually manage every byte of memory you allocate, while more modern languages include some form of garbage collection. The designer of N9I believes that the next logical step is to abstract away I/O in much the same manner.
Maximum Source Code Portability
The Java programming language attempted to introduce cross-platform binary object files ("write once, run anywhere"). These require a virtual machine, which is available on many widely used platforms. Nevertheless, Java bytecode applications never seemed to be popular on the desktop. Perhaps it was because the presence of a virtual machine slowed down overall performance. Perhaps the not-quite-right look of Java's GUI toolkit puts people off. Perhaps Java isn't as cross-platform as it tries to be ("write once, debug everywhere"). For whatever reason, "write once, run anywhere" has failed. Furthermore, it may not be feasible to introduce a virtual machine on some platforms (Apple, for instance, will not allow the JVM to run on its iPhone).
Regardless of that fact, the computing landscape is becoming more heterogenous in this decade. Free/open source operating systems such as Linux become ever-so-slightly more popular (especially outside the United States, where the iron grip of Microsoft is not as prevalent), and smartphone OSs such as Android and the iPhone OS introduce another factor. Companies who develop software typically focus on Windows only because they claim it would not be worth the money to make their software accessable to others. While it is possible to develop for multiple platforms using a conventional language, N9I is explictly designed to be portable at the source code level. A few design decisions were made regarding this - for example, N9I requires that all files on the local filesystem be referenced using a Unix path (even under Windows), and the compiler emits a warning if you use any platform-specific APIs. However, N9I compiles directly to a native binary instead of to a virtual machine (although it can be compiled into Java bytecode as well). Because N9I compiles to native code, it can provide cross-platform interfaces to OS facilities like environment variables, configuration, audio and video, and window management, while avoiding the pitfalls of a virtual machine. N9I is also an interpreted language, meaning you can run your program without needing to compile it.
N9I aims to reduce the effort and cost needed to write fully cross-platform applications. It is not feasible to expect 100% of N9I source code to compile on all of the supported platforms (i.e. because some platforms have capabilities others do not). N9I does not promise that. However, a reasonable effort is made to ensure that code that does compile on multiple platforms will function equivalently on those platforms. N9I provides a module called "Capability" that can be used to check to see if a platform supports a certain feature, such as tray icons. You can check for a Capability and write code that uses the capability only if the capability exists, rather than writing blocks of Win32-specific or Mac-specific code using OS-dependent APIs to access the capability.
@ ("at") and ResourceAlias
The foundation of N9I is an operator called @. The @, or "at operator", identifies the resource "at" the given location (which may be a string literal or a variable). By default, the at operator returns a ResourceAlias object. A ResourceAlias is "magic" in that assigning or reading a reference to a ResourceAlias will actually redirect the read or write to the specified resource. Destroying the ResourceAlias using its "unset" method clears the variable and allows it to be reused (note that unset cannot be called directly on the at operator). A ResourceAlias may also be created of a variable. Method calls (other than unset) invoked on the ResourceAlias are also redirected to the underlying resource.
A ResourceAlias can automatically "cast" itself into the needed variable type. For example, if you read from the ResourceAlias into a byte array, it will read binary data. If you read the ResourceAlias into a string or character array, it reads character data. If you write a string or character array to a ResourceAlias, N9I will write character data. If you write a byte array to a ResourceAlias, N9I will write binary data. An array of strings is represented a file in which each line corresponds to an array element. Dictionaries containing strings are represented by a key-value pair in each line, with the key and value separated by equals signs. Objects are represented as binary data, unless they are being stored to or read from an XML or JSON file (in which case it is stored as plain text). When reading or writing to a local file, N9I respects the system's preferred line ending. When accessing a remote resource or a socket, N9I uses a CR+LF as line ending.
When reading a file into a string, N9I will read the entire file. To read only one line, treat the file as an array and call pop() on it.
The at operator can also detect, based on the URI, what sort of resource the URI points to. For example, if the filename ends in ".zip", N9I assumes the at operator is being invoked on a zip file and provides zip methods on the resulting object; if given an XML or JSON file, it will allow you to create an Object out of it simply by casting the result to an Object.
The at operator can also be invoked on remote hostnames in the form host:port.
The IO.Authorize function is used to grant the program permission to access certain resources, such as FTP or HTTP locations. In some protocols, these may be placed directly in the URI passed to the at operator (for obvious reasons, this is probably not a good idea).
N9I keywords
- etc: in a parameter list, specifies that the function takes a variable number of arguments. etc is used together with a variable name, so that the arguments collected by etc will be stored as an array.
- public void printf(String str, etc args) { ...
- I: this variable represents the application itself. It is like a global version of this, which references the current object context.
- asynchronous: marks a function or object as being asynchronous; i.e. when it is invoked, it runs in its own thread. N9I doesn't have a "thread class", any function or object may act as a thread.
- public asynchronous void foo() { ...
- asynchronous MyThread thread = new MyThread()...
- public, private, protected: visibility modifiers
- void: indicates that a function does not return a value
- int, char, long, boolean, string: primitive data types
- with: given a block of code, temporarily places that object's members in the current scope
- const: declares a name as constant; i.e. it cannot be made to point to a new value
- class: denotes a class. Objects are created by creating instances of a class
- extends: denotes that a class "extends" (inherits members from) another class
- class Bar extends Foo { ...
- interface: denotes an interface. A class that "implements" an interface must conform to it.
- implements: denotes that a class "implements" an interface
- class Qux extends Bar implements Baz { ...
- I: refers to a superglobal object that contains information about the currently running application.
- this: in a class, refers to methods of that object
- this.doSomething(); // calls doSomething() in the current scope
Project files
Typically, languages define only the grammar of program source code. They do not, on the other hand, dictate the grammar of files used to configure compilation options. This results in code being difficult to move between development environments. N9I will define a "project" file type that is part of the official N9I specification. This project file will contain project-related settings. The N9I compiler will be given this file as input and is expected to be smart enough to produce a corresponding binary as output. This should result in an N9I codebase being not only operating system independent but also IDE-independent.
This file will dictate options such as:
- target platforms.
- dependencies.
- coding conventions for the project. An IDE or lint program can use this to verify the source code. For example, an IDE can mandate specific conventions (camelCase for variable names, opening braces on their own line, etc) based on configuration options. The N9I compiler can even be instructed to refuse to accept code that does not meet the convention.
- whether taintedness is enabled. Taint is an option that prevents data gathered from outside the program to be used in certain situations, such as system calls or the at operator.
- whether the compiler should raise a warning or an error if platform-specific API references are found.
- where the main function is located.
Packages, modules, classes, functions...
- Package: a folder that contains modules.
- Module: an N9I source file that contains classes, functions, and variables.
- Function: an named, executable block of code that resides in a module but outside a class
- Class: a construct that is used to create objects - contains member variables and methods
- Method: a function that resides in a class
- Variable: a name that has a value.
How this would look in a folder
- project Project is a file, represented by Project.N9P and located in the root folder of the project.
- module MainModule is a file, represented by MainModule.N9I and located in the root folder of the project
- package MyPackage is a folder
- module MyPackage.MyModule is a file, represented by MyModule.N9I and located in the MyPackage folder
- function MyPackage.MyModule.MyFunction is a function defined in MyModule.N9I
- class MyPackage.MyModule.MyClass is a class defined in MyModule.N9I
- methodMyPackage.MyModule.MyClass.MyMethod is a method defined in MyClass
- module MyPackage.MyOtherModule is a file, MyOtherModule.N9I in the MyPackage folder
- package MyPackage.MySubPackage is a folder, located in the MyPackage folder
- module MyPackage.MySubPackage.MyThirdModule is MyThirdModule.N9I located in MyPackage/MySubPackage
Visibility modifiers
These can be applied to anything defined in a module
- public: means that the variable, class, method, or function (hereafter referred to as "thing") can be accessed from any other location
- protected: means that the thing can be accessed only from within the package
- private: means that the thing can only be accessed from its immediate context; i.e. for things defined outside a class or function, this is the module it is defined in; for things defined in a class, this refers to the class.
- default: accessable only to the containing module
N9I API module structure
- function _: the gettext function, used for internationalization
- module Capability: Contains variables describing capabilities of the operating system under which the program is being run. Instead of testing for an operating system and using OS-specific code, N9I encourages you to test for the existence of a "capability" (i.e. threading, network support) and use the N9I cross-platform API to use the capability.
- module IO: Contains classes and functions related to input/output. I/O is typically done with the at operator, which is used to act on resources outside the program. Instead of providing resource-specific APIs for each type of resource, the at operator can be invoked on any kind of identifier and returns a link to a resource. Depending on the resource type, some extra methods may be available on the link - but all kinds of resources support read and write. Use the is operator to test for a resource's type.
- function IO.Type: Given a resource identifier string, returns the specific type of resource. The at operator does this automatically. If you have a ResourceAlias and want to test it for a certain type, use the is operator: if(@"http://eightzees.net/xmlfile.xml" is XML) or if(IO.Type("http://eightzees.net/xmlfile.xml") == "XML")
- function IO.Authorize: Authorizes the program to access resources through a given protocol or at a given location.
- class IO.ResourceAlias: Base class for anything returned with the at operator. A ResourceAlias is a special kind of variable in that when it is assigned to a variable, N9I binds that variable to the resource. Further reads and writes of the variable will be redirected to the resource in question unless ResourceAlias.unset() is called, which destroys the ResourceAlias and frees the variable name. A ResourceAlias may be used anywhere the at operator can, and vice versa. The basic operations permitted on a ResourceAlias object are read (or get), write (or set), and append. A ResourceAlias may be used anywhere a String, Object (including array), or number can; N9I will attempt to read the resource into a string, object, or number.
- class IO.File: Base class for all I/O file classes. IO.File supports the following methods: rename(), delete(), size(). For some file types, N9I will provide an even more specialized API.
- class IO.File.XML: XML file class. Supports Warrah - 2/8/20103C DOM methods. Note that, if an XML file resource is directly assigned to another object type, N9I may attempt to convert the XML into an object of that type. Invoking the at operator directly on a string containing well-formatted XML code will convert the code into a DOM tree and return one of these objects.
- class IO.File.ZIP: ZIP file class. Supports array or dictionary-style access.
- class IO.File.JSON: JSON file class. The notes regarding IO.File.XML apply here too.
- class IO.File.HTML: HTML file class. See IO.File.XML.
- class IO.File.PNG: PNG file class. Supports image manipulation in a number of ways.
- class IO.File.INI: INI file class.
- class IO.File.YAML: YAML file class.
- class IO.Socket: Base class for all I/O socket classes. Specialized classes may be returned depending on port number.
- class IO.Socket.HTTP: Returned for ports 80 and 8080.
- class IO.Socket.FTP: Returned for port 21.
- class IO.Socket.IRC: Returned for port 6667.
- class IO.Socket.SMTP
- class IO.SOAP: Created when a remote SOAP web service endpoint is referenced. Methods invoked on this object are passed to the SOAP web service.
- class IO.Process: a process created through invoking the at operator on a binary executable or script. Writing to an IO.Process sends information to standard input, while reading from it reads from standard output. When this resource link is unset, the underlying process is killed.
- module wx: bindings for the cross-platform wxWidgets GUI toolkit
- module Lang: language features. Note that many N9I structures - functions, classes, and the like - are defined as interfaces, allowing an N9I coder to create classes that implement such functionality.
- function Lang.ResolvePath: resolves a given pathname. N9I mandates Unix-style paths to ensure cross-platform compatibility. Local pathnames given to the at operator are passed through this function. Under Windows, it should sort of emulate cygwin's cygpath utility. note: this is being reconsidered
- interface Lang.String: represents a character string.
- interface Lang.Integer: represents a signed 32-bit whole number.
- interface Lang.Double: represents a signed 64-bit floating point number.
- interface Lang.Function: represents a function or a method. Objects implementing this interface must define invoke(), which takes a variable number of arguments. All functions and methods are instances of Lang.Function.
- interface Lang.Object: represents an object. Objects implementing this interface must define get() and set(). Note that it is only needed to implement Lang.Object if you need "magic" functionality; classes that do not cannot use this functionality.
- interface Lang.Class: represents a class. Objects implementing this interface must define construct(), add(), and remove() methods that allow one to create a new instance of the class, add methods and members, and remove them. Note that, as with Lang.Object, a class must actually implement Lang.Class to allow instances of them to act as classes.
- interface Lang.Array: represents an array. Objects implementing this interface must define get(), set(), and push() (which are accessed with the array access operator). In N9I, any object may act as an array.
- interface Lang.Dictionary: represents a dictionary, or map. Objects implementing this interface must define get() and set(), which are accesed with the dictionary access operator. In N9I, any object may act as a dictionary.
- interface Lang.TiedName: represents a variable that has been tied to an object, akin to Perl's tie operator. IO.ResourceAlias implements this interface.
- module Math: Contains functions relating to mathematics
- module Configuration: contains functions that store application configuration settings. This is a cross-platform API to platform-specific configuration storage mechanisms.
- class Configuration.Configuration: creates or retrieves an object containing configuration settings. Configuration is stored in a hierarchical manner, with a key containing either a value or a set of more keys. To create a configuration object, pass a string representing a configuration name to the constructor. If this configuration already exists, it will be given to you; otherwise, it will be created. Note that there is nothing stopping another N9I application (or an application written in another language which can use this data store) to read settings that belong to a different application.
- package System: contains system-specific APIs. N9I includes these as a courtesy, even though it violates N9I's principles. By default, the compiler will flag usage of anything locatedin this package as a warning. N9I will only provide an interface to anything that cannot be replaced with a cross-platform alternative. Programs that depend on the System package may not cross-compile correctly.
- module System.Win32: provides a bridge to the Win32 API.
- class System.Win32.COM: provides a mechanism to create a Windows COM object.
- module System.Cocoa: provides a bridge to the Mac OS X Cocoa framework.