New Syntax of TL

Motivation

Es hat sich herausgestellt, daß die TL-Syntax, so wie wir sie alle kennen, keineswegs LL(1) ist. In der Tat ist sie sogar mehrdeutig. Das ist mehr als nur ein kosmetisches Problem: Nachdem einige Fehler im Parsergenerator behoben wurden, weigert er sich, die alte Grammatik anzunehmen. Es gibt also nur zwei Möglichkeiten:
  1. Die alte, mehrdeutige Grammatik weiter mit dem fehlerhaften Parsergenerator übersetzen, oder
  2. die TL-Syntax so modifizieren, daß TL tatsächlich LL(1) ist, und die Bugfixes im Parsergenerator vornehmen.
Wir haben uns für den zweiten Weg entschieden. Das bedeutet: Wegen des Volumens der zu behandelnden Quellen ist dazu eine automatische Umwandlung sinnvoll, und eben diese Aufgabe erfüllt TL`tl2ntl'.

Syntax-Änderungen

Es hat sich nicht zu viel getan, TL ist immernoch TL. Die Änderungen sind im folgenden einzeln beschrieben.

dangling with

   "exception" value ["with" Signatures "end"]  -->
   "exception" value ["with" Signatures] "end"

   "raise" value ["with" Bindings "end"]  -->
   "raise" value ["with" Bindings] "end"

   "extend" value ["with" Bindings "end"]  -->
   "extend" value ["with" Bindings] "end"
Diese Änderung wurde nötig, da Ausdrücke wie
  raise exception "lala" with end
auf zwei verschiedene Arten geparst werden können:
  raise {exception "lala"} with end
  raise {exception "lala" with end}
Dies ist eine Variante des dangling-else-Problems.

Exception

   "Exception" ["with" Signatures "end"]  -->
   "Exception" Signatures "end"
Analog zur Wertebene.

infix-Identifier

Ide ::= identifier | infix | colonInfix | "{" Ide "}"
 -->
Ide ::= identifier | "infix" (infix | colonInfix)
Wenn man einen Infix-Identifier in seiner Rolle als Identifier und nicht als Operator ansprechen will, muß man ihn nun explizit als "infix" markieren. Das verhindert Fehler wie z.B.
  if test then 1 else -1 end
In der neuen Syntax heißt das nämlich:
  if test then 1 else
    infix-   (* Wert wird ignoriert *)
    1
  end
Weitere Beispiele:
let infix+ = int.add
let x = a + b
let y = infix+(a b)

open

   "open" ValueIde ":" Type  -->  
   "open" ValueIde "as" Type
Der Ausdruck
  tuple open x :Int end
könnte interpretiert werden als
  tuple {open x :Int} end
  tuple {open x} :Int end
Im zweiten Fall würde `:Int' als anonyme Typbindung interpretiert.

tuple_case

    "tuple" "case" ideG "of" type [with] Bindings "end" -->
    "tuple_case" ideG "of" type ["with" Bindings] "end"
Der Ausdruck
  tuple
    case x
     when c1 then 1
     when c2 then 2
    end
  end
führte vorher zu einem Parse-Fehler.

keine Locations

Die neue Grammatik berücksichtigt keine Locations mehr.

Präzedenzen

Es gibt eine subtile Modifikation, um die in Tycoon gewünschten Präzendenzen korrekt in einer LL(1)-Grammatik auszudrücken. Wenn man streng nach der alten Grammatik geht, könnte der Ausdruck
  fun(x :Bool) x andif foo
mehrdeutig geparst werden:
  {fun(x :Bool) x} andif foo
  fun(x :Bool) {x andif foo}
Da die zweite Interpretation die normalerweise gewünschte ist, wurde `fun' die niedrigste Präzedenz zugeordnet. Dadurch kann in den meisten Fällen genauso formuliert werden wie in der alten Syntax.

Es gibt eine Ausnahme: Wenn `fun' innerhalb eines Infix-Ausdrucks auftaucht, müssen explizit Klammern gesetzt werden.

Das selbe Problem entsteht mit `assert' und auf der Typebene mit `Fun' und `Oper'.

Beispiele:

let infix @@ (A,B<:Ok a:A b:B) = tuple a b end;

let f = fun(x :Int) x @@ 7;    (* genau wie bisher *)

100 @@ fun(x:Int) 7 @@ 8;    (* nicht erlaubt *)

(* so wurde es bisher interpretiert: *)
  100 @@ fun(x:Int) {7 @@ 8};
  100 @@ {fun(x:Int) 7 @@ 8};

(* das hätte aber auch gemeint sein können: *)
  100 @@ {fun(x:Int) 7} @@ 8;

(* Auch hier sind Klammern nötig: *)
f := {fun () :Int 17}

Aufruf von tl2ntl

Im Verzeichnis src/tl2ntl befindet sich ein Tycoon-script `makeit.tyc' zur Erstellung eines bootFiles. Das Bootfile wird unter dem Filenamen src/tl2ntl/t2t.x abgelegt.

Das Bootfile interpretiert alle Kommandozeilen-Argumente als Quelltextfiles. Jeder File wird mit sccs ausgecheckt, umgewandelt, und mit dem Kommentar "tl2ntl" wieder eingecheckt.

Es empfiehlt sich, nur Files aus dem aktuellen Verzeichnis zu behandeln, da es sonst Probleme mit SCCS gibt.

Die Files müssen genau ein Semikolon enthalten. Ist keins drin, steigt das Programm mit einer exception aus ("failed"), sind mehrere drin, wird nur der Text bis zum ersten Semikolon umgewandelt, der Rest bleibt wie er war. Das dürfte nur bei scripts (.tyc) eine Rolle spielen.

Bei der Umwandlung werden einige Kürzel ausgegeben, die für die angewandten Modifikationen stehen:

?
Vor einem infix oder colonInfix wurde "infix" eingefügt
?{
Klammern um ein infix oder colonInfix, das in einem let auftaucht, entfernt
XE
Nach "Exception" ein " end" eingefügt
XW
Bei "Exception with ... end" das with gelöscht
L
eine location in einen Kommentar verwandelt
:
einen Doppelpunkt (bei open) in ein "as" verwandelt
V
tuple case in "tuple_case" verwandelt
TW
"with" bei tuple case mit Bindings eingefügt
W
bei exception oder raise wegen fehlendem "with" ein "end" angehängt
S
squashed spaces (?)
|
tokens getrennt, die sonst zusammengefallen wären
{}
Klammern eingefügt, um Präzedenz klarzumachen
D{}
Klammern gelöscht, weil Präzedenz sowieso klar (z.B. bei infix-Ops)
K
identifier umbenannt, um Verwechslung mit einem keyword zu vermeiden
Außerdem existieren mehrere Shellskripte:
translate files...
übersetze alle als Argument angegebenen Quelltextfiles.
transenv directory
wandelt alle Files *.tm und *.ti im Directory um, deren SCCS-History nicht den String `tl2ntl' enthält. Die Ausgaben werden in die Datei directory/log geschrieben. Es empfiehlt sich, nachher nochmal mit `grep failed */log' (für abgefangene Tycoon-exceptions) und `grep abnormally */log' (`exited abnormally', Absturz der Tycoon-Maschine) nach nicht-übersetzten Files zu suchen.
transall
ruft transenv für alle Unterdirectories des aktuellen Directories auf, deren Namen noch nicht in der Datei `translated' des aktuellen Directories enthalten sind

Die vollständige, neue Grammatik

ide ::= identifier | 
        "infix" (infix | colonInfix);

ideList ::= 
	ide {"," ide};

optType ::= [":" type];

optBound ::=
	["<:" type];

typeBinding1 ::=
	["Dyn"] ide formalParameters optBound "=" type;

typeBinding ::=
	"Let" ["Rec"] typeBinding1 {"and" typeBinding1};


(* Signatures *)
   
Signatures ::=
	{ ValueOrTypeSignatures | TypeBinding | "Repeat" Type };

ValueOrTypeSignatures ::=
	["var" | "Dyn"] [ideList formalParameters] (":" | "<:") type;

formalParameters ::=
	{ "(" signatures ")" };

withSignatures ::=
	["with" signatures];


(* Types *)

Type ::=
	"Oper" "(" signatures ")" optBound Type |
	"Fun" "(" signatures ")" ":" Type |
	Type1;

Type1 ::=
	Type2 { colonInfix Type2 };

Type2 ::=
	Type3 { infix Type3 };

Type3 ::=
	Type4 { "(" { Type } ")" };

Type4 ::=
	"{" Type "}" |
	ide { "." ide } |
	"Ok" |
	"Nok" |
	"Tuple" signatures
	  { "case" ideList withSignatures }
	"end" |
	"Record" signatures "end" |
	"Exception" signatures "end" ;


(* values *)

optVar ::= [ "var" ];

valueBinding1 ::=
	optVar ide formalParameters optType "=" value;

valueRecBinding1 ::=
	optVar ide formalParameters optType "=" value;

valueBinding ::=
	valueBinding1 { "and" valueBinding1 } |
	"rec" valueRecBinding1 { "and" valueRecBinding1 };

bindings ::=
	{ optVar value |
	  "let" valueBinding |
	  typeBinding |
	  ":" "Dyn" type |
	  "open" ide [ "as" type ]
	}

tuple ::=
	"tuple" bindings "end";

withBindings ::=
	["with" bindings];

tupleCase ::=
	"tuple_case" ideG "of" type withBindings "end";

else ::= ["else" bindings];

if ::= 
	"if" value "then" bindings 
	  { "elsif" value "then" bindings }
	  elseG
	"end";

withIde ::= 
	["with" ide];

caseBranch ::=
	"when" ideList withIde "then" bindings;

case ::=
	"case" ["of"] value { caseBranch } else "end";


tryBranch ::=
	"when" value withIde "then" bindings;

try ::=
	"try" bindings { tryBranch } else "end";

for ::=
	"for" ide "=" value ( "upto" | "downto" ) value "do"
	  bindings
	"end";

value4 ::=
	ide |
	integer | real | string | char | longreal |
	tuple |
	tupleCase |
	"record" bindings "end" |
	"extend" value withBindings "end" |
	"array" bindings "end" |
	"exception" value withSignatures "end" |
	"begin" bindings "end" |
	"loop" bindings "end" |
	"ok" |
	"exit" |
	"while" value "do" bindings "end" |
	if |
	for |
	case |
	try |
	"raise" value withBindings "end" |
	"reraise" |
	"{" value "}" |
	"typeRep_new" "(" ":" type ")" |
	"dynamic_new" "(" value ")" |
	"dynamic_be" "(" value ":" type ")" |
	"_reflect" bindings "end";

value3 ::=
	value4
	{ "(" bindings ")" |
	  "?" ide |
	  "." ide |
	  "!" ide |
	  "[" value "]" |
	  "of" bindings "end"
	};

value2 ::= 
	value3 { infix value3 };
	
value1 ::=
	value2 { ("orif" | "andif" | colonInfix) value2 };

fun ::=
	"fun" "(" signatures ")" optType value;
	
assert ::=
	"assert" value;

value ::=
        value1 |
	fun |
	assert;


Axel Wienberg, (16-oct-95)