Improvements Over MiniCalc
Tusk offers many improvements over MiniCalc.
Overall, Tusk is much more similar to Delphi
than MiniCalc is,
as discussed in many of the following sections.
Perhaps the single most important difference
between Tusk and MiniCalc is that Tusk offers
parse-time checking of numerous issues that MiniCalc
checks only at runtime.
For example, if an identifier is misspelled,
or a function is called with the wrong number
of arguments, Tusk catches these problems before
running any code.
MiniCalc finds these issues only if/when the
program attempts to execute the deficient
statement.
In MiniCalc, everything is a Variant.
In Tusk, this is also true, at least at runtime.
However, at parse time, Tusk has a robust type
system…
- Functions declare their return type.
- Procedure / function arguments specify a data type.
- Constants and variables are of a declared data type.
- Implicit type casts are safe (such as Char to string or Integer to Int64), while unsafe conversions require an explicit function call (such as string to TDateTime or Double to Byte).
MiniCalc uses dynamic scoping,
while Tusk and Delphi use static (lexical) scoping.
For example, consider this MiniCalc code…
const
a = 10;
function f;
begin
@Writeln(a);
end;
function Demo;
const
a = 99;
begin
f();
end;
Demo();
The above code prints 99.
In contrast, the following (nearly identical)
Delphi code prints 10…
const
a = 10;
procedure f;
begin
Writeln(a);
end;
procedure Demo;
const
a = 99;
begin
f;
end;
Demo;
The above Delphi code also works unchanged in Tusk,
and prints the same value.
In MiniCalc, reading an undefined variable yields
Unassigned, which quietly converts to
zero / false / empty string / etc.
Similarly, writing to an undefined variable
simply defines the variable (though not when in a function).
Tusk has neither of these downsides:
all variables must be declared before they are used.
Tusk offers a dedicated operator, unary /,
for accessing a topmost identifier.
MiniCalc has no such facility,
but uses a convention of naming global
functions and constants with a prefix of @.
This is rather messy, as it makes MiniCalc code
look different from Delphi code…
x := @Sqrt(@Abs(x) - @Ln(y) * @Pi);
x := Sqrt(Abs(x) - Ln(y) * Pi);
If the global Pi was hidden, MiniCalc has no solution…
const @Pi = '...';
x := @Sqrt(@Abs(x) - @Ln(y) * @Pi); // can't access the real @Pi
Meanwhile, Delphi code could do this…
const Pi = '...';
x := Sqrt(Abs(x) - Ln(y) * System.Pi);
In Tusk, the / operator is used to access the
topmost definition of an identifier
(which may or may not be global)…
const Pi = '...';
x := Sqrt(Abs(x) - Ln(y) * /Pi);
This symbol was chosen, among other reasons,
because it often denotes "top level" or "root".
Tusk, like Delphi, does not require an empty
pair of parentheses to call a procedure or function
with zero arguments.
MiniCalc does require the empty parentheses
for global functions, but not for methods.
In MiniCalc, there are many differences with Delphi
related to operators.
MiniCalc defines a bewildering assortment of operators,
around 35 more than Delphi.
These do little to improve the coding experience in MiniCalc,
and (when used) are significant barriers to Delphi compatibility.
Tusk defines only three operators not present in Delphi:
- The topmost operator, /
- The exponentiation operator, **
- The call suppression operator, @@
MiniCalc improves upon Delphi's operator precedence,
but this comes at the expense of Delphi compatibility.
MiniCalc offers 34 true operators that do not exist in
Delphi (such as #and, +=, ?=, and <=>),
changes the meaning of the & operator,
changes the precedence of several other operators,
and changes the ^ operator from postfix to prefix.
Overall, MiniCalc offers a dizzying number of additional
operators, precedence levels, and deviations from Delphi,
all of which is
carefully documented.
Meanwhile, Tusk's operators
much more closely match
Delphi's operators, leading to greater compatibility
and less confusion.
One specific area of concern is mixing and / or
operators with relational operators (=, <>, <, etc).
For example, in MiniCalc, these two are equivalent…
// These are equivalent (nice!)
@Writeln(a = b or c = d);
@Writeln((a = b) or (c = d));
However, in Delphi, these are equivalent…
// These are equivalent (yuck!)
Writeln(a = b or c = d);
Writeln((a = (b or c)) = d);
Thus, the expression a = b or c = d is legal in both
MiniCalc and Delphi, but has two different meanings.
Tusk improves upon MiniCalc by using the same operator
precedence as Delphi, for increased compatibility.
At the same time, Tusk forbids problematic
"under-parenthesized" expressions that Delphi permits
(more details here).
MiniCalc uses the & operator to take the address
of an expression.
Tusk and Delphi use the @ operator to take addresses,
and the & operator to escape keywords/operators.
All three languages use the ^ operator to dereference
a pointer.
However, in MiniCalc this is a prefix operator;
in Tusk and Delphi, it is postfix.
Tusk uses the ** operator for exponentiation,
whereas MiniCalc uses ^.
MiniCalc defines several keywords/operators that are not
keywords in Delphi: bitand, bitnot,
bitor, bitxor, defer,
false, inceptive, null,
true, unassigned, and using.
In Tusk, none of the above are keywords;
Tusk defines four additional keywords:
Break, Continue, Exit,
and out.
Their status as keywords can easily be circumvented
using the & operator, which works just as in Delphi.
Also, Tusk treats at and on as keywords,
which Delphi technically does not, however
Embarcadero's documentation states…
The words
Therefore, at and on are keywords in Tusk.
Again, the & operator easily addresses this issue.
at
and on
also have special meanings,
and should be treated as reserved words.
Tusk distinguishes between functions
(which return a value) and procedures
(which do not);
MiniCalc does not make this distinction.
Tusk supports Delphi's new
ternary operator…
Writeln(if Qty > 100 then 'Large' else 'Small');
Note that Tusk does not support the C-style ternary operator:
condition ? yes : no.
Tusk directly supports more Delphi data types than MiniCalc…
Tusk has much better support for generics than MiniCalc.
In MiniCalc, you can call @NewList or
@NewIntegerList, but you cannot instantiate an
IList<Boolean> or an IList<IStringList>.
In Tusk, you can use and instantiate generic types,
including types that may not be compiled into the host
Delphi application.
For example, IList<string> is always compiled in,
but IList<Single> may not be.
Either way, you can work with IList<Single> in Tusk.
In fact, you can do this kind of thing…
type
TMyEnum = (Choice1, Choice2, Choice3, Choice4, Choice5);
var e: TMyEnum := Choice2;
var ls: IList<TMyEnum> := NewListOf(TMyEnum);
ls.Add(e);
We plan to improve the syntax for instantiating generic lists,
from the above to TDSList<TMyEnum>.Create.
Tusk supports enums (scoped and non-scoped)
the way Delphi does: they are integers when stored
in a Variant.
In contrast, MiniCalc stores enums as strings,
leading to numerous compatibility issues
(for example, MiniCalc performs ordered comparisons
on enums based on name, while Delphi and Tusk
use the ordinal value instead).
Tusk supports enums in common RTL functions,
including Ord, Inc, Dec, Succ, Pred, etc.
Tusk supports Dephi's native set type
(sets of enum, Byte, Int8, and AnsiChar).
Tusk supports the familiar set operators:
+, -, *, in,
as well as the Include and Exclude routines.
Naturally, the for…in
loop supports sets.
MiniCalc emulates a set of enum with a list of string.
Tusk directly supports Delphi's
dynamic arrays. In contrast, MiniCalc arrays are
variant arrays, which are less efficient (because
they use the COM memory manager, not Delphi's FastMM)
and don't have the same semantics. For example, Variant
arrays are copied by value, but Delphi and Tusk
dynamic arrays are copied by reference.
Also, Variant arrays require the elements to be
OLE Automation-compatible, which rules out types
like sets and records, and converts Delphi
string and AnsiString values to WideString,
which use the slower COM memory manager
and are not reference-counted.
Tusk directly supports record types,
such as Decimal, TBitVector, TPoint, etc.
In MiniCalc, Decimal is supported via its custom
variant type, but this omits methods and properties
on the Decimal record such as Abs, IsZero,
InRange, etc.
Similar issues affect many other record types,
for example, TDSCharSet.
If s is a TDSCharSet and ch is a Char,
then Delphi and Tusk test for membership as follows…
s.Has(ch)
Meanwhile, MiniCalc requires this…
@HasValue(s, ch)
In Tusk, records such as Decimal and TDSCharSet
are the real deal, becoming the custom variant type
(varDecimal and varCharSet)
only when cast (implicitly or explicitly) to Variant.
Tusk does not support MiniCalc's [~ ... ~]
multi-line string literal.
It does, however, support Delphi's relatively
new ''' ... ''' multi-line string literal.
Tusk also offers string literals using double-quotes,
but these differ from MiniCalc – they are compatible
with C, C++, C#, Java, JavaScript, and JSON.
Of course, Tusk also supports traditional Delphi-like
string literals, using single quotes and pound signs,
and the newer multi-line strings that use 3 apostrophes.
In MiniCalc, the library documentation is maintained manually.
So, whenever a new interface method or property is exposed,
whenever a new global function is defined,
or a new type is added to the mix,
the documentation must be updated manually.
However, Tusk offers a built-in mechanism to generate
Sage documentation for all types, properties, methods,
and global routines.
Thus, as Tusk evolves,
the documentation can be generated easily,
without the risk of missing entries,
copy-and-paste errors, etc.
⏱ Last Modified: 2/15 9:51:46 am