Simple Programming Software

 


Programming Made Simple

This article introduces a new high-level computer language called Ubercode. It describes the reasons for another language, it shows the innovative features, it shows a real working program, and it discusses the problems involved in language design. Ubercode was launched in June 2005, and exists as a real product and runs under all versions of Microsoft Windows from 1995 to the present.

1. Reason for a new language

The key reason is simplicity. Beginner and intermediate level programmers should be able to write simple programs with the same ease that was available by using QuickBasic and earlier versions of Visual Basic. Many developers lack the time or inclination to master complex environments such as Visual Studio or the ".NET" environment.

It is unfortunate that ease of use has recently been lost by the move towards managed environments such as Java, C# and VB.NET. There is clear evidence for this in the reluctance of Visual Basic programmers to move to VB.NET [see http://clasicvb.org/petition/] - these programmers feel their only choice is to move to the more complex environment of C#.

Therefore Ubercode's purpose is "ease of use". Paradoxically "ease of use" is not that easy to define since it covers more than matters of syntax, or questions such as where the curly brackets go. As a subjective definition, I consider any language claiming "ease of use" should meet the following requirements:

- Single point installation. The product should install from a single disk (CD or DVD), or from a single URL. Installation should not require extra libraries or platforms that must be downloaded and installed separately, and should not require certain service packs or Internet Explorer to be pre-installed. Installation should ask the minimum number of questions, should avoid reboots, and the days of prompting users to choose whether to install some or all of the files in a 50MB package are long gone.

- Type and run. Early developer environments had the feature where you could type in a simple program, and press the Run key (usually F5) to save, compile and run the program. Even though it seems trivial, this feature is essential to beginner programmers. It means they can get started before having to understand the structure of their chosen programming language "in the large", and before having to understand the build process. But in most cases this feature is now lost. It is now necessary to create projects, workspaces or solutions before being able to compile and run a simple program. Ease of use requires that when the Developer environment starts, it should be possible to create a blank window, type in a program of a few lines, and click Run to make the program work.

- Documentation. Full online help with examples should be available. If the Help key is pressed on any keyword, library function, or in any dialog, a help screen should instantly (within one second) be shown. The help screen should contain more than one sentence, and in the case of language elements, it should include an example program that actually works. It is my experience that no computer language (apart from Ubercode) meets this requirement.

- Easy to distribute programs. Programs are generally written in order to run them on other computers, so ease of use requires it be easy to group the files required by a working program. The developer environment should include features to copy the required files to a disk, or at least to identify the required files and their locations. The distributed program should also be able to work on different types of hardware and with different versions of Windows. In practice, this means the language runtime must work on all versions of Windows (or all versions of Windows from a given date) regardless of subsequent service packs or Internet Explorer versions.

- Native look and feel and portability. The developer environment and the programs it compiles should have the same look and feel as the version of Windows on which it runs. To do otherwise is to have a program that looks dated, or looks strange to its users. It is worth noting that when Microsoft want to make a programming style obsolete, they do so by making programs produced with that version look dated under newer version of Windows. For example Windows 3.1 applications had a dated look under Windows 95, and more recently Visual Basic v6 applications do not use the new style appearance under Windows XP. Ease of use requires that applications have the correct look and feel for the version of Windows under which they run.

- Everything is "in the box". To support ease of use, when you buy a programming package, you should have everything you need to develop and distribute programs. Development should not be held up because of missing libraries, incomplete documentation, dependencies on external software, or versions of Windows or service packs or versions of Internet Explorer. The product should include introductory manuals, reference manuals, installation keys and support contacts.

- Language design. Although this is important, I have put it last since it is the only part of "ease of use" which is determined by language syntax. Modern languages should include features such as fully automatic memory handling, simple string types, higher level collection types such as lists and tables, and automatic error handling. There is some evidence that self-bracketing keywords (if ... else ... end if) are clearer than begin ... end blocks in Delphi or curly bracketed blocks in C#. Also tools such as compilers and linkers should be automated as far as possible. Beginner programmers should never need to know about make files, linker options, which libraries to include, and it should be impossible for the compiler or linker to use mis-matched library files or to encounter unresolved references.

This list has shown the main requirements for "ease of use". It is interesting that earlier languages such as QuickBasic and Visual Basic v3 met most of these requirements, whereas modern managed languages are failing particularly in terms of complexity, and in terms of ease of deployment under other versions of Windows. Ubercode is a modern language designed to meet all the 'ease of use' requirements. Ubercode is a deliberate move in the direction of accessible languages such as Basic and Pascal. This makes Ubercode useful for beginner programmers, and gives them flexibility to write more useful programs as their skills grow.

2. Parallels with existing languages

I now want to discuss features Ubercode has in common with other mainstream computer languages. In this section I discuss languages and their Developer environments together, since the two are usually supplied in the same package. Instead of presenting an exhaustive "laundry list" of features, this section takes a high level view of features that should be in any modern language. This forms the basis for the discussion of new features in section 3.

- Applications can be built from multiple files. Languages should support blocks of code stored in separate files, and each file usually contains a single class (C#, Ubercode) or a unit (Delphi). Files and classes are the foundation of code re-use, and make it possible to work on larger real-world projects. It is interesting that lack of support for multiple files was one reason why Pascal was not widely adopted for commercial development.

- Languages should include a tool chain. Modern languages include their compiler, linker, and interpreter if required; build utilities and their run time library. The developer environment should include features for managing the tool chain, and should include extra elements such as dialog (form) editors and debuggers. Again all modern languages do this.

- Compiled code offers reasonable performance. Some languages (Delphi, C++, and Ubercode) can be compiled directly to object code, whereas others (C#, Java, Visual Basic) are compiled to an intermediate format which is then interpreted. Although the interpreted format is slower, the difference is not significant for most applications. However an interpreted language should make it possible to call code in external DLLs, or should offer extra library routines (typically for array handling, mathematics and graphics), to cover situations where performance is important.

- Common data types and functions. Programming tasks use string and numeric types and many common functions that operate on these types. Although there is not space to give a full list here, languages as different as Java and Basic and Ubercode provide functions with similar purposes. For example functions to search through strings, to extract parts of strings, to convert strings to upper or lower case, and common mathematical functions are available in all modern languages.

- Arrays and structured types. All languages should support arrays (ideally resizable), structures (records) and list or collection types. Languages such as Visual Basic and Ubercode allow types to be nested, for example an array can exist where each element is a list of structures. In all cases the language should automatically support the memory requirements of these types, by allocating and destroying (or garbage collecting) memory at run time. Some languages handle the creation of nested types using pointers and multiple references; others do so using value semantics (earlier versions of Visual Basic). Although space does not permit a detailed argument, value semantics are better since they avoid aliasing.

- Graphical user interface. Most programs written for Windows require a user interface, so languages must allow the creation of menus and dialog boxes with user controls. Inside a program the UI is handled using properties, methods and events which trigger the running of code. Languages differ somewhat in terms of how they define the interface (i.e. what controls are in a dialog). C# and Java use statements at run time, Delphi and Visual Basic use proprietary FRM or DFM files, and C++ and Ubercode use RC files (standard Windows resource compiler files).

- File handling. Programs need to work with files and folders, so languages should include functions to create, destroy, read and write from folders and different types of file. Functions that can read and write common types of file (such as text files, binary files, CSV files, database files, XML files and bitmap files) are very useful.

- Printing support. Modern languages should make it easy to copy bitmaps and text in different colors and fonts to any attached printer. In Visual Basic and Ubercode the same graphics functions that draw images and text can be used for the printer. Strangely enough this is an area where some modern languages fall short - it is almost as if the developers forgot to add printing support, making it necessary to print using the Windows API.

This concludes the common features in modern languages that are improvements on earlier 3GLs such as C and Pascal. For readers that are interested, I have compiled a more detailed list at compare/index.html.

3. Key new features

I will now look at features that are new to Ubercode, and at features that are implemented in an improved way.

- Program verification. This feature allows functions to validate their inputs when they are called, and to check their outputs when they exit. This is an important technique for improving software reliability and for making the purpose of functions much clearer [see International Developer April 2005, "Who's afraid of the Big Bad Wolf", p46]. Program verification is also known as Design by Contract, and the only languages currently supporting it are Eiffel and Ubercode. Program verification is implemented using preconditions and postconditions. These are Boolean expressions using the function's parameters which must test True before the function is called, and which must test True using the function's return values. Here is an example of a postcondition:

  function RemoveSpaces(in str1:string[*] out result:string[*])
  postcond Instr(" ", result) = 0
  ...
  end function

The RemoveSpaces function is intended to return a copy of its input string with all spaces removed. The postcondition tests the returned string has no spaces, and serves two purposes: firstly it makes the behavior of RemoveSpaces clear to the casual reader, and secondly it acts as a runtime check on the internal logic of RemoveSpaces (the internal code is not shown). The Ubercode developer environment has an options dialog to specify the degree of precondition and postcondition checking which takes place at run time:

Compiler options

The checks are specified in the top left corner of the dialog. In general, code under development should check preconditions and postconditions, whereas release quality code should check preconditions only.

- Program Security. Ubercode is a secure language, resistant to program crashes and exploits such as buffer overflows and type misuse. Buffer overflows and type abuse are prevented because the run time system knows the size, type and address of all data so it can validate all reads and writes to ensure they are within the buffer size. Ubercode does not store variable sized data on the stack - this prevents exploits that corrupt function return addresses by overwriting the local variable stack allocation. Ubercode also has two levels of error handling. A class level error handler gets first look at all errors within the class, and then a system wide error handler prints a default message. Error handlers can be customized, and for most errors programmers can choose whether the program terminates or continues running. This means that error boxes such as the following:

General Protection Faults

are a thing of the past for Ubercode programmers.

- High level collection types. Ubercode supports fixed size and resizable arrays, lists and tables. Arrays may be multi-dimensional, and all these types may have components of any type, including other structured types. For example lists of arrays or arrays of tables, or any other combination are possible. Memory is managed automatically without garbage collection. Arrays, lists and tables may all be iterated using a "for each" loop. Lists are similar to collections in other languages, and Tables are similar to dictionaries or hash tables.

- Improved memory techniques. All variable-size Ubercode data types are dynamically allocated, reallocated and freed when required. For example the following code:

  MyString <- MyString + "a"

appends a single character to a string, and (other than the initial string declaration) no extra lines of code are required to manage the memory used by the string. Compare this to C# and Java, where the above lines of code create a new string object containing the new string text, leaving the previous string text to be freed when garbage collection takes place. Ubercode avoids garbage collection by handling all memory automatically. Avoiding garbage collection is important, since it makes an application's memory requirements more predictable, and it avoids random pauses which can negatively impact on near real-time applications.

- Simple class structure. To help beginner programmers, the class structure of Ubercode has been made deliberately simple by storing each class in a separate file. Classes begin and end with the "class" and "end class" keywords, and classes are inherited by the "uses" keyword. Functions and values in a class that are intended to be used outside the class use the "public" keyword. Although this is a simple model, it is well suited for beginner programmers, and it makes possible a "drag and drop" style of programming where new classes can be added from a pick list. The Developer Environment also includes tools to view all identifiers and objects in all classes, and to quickly view all function headers within a class.

- Tools for running under different versions of Windows. After writing and testing an Ubercode program, you can use the Developer Environment to help distribute the program. There are menu commands to show the names and paths of all files in the program (including library files), and commands to copy all these files into a zip file or into a self-installing exe file. This is the dialog used to create the zip file:

Copy to EXE file

After a program is packaged, it is likely to be installed on a computer that is different from the one it was developed on. Therefore the Ubercode run time libraries and exe files must be independent of the ".NET" framework, MFC libraries, or Windows DLLs. To achieve this Ubercode uses the Windows API for all functions and does not rely on particular elements of Windows that may not exist. This has the further advantages of making Ubercode exe files more reliable, and of ensuring Ubercode programs have the correct look and feel for the current version of Windows. For example an Ubercode program that looks like this under Windows 2000:

Windows 2000 appearance

looks like this under Windows XP:

Windows XP appearance

Note the same program has the correct Windows look and feel in both situations. This is fully automatic, and the Ubercode programmer does not have to use any special code to ensure this takes place. This option is not available in other languages - managed languages such as C# and VB.NET do not run under versions prior to Windows 2000, and languages such as Visual Basic v6 do not have the XP style.

4. Introductory program

When introducing beginner's languages such as Basic, it is traditional to show the source code for a simple game such as the Moon Lander program. It is now over 30 years since man first landed on the moon, and there is not enough space here for a 100 line program, so here is the Mars exploration program instead. This program move a sprite (mars explorer) round the screen area:

Sprite program

Here is the source code:

  // Sprite.cls
  Ubercode 1 Class Sprite

  private const
    SPRITEX : integer <- 32
    SPRITEY : integer <- 32
    SPRITEPIC : string[*] <- "sprite.ico"
  
  public callback function main(in  EventId:integer
                                    ControlObj:control
                                    Key:integer
                                out Cancel:boolean)
  var
    x:integer(0:MAXINT) // Top left corner of sprite
    y:integer(0:MAXINT) // Top left corner of sprite
  code
    select EventId
    case LOAD_EVENT =>
         call Startgraph(me)
         call SetBackcolour(me, DARK_RED)
         call Drawshape(me, 0, 0, 0, 0, GetPagewidth(me), GetPageheight(me))
         call Drawpicture(me, SPRITEPIC)
         call SetCurrentX(me, 0)
         call SetCurrentY(me, 0)
    case KEYPRESS_EVENT =>
         x <- GetCurrentX(me)
         y <- GetCurrentY(me)
         call Drawshape(me, 0, 0, x, y, x+SPRITEX, y+SPRITEY)
         select Key
         case KUP =>
              if y > 0 then
                y <- y - SPRITEY
              else
                call Sound("uhoh.wav")
              end if
         case KDOWN =>
              if y < GetPageheight(me) - SPRITEY then
                y <- y + SPRITEY
              else
                call Sound("uhoh.wav")
              end if  
         case KLEFT =>
              if x > 0 then
                x <- x - SPRITEX
              else
                call Sound("uhoh.wav")
              end if
         case KRIGHT =>
              if x < GetPagewidth(me) - SPRITEX then
                x <- x + SPRITEX
              else
                call Sound("uhoh.wav")
              end if  
         end select
         call SetCurrentX(me, x)
         call SetCurrentY(me, y)
         call Drawpicture(me, SPRITEPIC, x, y, x+32-1, y+32-1, 
                           DRAW_TOPLEFT+DRAW_PIXELS+DRAW_SAVE_CURRENTXY, 0)
    end select
  end function

  end class

This works as follows:

The constants at the top of the program define the size of the sprite and its filename. In this case the sprite is a Windows icon file. The main function handles all the events in the program, because it is declared "callback". Callback functions are those passed events by the operating system.

The code under the Load_event sets the screen into graphics mode and draws the sprite in its initial position (top left corner). The Keypress_event detects the up, down, left and right keys and moves the sprite by one unit (32 pixels) in the specified direction. Movement occurs by erasing the sprite at its current position, calculating the new position, and redrawing the sprite there. If the keys would move the sprite off the screen, a warning sound is played and the sprite is redrawn in its previous position. The program can be ended by clicking the close box.

To run the program, go to downloads, download and install the Ubercode Trial Pack (no personal details are required and the download is free), start the Developer Environment, use the File - Open Examples menu command and choose the Sprite example. If you want to run the actual Mars Lander program, choose the Lander example. If you are a traditionalist who wants to run the Moon Lander program, you can change the constants at the top of the program and re-run it.

5. Difficulties encountered

This section discusses problems found when designing Ubercode. To quote from Tremblay and Sorenson, "Nothing seems so easy as the design of a new language, yet nothing is so complex". This section may therefore be of interest to others embarking on the perilous course of language design.

- What to leave out. One of the biggest problems is deciding what should be included and what should be left out. When a new feature is required, the immediate temptation is to add the feature in the most immediate and straight forward way. This is invariably a mistake - a well designed computer language has few redundant parts, so the addition of a new feature can have far reaching effects on the language, compiler and run time library. In most cases the feature is not really new, but is a more generalized application of an existing part of the language. In such cases the new feature is not added at all, but the existing part is improved to embrace the new functionality. There is a real danger of any new language degrading into an uncoordinated mess, and the danger is only avoided by keeping a clear vision of the language structure.

- Language syntax. All modern languages are defined with deterministic grammars. This means that when following the syntax rules to parse a valid program in the language, the current symbol in the program unambiguously specifies the parser's path through the syntax diagram. This is equivalent to a LALR grammar or LL(k) grammar for recursive descent parsers. Although this is the current state of compiler technology, this is restrictive for language designers as some work is necessary to convert a grammar into LALR or LL(k) form. It would help language designers if compilers could handle ambiguous grammars. But it is possible the savings are not that significant, since in Ubercode the design of the syntax and the language took much less time than the design and testing of the run time library.

- Graphical User Interface. The Ubercode UI is fairly straightforward as it is based on the Windows interface in its present (post 1995) form, although there is a large amount of internal detail. To simplify the design, the UI objects, properties, methods and events were put into a separate layer and kept separate from the core run time library. UI objects relate to graphical controls (checkboxes, pushbuttons etc) and to higher level objects such as dialog boxes and printers, properties relate to control attributes that can be read and written (get and set), methods relate to routines that alter the state of objects (Show, Hide, Load, Unload), and events relate to actions such as menu choices, button clicks, scrollbar moves and similar. Events are most easily implemented by callbacks which is similar to the Java, C# and C++ method of implementation. This division of the Ubercode run time into UI and non-UI parts makes it possible to port Ubercode programs by re-writing the core functionality separately from the program interface.

- Choice of name. Although not a technical decision, it was very difficult to choose a suitable name! Ubercode was originally called Lingo and was hosted at lingolangage.com. But Lingo had two potential conflicts: the first was an inactive UK based holder of the Lingo trademark, and the second was a Macromedia product using the name Lingo, even though Macromedia had not registered this name as a trademark. In general a name should be (1) memorable, (2) it should be able to be registered, (3) it should be available as a domain name, and (4) it should not currently be in use as the name of any kind of significant product. Lingo failed on tests 3 and 4, so the decision was made to change the name.

6. Future improvements

Ubercode is a new language, having just been released at v1.0 in mid 2005. One important characteristic of future releases is that they shall not break existing code. In the event this is unavoidable the new version will have importers that convert from v1 format into v2 format. To assist this all Ubercode classes have the following structure:

  Ubercode 1 Class Myclass

  end class

The "1" in the class heading defines the version, so if the same class is opened by a (yet unreleased) v2 Developer Environment, any required conversions will be automatic. Because Ubercode is based on a secure and well-tested foundation, most improvements will be to increase the number of platforms Ubercode runs on, rather than modifications to the language.

- Linux port. Linux includes Windows API compatibility libraries (the wine project) which are currently under development at v0.9 release - these are probably a year away from being commercial quality. In principle these libraries make it possible to port Windows applications to run natively under Linux. Furthermore since the latest versions of the Mac OS are based on Unix, and Apple are moving to use the Intel x86 platform, these libraries will also permit Windows applications to run on the latest Apple Macs. Therefore Ubercode has the medium term goal of using these libraries for a Linux port.

- DLL interface. At present the Ubercode interface to DLLs is via the run time library itself. Although DLLs are complex, there may be advantage to allowing programmers direct interface to DLLs from Ubercode classes. However this improvement is only of benefit to more advanced programmers who are comfortable calling DLLs and debugging those calls.

- Event handler simplification. At present Ubercode event handlers for a window are implemented as a select statement inside an event function. This system was chosen because each window then has a single event function of the same name. However it may be better to move to a more fine grained approach, where the event handlers for a dialog are also qualified with the name of the event (such as Form1_Load in Visual Basic). This may make programming easier for beginners. If this is done there may also be advantage to choosing common event names based on JavaScript and C#, instead of the older style Visual Basic names.

Conclusion

Beginner and intermediate programmers can use Ubercode to create applications that carry out real-world tasks in a reliable and portable manner. Ubercode has extensive support for existing data types and file types. The integer, string, currency and floating point types are compatible with Visual Basic and C/C++/Delphi. The CSV file, text files, binary files, dBase files and XML files are compatible with Ubercode data types. These files can be created from Ubercode programs, or can be read into programs. Ubercode is feature-complete without needless complexity, and Ubercode is powerful enough to write more useful programs as programmers develop in skill.

About the author

Picture of the author

I have been working as a software developer for about 20 years, and have programming expertise is in C, Delphi, Basic, VBA and of course Ubercode! Software development consists of much more than programming, so my skills also include software testing, documentation, help files and manuals, licensing, shrink wrap and packaging, marketing, and website development.