NewtonScript:      NTK   ·   Einstein Toolkit   ·   «Tutorial»


Einstein Toolkit

Starting with version 2020.5.0, Einstein includes Toolkit. Toolkit is an integrated developer environment (IDE) for writing and testing NewtonScript apps.

But let's start at the very beginning: NewtonScript is an object-oriented programming language loosely based on Smalltalk, LISP, and Self. It's syntax borrows from Pascal.

Einstein Toolkit can compile and run a few lines of code, and it can also generate a Package file, containing an entire application, that will install and run on MessagePad devices. In fact, a package is just a large NewtonScript data structure that is created by running NewtonScript.


Printing to the Terminal

Open Einstein, open Toolkit, clear the sample source code and enter only one line of code:

p("Hello");
Press Apple-B or Ctrl-B to build your code. The terminal window below the source code editor should show the text:

Compiling inline...
"Hello"
Info: package compiled.
Toolkit notifies you that it compiled your code. p is the command to print data to the terminal, and "Hello" is the text we want to print. If the script compiled, Toolkit will run the commands and display the result.

If we made a typo, we will get a line similar to:

f("Hello");
----
exception evt.ex.fr;type.ref.frame: -48808: kNErrUndefinedGlobalFunction
or

!p("Hello");
----
E:lines 2: syntax error:
!
^


Data Types and Variables

"Hello" in our previous example is of the type string. We can store a string and all other types in a variable for later use, for example:

a := "Hello";
p(a);
----
"Hello"
We can also store numbers, either as integer or as real numbers. The syntax for that is just as easy:

a := 25;
b := 4.5;
p(a*b);
----
112.500000


Where is my Math?

Maybe you get all excited now and want to try some math, but a:=sin(3.14); complains that sin() is an undefined global function, How can that be?

Well, we have two completely independent NewtonScript compiler/interpreter in Einstein. There is the interpreter in the Newton ROM that is used to run your packages, and there is a second interpreter called newt/0 inside Toolkit. Newt/0 is used to compile your script into bytecode and wrap it in a package. It is somewhat limited to that job.

Once the package is installed and runs on the Newton, you can make use of all teh graphics calls, the user interface implementations, and of course all the math calls, including sin() , that come with NewtonOS.


Complex Data Types

Storing a number or some text is nice, but what if we want to store multiple numbers. For that, we have arrays. Arrays are just a list of other data types in the specidied order. You can access data inside an array through its 0-based index:

x := [ 1, -5, 7, "Peter" ];
p( x );
p( x[2] );
----
[
	1, 
	-5, 
	7, 
	"Peter"
]
7
Arrays store data in an ordered fashion. We can also store data by naming them. This type is called a Frame, and the datasets inside are called Slots:

author := {
  name: "Matthias",
  zip: 40474,
  city: "Dusseldorf"
};
p( author.name );
----
"Matthias"
And to make this really interesting, complex, and versatile, we can put arrays in frames and frames in arrays, and any other combination:

author := {
  name: "Matthias",
  speaks: [
    "German",
    "English",
    "NewtonScript"
  ]
};
p( author.speaks );
----
[
	"German", 
	"English", 
	"NewtonScript"
]


References

Let's say I want to put my address into my address book and and send it to Walter. There is no reason to type or store the address twice. We can simply reference it:

matt := { name: "Matthias", zip: 40474, city: "Dusseldorf" };
pete := { name: "Peter", zip: 90291, city: "Venice" };
addressbook := [ matt, pete ];
sendToWalter := [ matt ];
p(addressbook[0].name);
----
"Matthias"
Let's say, I decide to move to Cologne. We can just add the following code to the previous lines:

matt.city := "Cologne";
matt.zip := 50667;
p(sendToWalter);
----
[
	{
		name: "Matthias", 
		zip: 50667, 
		city: "Cologne"
	}
]


Other Types

There are a few other types that I need to mention here that you will come across:

a := TRUE;  /* a decission can be TRUE or NIL */
b := NIL;   /* NIL is just 'nothing', and also means 'false' */
c := $a;    /* store the letter "a" */
d := 'name; /* remember that slots have names to reference them? */
            /* They are called symbols and are starting with a tick */
            /* ' if they could be confused with a varaible name */
e := @16;   /* these are called magic numbers. Think of them as a */
            /* global shortcut. @16 for example is the sound  */
            /* when MessagePads start up */
/* There is also a "binary" type that can hold graphics, */
/* sounds, and other data */
Oh, yeah, and /*...*/ and // are comments for humans and are ignore by NewtonScript.


Program Flow

Storing data is fine, but we want to make decissions based on the data we have.

a := [ 1, 2, 5, 7];
if a[2]=5 then
  p("I expected that")
else
  p("Who modified my test data?");
Note that there is no semicolon after the first print command. NewtonScript sees the entire if clause as a single command. If we need to bundle multiple statements into an if-clause, we can use begin and end to group statements.

a := [ 1, 2, 5, 7];
if a[2]=5 then begin
  p("I expected that");
  p("Nobody expects the number 5 at index 2!")
end;


Looping the loop

We can count

for i:=1 to 5 by 2 do
  p(i);
----
1
3
5
or loop with loop...break or while...do, or we can loop through all the memebers of an array or frame.

a := [ 5, 7, "Claus" ];
foreach i in a do
  p(i+2);
----
7
9
Exception evt.ex.fr;type.ref.frame: -48404: kNErrNotANumber
Huh! And we learned that we can't add the number two to Claus. Sorry, Claus.


Keeping it all in a Function

Writing those lines of code is neat, but what is we want to reuse a group of lines, just as we earlier reused an address by referencing it. Well, we can. We group lines of code in a function. You have been using one function from the very start named p(). Let's write a function that prints two values instead of just one:

DefGlobalFn( 'printTwo, func(x, y) begin p(x); p(y); end );
printTwo( 3.1415, "Verena" );
----
3.141500
"Verena"
Since functions are just another type of data, they can be a slot in a frame or array as well.

matt := { 
    name: "Matthias", 
    zip: 40474,  
    city: "Dusseldorf", 
    print: func() begin
            p(name); p(zip); p(city);
        end
};
matt:print();
----
"Matthias"
40474
"Dusseldorf"
Note the differenc in notation in accessing print by using a colon before the symbol and () after the symbol, vs. accessing name by just writing matt.name. In NewtonScript lingo, we are sending the object matt the message print.


Inheritance (Get ready for a new Concept)

Let's say that almost all my friends live in Dusseldorf, Germany. To make an address book, it would be a nuisance to repeat that information every single time. Can we create an object that behaves like a template for other objects? A new object inherits aspects of another existing object.

friends := { 
    zip: 40474,  
    city: "Dusseldorf", 
};
paul := {
    name: "Paul",
    _proto: friends
};
peter := {
    name: "Peter",
    _proto: friends
};
p(paul.city);
----
"Dusseldorf"
So the object paul inherited all aspects of friends simply by definig a _proto slot. We can also create a chain of inheritance by putting a _proto slot into friends that references yet another object, maybe named people.

But wait a minute, Paul doesn't live in Germany, he's in France. So writing paul.city := "Paris"; overrides the slot friends.city for Paul only. p(peter.city); will still print Dusseldorf.


My first Newton Application

What's so cool about inheritance, you ask? Well, NewtonOS provides a great number of prototypes that implement user interface elements, databases, communication endpoints, and much more. Understanding the basics of inheritance, we now know enough to write our very first very own Newton program.

Pressing Apple-B or Ctrl-B builds the package and gives a chance to check for errors. Press Apple-R or Ctrl-R to build, install, and run the package on your emulated Newton.

newt.theForm := {
    _proto: protoFloatNGo
};
----
Compiling inline...
Info: package compiled.
Installing...
Run...
Huh, I hear you say, that is already alll? Well, yes, Einstein Toolkit creates a standardized package description in th eobject named newt for you, and the NewtonOS prototype protoFloatNGo already implements everything to display a movable window on the Newton screen.


Ok, ok, I want a Button

Yes, we can add a button to the window.

myButton := {
    _proto: protoTextButton,
    text: "Thanks!",
    viewBounds: {
        left: 50, right: 140,
        top: 20, bottom: 40
    }
};
newt.theForm := {
    _proto: protoFloatNGo,
    stepChildren: [
        myButton
    ]
};
There are a few new aspects here. First of all, as in the example above, we create the user interface by assigning it to newt.theForm. In NewtonOS lingo, an app has a hierarchy of views and controls, creating a form. newt is a predefined object that defines you app package in newt.app and can be modified or overwritten. You may, for example, change the copyright message in you package by writing newt.app.copyright := "Copyright (c)2020 me, and me alone!.

We also created a second variable myBotton and refrence it in an array named stepChildren. Why is it an array, you ask? Well, we can add as many bottons in this array as we like. We can even add a view that in turn contains more views and controls.

Why is it called stepChildren, and not children? Well, NewtonOS calls these views templates. When the app is openend by the user, NewtonOS creates a clone of our user interface. All actions will be done with the clone, so the next time the app is launched, the OS can create a fresh clone. Cloning step children makes the members of the children array.


I want Action

So far, we have created a neat little user interface, but it doesn't really do anything yet. When the user taps the button that we just defined, NewtonOS sends a message to myButton named ButtonClickScript. So if we implement a function named ButtonClickScript in myButton , it will be called whenever the user taps the button.

myButton := {
    _proto: protoTextButton,
    text: "Thanks!",
    viewBounds: {
        left: 50, right: 140,
        top: 20, bottom: 40
    },
    ButtonClickScript: func() 
    begin
        ModalConfirm(
            "This is Einstein Toolkit\r\r" & 
            "Thank you for trying out NewtonScript",
            [ "OK" ]
        );
    end
};
newt.theForm := {
    _proto: protoFloatNGo,
    stepChildren: [
        myButton
    ]
};
The global function ModalConfirm takes two arguments. The first argument is a string (here, we concatenate two strings using the & operator), and the second argument is an array of strings, each one genrating a button. One button, named "OK", is enough in this case.


What is newt?

Use p(newt.app); to look at the object that defines details of the package. Individual parts of newt.app can be overridden to adapt the package tou your needs.

newt.pkgPath can be set to a string describing the file path and name for creating the package file in a custom location.

Users can also define the following variables to override package settings:

kAppName := "Hello:WONKO";
kAppSymbol := '|Hello:WONKO|;
kAppLabel := "Hello";


What's next?

So this was a quick introduction into NewtonScript for MessagePad.

The Apple NewtonScript Reference will give you more details on NewtonScript and all the functions required to create and modify Newton data structures. The Programmer's Guide explains how to write meaningful apps, and the Programmer's Reference lists all prototypes and slots you can implement or override.

The User Interface Guidelines "describes how to create software products that optimize the interaction between people and devices that use Newton 2.0 software".