http://www.newton-inc.com/dev/techinfo/qa/qa.htm
myEncloser := {
importantSlot: 42,
GetImportantSlot := func()
return importantSlot,
nestedSlot := {
myInternalValue: 99,
getTheValue := func()
begin
local foo;
foo := :GetImportantSlot(); // WON'T WORK; can't find function
foo := myEncloser:GetImportantSlot(); // MAY WORK
importantSlot := 12; // WON'T WORK; will create new slot in nestedSlot
myEncloser.importantSlot := 12; // MAY WORK
end
}
};
myEncloser.nestedSlot:GetTheValue();
The proper way to accomplish this is to give the nested frame a _parent
or _proto
slot that references the enclosing frame. Nesting the frame is not strictly necessary in this case, only the _proto
or _parent
references are used.MyFrame:= {}; theSlotName := "Slot_1";
MyFrame.Slot_1
Intern
takes a string and returns a symbol. There is also a mechanism called path expressions (see the NewtonScript Reference), that allows you to specify an expression or variable to evaluate, in order to get the slot name. You can use these things to access the slots you want: MyFrame := {x: 4};
theXSlotString := "x" ;
MyFrame.(Intern(theXSlotString)) := 6
theSlotName := "Slot_1";
MyFrame.(Intern(theSlotName)) := 7;
// myFrame is now {x: 6, Slot_1: 7}
call func()
begin
local s,v;
local root := GetRoot();
local base := root.|YourApp:YourSIG|; // name of app
local prot := base._proto;
foreach s,v in base do
begin
if v and v <> root AND v <> base AND v <> prot then
begin
Write ("Slot:" && s & ", Value: ");
Print(v);
end;
end;
end with ()
The debugging function TrueSize
can also be a valuable tool to determine the heap used by your applications. See the NTK User Guide for more information about TrueSize
. thrower: func(x) begin
if x then
throw('|evt.ex.msg;my.exception|, "Some error occurred");
end;
returner: func(x) begin
if x then
return -1; // some random error code,
0; // nil, true, whatever.
end;
Code to throw and and handle an exception: local s;
for i := 1 to kIterations do
try
call thrower with (nil);
onexception |evt.ex.msg;my.exception| do
s := CurrentException().data.message;
local result;
local s;
for i := 1 to kIterations do
if (result := call returner with (nil)) < 0 then
s := ErrorMessageTable[-result];
local s;
try
for i := 1 to kIterations do
call thrower with (nil);
onexception |evt.ex.msg;my.exception| do
s := CurrentException().data.message;
TRUE
instead of NIL
so the "error" occurs every time was interesting. The return value loop takes about 60 ticks, mostly due to the time needed to look up the error message. The exception loop takes a whopping 850 ticks, mostly because of the overhead in the CurrentException
() call.breakOnThrows
global to stop your code and look at why there's a problem. With result codes you have a tougher time setting break points. With a good debugger it could be argued that you can set conditional break points on the "check the return value" code, but even when you do this you'll have lost the stack frame of the function that actually had the problem. With exceptions and breakOnThrows
, all the local context at the time the exception occurred is still available for you to look at, which is an immense aid.PrimClassOf
will return an object's primitive type.String
. A string object contains a 12-byte header plus the Unicode strings plus a null termination character. Note that Unicode characters are two-byte values. Here's an example: "Hello World!"
This string contains 12 characters, in other words it has 24 bytes. In addition we have a null termination character (24 + 2 bytes) and an object header (24 + 2 + 12 bytes), all in all the object is 38 bytes big. Note that we have not taken into account any possible savings if the string was compressed (using the NTK compression flags).kInkChar
is used to mark the position of an ink word. The ink data is stored after the null termination character. Ink size varies depending on stroke complexity. [12, $a, "Hello World!", "foo"]
We have a header (12 bytes) plus four bytes per element (12 + (4 * 4) bytes). The integer and character are immediates, so no additional space is used, but we have 2 string objects that we refer to, so the total is (12 + (4*4) + 38 + 20 bytes) 86 bytes. We have not taken into account savings concerning compression. Note that the string objects could be referred by other arrays and frames as well, so the 38 and 20 byte structures are stored only once per package. {Slot1: 42, Slot2: "hello"}
We have a header of 28 bytes, and in addition we have two slots, for a total of (28 + (2 * 8)) 48 bytes. This does not take into account the space used for each of the slot name symbols or for the string object. (The integer is an immediate, and so is stored in the array.) {Slot1: 56, Slot2: "world"}
We have a header of 12 bytes. In addition, we have two slots (2 * 4), and additional 16 bytes for the size of a map with no slots „ all in all, 36 bytes. We should also take into account the shared map, which is 16 bytes, plus the space for the two symbols.RelBounds
to create the viewBounds
frame, and it means there will be a single viewBounds
frame map in the part produced.if value.path = '|name.first| then ... // WRONG
A: There are several concerns. '|name.first|
is not a path expression, it is a symbol with an escaped period. A proper path expression is either 'name.first
or [pathExpr: 'name, 'first]
. The vertical bars escape everything between them to be a single NewtonScript symbol.value.path = 'name.first
will always fail, because path expressions are deep objects (essentially arrays) the equal comparison will compare references rather than contents. You will have to write your own code to deeply compare path expressions.'name = [pathExpr: 'name]
will always fail, as the objects are different.'name.first
:if ClassOf(value.path) = 'pathExpr and Length(value.path) = 2
and value.path[0] = 'name and value.path[1] = 'first then ...
...
local myFunc := func(...) ...;
local futureSoupEntries := Array(10, nil);
for i := 0 to 9 do
futureSoupEntries[i] := {
someSlots: ...,
aFunction: myFunc,
};
...
A: When a function is defined within another function, the lexically enclosing scope (locals and paramaters) and message context (self) are "closed over" into the function body. When NewtonScript searches for a variable to match a symbol in a function, it first searches the local scope, then any lexically enclosing scopes, then the message context (self), then the _proto and _parent chains from the message context, then finally the global variables.TotalClone
is made during the process of adding an entry to a soup, and this includes the function body, lexical scopes, and message context bound up within any functions in the frame. All this can take up a lot of space.DefConst('kMyFunc, func(...) ...)
) it will not have the lexically enclosing scope, and the message context at compile time is defined to be an empty frame, and so cloning such a function will take less space. You can use the constant kMyFunc
within the initializer for the frame, and each frame will still reference the same function body. (Additionally, the symbol kMyFunc
will not be included in the package, since it is only needed at compile time.)_proto
based scheme instead. Each soup entry will necessarily contain a complete copy of the function, but if you can guarantee that the function body will always be available within your application's package, it might be unnecessarily redundant to store a copy with each soup entry.TrueSize
to get the size of a soup entry I get results like 24K or even 40K for the size. That can't be right. What's going on?TrueSize
"knows" about the underlying implementation of soup entries. A soup entry is really a special object (a fault block) that contains information about how to get an entry and can contain a cached entry frame. In the information about how to get an entry, there is a reference to the soup, and various caches in a soup contain references to the cursors, the store, and other (large) NewtonScript objects. TrueSize
is reporting the space taken up by all of these objects. (Note: calling TrueSize
on a soup entry will force the entry to be faulted in, even if it was not previously taking up space in the NewtonScript heap.)TrueSize
is not very useful when trying to find out how much space the cached frame for an entry is using. A good way to find the space used for a cached entry frame is to call gc(); stats();
record the result, then call EntryUndoChanges(entry); gc(); stats()
. The difference between the two free space reports will be the space used by the cached frame for a given entry. EntryUndoChanges(entry)
will cause any cached frame to be removed and the entry to return to the unfaulted state. Gc() then collects the space previouly used by the cached entry frame.TrueSize
breakdown of the types of objects used, you can Clone
the entry and call TrueSize
on the copy. This works because the copy is not a fault block, and so it does not reference the soups/cursors/stores.Floor
and Ceiling
seem broken. For instance, Floor(12.2900 * 10000)
returns 122899, not 122900. What's going on? Floor
or Ceiling
. This happens because of the way floating point numbers are stored, and the limitation is common to many real number representations. In the same way that 1/3 cannot accurately be represented in a finite number of digits in base 10 (it is .3333333333...), likewise 1/10 cannot be exactly represented as a fractional part in base 2. Because number printers typically round to a small number of significant digits, you don't normally notice this. The NTK inspector, for example, displays only 5 significant figures in floating point numbers. However, if you display the number with enough precision, you'll see the representation error, where the real is actually slightly larger or smaller than the intended value. FormattedNumberStr(0.1, "%.18f") -> "0.100000000000000010"
FormattedNumberStr(0.3, "%.18f") -> "0.299999999999999990"
Floor
and Ceiling
are strict, and do not attempt to take this error into account. In the example, 12.29
is actually 12.2899999999999990
, which multiplied by 10000
is 122,899.999999999990
. The largest integer less than this number (Floor
) is correctly 122899
.RIntToL
, which rounds to the nearest integer avoiding the problems caused with round-off error and Floor
or Ceiling
. RIntToL(x)
produces the same result that Floor(Round(x))
would produce. RIntToL(12.29*10000) -> 122900
FormattedNumberStr
. These functions typically round to the nearest displayable value. To display 2 decimal digits, use "%.2f": FormattedNumberStr(12.29, "%.2f") -> "12.29"
$29.95
as the integer 2995
or 29950
, then divide by 100
or 1000
to display the number. If you do this, keep in mind that there is a maximum representable integer value, 0x1FFFFFFF
or 536870911
, which is sufficient to track over 5 million dollars as pennies, but can't go much over that.Floor
deals with round off errors, you'll need to do some extra work keeping track of the precision of the number and the magnitude of the round off error. It's worthwhile to read a good numeric methods reference. Floating point numbers in NewtonScript are represented by IEEE 64-bit reals, which are accurate to around 15 decimal digits. The function NextAfterD
provides a handy way to see how 'close together' floating point numbers are. FormattedNumberStr(NextAfterD(0.3, kInfinity), "%.18f");
-> "0.300000000000000040"
http://gemma.apple.com/dev/techsupport/insidemac/PPCNumerics/PPCNumerics-2.html
The Newton floating point environment is not as rich in features as the PowerPC environment, and the PowerPC numerics document is only mentioned as a useful resource for understanding floating point issues. It in no way documents API or features of the Newton floating point environment.1001.1100001
1.0011100001 * 2^3
3+0x3FF = 0x402 = 100 0000 0010
0 10000000010 0011100001000000000000000000000000000000000000000000
0x4023840000000000
StrHexDump(9+97/128, 16) -> "4023840000000000"
real
. In addition to the NewtonScript floating point literal syntax, you can use the compile time function MakeBinaryFromHex
to construct real numbers, and you must use this style for custom NaN values. The most recent platform files for Newton 2.0 and Newton 2.1 provide constants for negative zero (kNegativeZero
), positive and negative infinity (kInfinity
, kNegativeInfinity
), and a canonical NaN (kNaN
). MakeBinaryFromHex("4023840000000000", 'real) -> 9.7578125 // = 9+97/128