Causeway->Products->CSharp translator->Limitations

Limitations


Home
  About us
  Contact info
  Vector graphics
  SVG Fills & Filters
Products
  Price list
  CSharp translator
    User review
    Compiling APL
    Examples
    Limitations
    Seminars
  Dyalog
  APL2000
Support
  CUSP
  CausewayPro
  RainPro
  Newleaf
  Helpstuf
  Leafhtm
Tutorials
Demos
  Climate Charts
  VML graphics
  SVG examples
Free Stuff
  CSS Editor
Publications
  Seminars
  Articles
Causeway Graphical Systems
Compiling APL - current limitations and restrictions

This is a very roughly edited copy of our working notes. Many of the limitations are vanishing as we add better code-recognition, so if you see something that would stop you in your tracks, please let us know and we will see how easy it would be to fix!

Basic restrictions and caveats

NO local trad functions. Unnamed dfns are fine, and local named dfns are OK as long as they fit on one line. Multiline dfn definitions are fine inside other dfns. Dops work OK, but user-defined tradops are unlikely to be handled.

Ambivalent trad functions are not supported at present, as []NC larg makes no sense. No problem for Dfns as the 'alpha<-xx' syntax is easy to spot.

Arrays are simple and homogeneous or genuinely nested. This implies

 vec„'Hello',6

is an error. Most APLers think it should be anyway!

Scalar primitives only pervade arrays to depth 2 (we only allow string[][] and int[][] and double[][]). Deeper arrays are allowed, but must be handled with each or loops. ArrayLists are designed to provide efficient 'repository' functionality and can be used freely as vectors of things in .Net. The only primitives supported on arbitrary arrays are Pick, Index, Reshape at present. Probably some others like Compress and Drop will be added as required.

You need to be careful about rank. Singletons are not the same as scalars! Use †½mat not 1†½mat in general!

You need to be careful about the type of arrays. If you have a (pre-declared) double vector of float numbers, and want to assign the result of a strictly integer function to it, you must promote it explicitly with '+' as in ...

 mydvec „ + myintvec

You can use + to go from bool --> int and int --> double. It is harmless if used on a double.

This may be used with constants, but the C# generation is clever enough to spot things like:

 mydvec „ 2 3

and declare the constant as "new double[] {2,3};" for you. Similarly

 mydvec „ «

will declare "new double[0];" automatically, so you can use {zilde} fairly safely.

Strings and Arrays of Characters

Text constants are treated as strings, unless they only have one character. If you want to get 'inside' a string, you must explode it into a char[] which is done with ravel (,)

mycharvec „ ,'Hello, world'

To turn it back into a string, you must format it! In general, strings are treated as if they were char[] so 7{rho}"Hello" is a new string "HelloHe" and so on. Indexing looks inside and returns the selected characters as a string. This adds a few annoyances, but generates much nicer looking C# code which probably runs faster too. Note that strings are immutable in C#, so you cannot use indexed assignment or masked assignment on strings. Ravel them to get a char[], fool with that and use {format} to restore the result to a string!

Note that if you declare "string mystr" then do mystr<-'A' the generated code WILL assign mystr as a string, viz mystr="A", correctly. Similarly if you declare it to be a char[] it will be correctly initialised with an array of lots of chars.

Assignment and Arrays as Reference Types

Embedded assignment is strongly discouraged. Execution order may fool you. However it does work as expected in most reasonable cases. Beware of arr1 <- arr2 <- zilde as arrays are reference types so you just created 2 pointers to arr2. WATCH IT!!

Indexed assignment into arrays can cause strange side-effects. Arrays are passed by reference in normal C# fashion, so calling function foo and passing myarray ...

   foo myarray
   myarray[2]„12

... will overwrite the 2nd element of myarray IN THE CALLING FUNCTION. Be careful!! If you really want to do this, make sure to clone the array first using redundant indexing, for example:

   foo myarray[]
then mess with your new array. To avoid most of these issues ...
   vec2 „ vec1

will always be parsed to vec2 = (int[])vec1.Clone() to be sure you get a real copy. We could do this for function arguments too, of the above become too much of a pain.

Other Syntactic Issues to Watch For

Logical expressions are combined with && and || which means that they are LAZY. If the leftmost clause succeeds, the rest will simply not be run. This could result in application code which depended on side-effects of tests not working as expected.

The only Goto forms are:

  Goto Lab or GoTo 0
  Goto(expression){compress|take|drop|rho}Lab
  Goto Lab /{commute} expr
  Goto Lab {times}{iota} expr

So you cannot use Goto labels[PFk] as in mainframe days!

You can't compress a label vector!

You can't use {execute}(condition)/'do this' as an idiom - but this could be recognised and converted if required!

No execute (but you can use []FI to get numbers from text chars)
No []ELX or []ALX
Only simple :TRAP 0 :else :endtrap supported.
Dyalog []SIGNAL ignores the number, so behaves like []ERROR in APL+Win
No []FMT (use $PF or write your own, or provide C# alternative expression)
No []FX or []DEF
No []NC support (ambivalent trad functions are not allowed here)
No []VFI - use $FI and $VI in Dyalog APL to mimic []VI and []FI

Note that $VI and $FI accept low-minus, so don't bother making them high here.

Trig functions have scalar constant left arguments only so

 1{circle} array      ... is fine
 (4 rho 2 1){circle} array   ... will fail

One exception is the classic Polar->cartesian idiom:

 2 1 {circle} scalar    ====> vector of (x,y) co-ordinates
 2 1{jot}.{circle} vec  ====> matrix of xy co-ordinates

which is special-cased for double scalars and vectors of angles.

Residue - implemented with the C# definition so is the same as APL for +ve arguments but will give different results for -ve arguments. Be careful!

   :If beta<0      © Switch to APL behaviour for -ve arguments!
     beta„pi-pi|pi-beta
   :Else
     beta„pi|beta
   :End

If this gets to be a pain, we could allow AE.Residue to have APL behaviour and leave AE.Remainder to behave the C# way.

For now, all system variables are read-only:
[]TC[2] or []TCNL Newline
[]AV[4] ditto
[]TCLF Linefeed
[]D Digits (as a string)
[]A Uppercase alphabet (as a string)
[]IO IO is 1 always
[]PP PrintPrecision - machine default - ignored
[]PW PrintWidth - irrelevant
[]CT ComparisonTolerance is machine precision
[]RL RandomLink is meaningless
[]AV AtomicVector - not supported yet - should be avoided

so []IO is fixed at 1 here. May be relaxed in future if we really need these.

Note that you get new random numbers every time - you cannot force the 'seed'.

Functions with complex arguments must take the strand on the right so "foo a b c" is OK and "a foo b c" is OK but a b foo c d is not handled.

No []SHADOW (localisation must be determined lexically)

Only 'canned' expressions can be used with {each} for example:
'.',{each}stringarray # Fine
+/{each}vvec # Works as this inner product is idiomised
<\{each}vvec # No, until we idiomise lessthanscan as a primitive
Canned reductions and scans will be added as needed. Pepper (double each) is not allowed (yet).

Structural limits on Arrays

Inner and outer products are limited to 1 or two dimensions.
Arrays are limited to 0 1 2 dimensions (so we do scalars, vectors, matrices) only.
Dyadic transpose is therefore not supported (note that 1 1{transpose}mat is treated as an idiom and mapped to AE.MajorDiagonal(mat) always.)

Reshape takes a SCALAR left arg to create a vector result. Passing a length-1 vector will always be an error. For symmetry the shape of a vector is a scalar.

You cannot take the shape of a scalar. Rank = {rho}{rho} is special-cased here.

Dyadic reductions - some are special-cased as per inner products.

Limited options to the left of assignment viz

{Quad}<-stuff
{QuoteQuad}<-stuff
var<-stuff
arr[this;that]<-stuff
(msk/arr)<-stuff
((expr){pick}arr)<-stuff ... OK for vectors (converted to IndexAssign)

Strand assignment is allowed - but you can declare a multi-argument function which eliminates most of these. The resulting strand (usually line-1) is simply eliminated from the generated code. Strand assignment to a set of local variables is fine BUT you must pre-declare all the locals in question so that they can be correctly casted.

For terminal input str<-{QuoteQuad} is be OK but DOES NOT pad with blanks to the left of whatever the user typed to dummy the space used by the prompt. Relic VS APL behaviour no longer required!

Strands of names, constants and (expressions) seem OK, but avoid if possible as there may be all sorts of odd edge effects. If all the elements evaluate to the same data type, the result will be a simple array.

Session echo must be explicit with assignment to {Quad} or {QuoteQuad}, even at the left-hand end of a line. Other hanging output is just buried. Assigning to QuoteQuad uses Console.Write so requires 'using system;' in the sourcecode.

Dynamic Functions

Simple inline dfns work as you expect, and may contain guards and diamonds and other dfns:

 res„{3.1×{1+¾}¾}stuff

They may be transient (and nameless) or localised and named:

 AddTwelve„{¾+12}
 Incr„1°+
 IsOdd„{0=2|¾:'Even' ª 'Odd'}

Dfns are always created at the point of use, so they can take any appropriate data type as argument. Compositions are parsed as if they were dfns.

Multi-argument Dfns are parsed correctly if they include v1 v2 v3<-{omega} on the first line of the function. This seems to fit with most people's coding style, e.g.:

   step3„{costs zeros„¾                © 3: cover cols with starred zeros.
     stars„zeros=2                     © starred zeros.
     covers„2×cols stars               © covered cols.
     ~0¹covers:stars                   © all cols covered: solution.
     step4 costs zeros covers          © next step: 4.
   }

Note that in a tradfn, a locally defined dfn may not span more than one line. This would be quite a messy restriction to remove, but could be done.

Globally defined dfns are also generated at the point of use, so there is no need for any declarations of argument types etc. This has the consequence that a Dfn cannot be declared public, or restrict in any way the acceptable data types. A class will always need at least one tradfn as its public face.

Tail recursions are detected and generated as 'return to start' jumps rather than genuine recursions in C#. This allows mad benchmarks like Ackermann's function to run insanely fast. The same is not true of tail calls, so there may be a danger of 'stack full' errors when tail calls are used for very long loops, for example

todd„{    © Double-tailed
   even„{¾=0:1 ª ¾=1:0 ª odd ¾-1}
   odd„{¾=1:1 ª even ¾-1}
   odd ¾
 }

This will run out of stack space somewhere round 100000! This is unlikely to cause many problems in real application code.

Signalling Errors

72 is converted to ArgumentException. All other errors are just signalled.

Control Structures

If and AndIf must be on adjacent lines. No support for

 If summat
  do this
 AndIf summatelse
  do this as well
 Else
  do that
 End

which works in Dyalog (but not +Win, I think)

No support for

 :While (cond1)
   stuff
 :Until (cond2)

all other structures are OK, but note that

:Repeat
  forever
:EndRepeat

must end with an explicit EndRepeat, not just :End here.

:Select stuff
  :Case 1
  :CaseList 2 3
  :Else
:EndSelect  

may only be used with int and string data. Also the case expression is a constant, so

 CaseList 3+¼7

is NOT allowed here. If this proves too much of a pain, we could add code to parse 'hard' select blocks out into if..elseif....elseif....else chains instead. These will usually run slower, as the simple switch-case structure is handled very nicely by the C# compiler!

Special comments (to be avoided in normal use) are

  ©: type var1,var2    declares stuff
  ©# mark out inline C# alternatives to impossible stuff like system calls
  ©~ mark lines that are simply omitted as unnecessary (e.g. []EX to clean up)
  ©{ C# stuff } gives an alternate C# expression for a single line of APL
  ©©s Summary info creates XML documentation for the function (several lines allowed)
  ©:Skip    does what it says - this function is bypassed completely
  ©:Nobody  just builds the overloads and skips the base function.

The {lamp}# lines must come in 3s with the APL block followed by the C# block in this order.

 :if 1  ©#  APL original
   (len pattern)„ŒWCALL 'GetProfileString' 'intl' 'sshortdate' 'dd/MM/yy' (25½' ') 24
   pattern„len†pattern
 :else  ©#  C# equivalent 
   DateTimeFormatInfo dti = new DateTimeFormatInfo();
   pattern = dti.ShortDatePattern;  
 :end   ©#  End of inline code
 
 fluff„'ZI4' ŒFMT ¼12  ©~ The original code is preserved as a comment
 
 fluff„,'ZI4'ŒFMT var  ©{ fluff = var.ToString("0000"); }

Declarative comments are currently

  ©:Public
  ©:Public override 
  ©:Private
  ©:Static
  ©:Constructor      .... not reqd if the function-name == the class name
  ©: int a,b,c

Others may follow (e.g. ClassConstructor) so probably best to avoid ©: altogether here. You may leave spaces between the colon and the keyword (or not) as you prefer.

C# (and VB.Net) keywords should be avoided in general, and in particular in result variables and 'magic' variables like C# loop counters. There are some common traps such as:

new this fixed bool true false

Probably others yet to be discovered. These will work OK as normal locals, as we protect them with the '@' prefix. Still better avoided, as the resulting C# looks very ugly!

There are a few places where you must use 'true' and 'false' to pass boolean constants to functions, as '1' could be a bool or an int or a float! Suggest defining these as globals with the values 1 and 0 and using them everywhere!

Wrap Up

That is about all there is to it. Come to one of our seminars with the ugliest, oldest code you can find, and give it a try!


Website maintained by adrian@causeway.co.uk
Telephone: +44 (0) 1439 788413