MODULE---------------------------------------------------------------------------; IMPORT TextList, TextSeq, TextRefTbl, Pickle, TextRd, TextWr, Lex, Scan, FloatMode, Fmt, TextTextTbl, ASCII, TextExtras AS TextEx, Text, Rd, Wr, FileRd, FileWr, TextConv, OSError, Thread, RdExtras; IMPORT MsgIF, MsgX, TextUtils, ProcessEnv, TextReadingUtils; REVEAL T = Public BRANDED "SimpleValueEnv.T 0.0" OBJECT par : T; env : TextRefTbl.T; mu : MUTEX; msgif : MsgIF.T; METHODS typeI(name : TEXT; rec := TRUE) : Type := GetTypeInternal; textValInternal(name : TEXT; rec := TRUE) : TEXT := TextValInternal; substTextValInternal(name : TEXT; rec := TRUE) : TEXT := SubstTextValInternal; textValOrNilInternal(name : TEXT; rec := TRUE) : TEXT := TextValOrNilInternal; OVERRIDES init := Init; parent := Parent; copy := Copy; defined := Defined; type := GetType; intVal := IntVal; natVal := NatVal; refVal := RefVal; textVal := TextVal; listVal := ListVal; substTextVal := SubstTextVal; textValOrNil := TextValOrNil; substTextValOrNil := SubstTextValOrNil; seqVal := SeqVal; setIntVal := SetIntVal; setNatVal := SetNatVal; setRefVal := SetRefVal; setTextVal := SetTextVal; setListVal := SetListVal; setSeqVal := SetSeqVal; delVal := DelVal; setFromTextTextTbl := SetFromTextTextTbl; toTextTextTbl := ToTextTextTbl; toText := ToText; keys := Keys; keyList := KeyList; END; SimpleValueEnv
PROCEDURE---------------------------------------------------------------------------Init (self : T; parent : T := NIL; msgif : MsgIF.T := NIL) : T = BEGIN self.mu := NEW(MUTEX); self.par := parent; self.env := NEW(TextRefTbl.Default).init(); self.msgif := msgif; RETURN self; END Init;
PROCEDURE---------------------------------------------------------------------------Parent (self : T) : T = BEGIN LOCK self.mu DO RETURN self.par; END; END Parent;
PROCEDURE---------------------------------------------------------------------------Copy (self : T; env : T; deep := TRUE; rec := TRUE) = VAR iter : TextRefTbl.Iterator; name : TEXT; val : REFANY; PROCEDURE CopyOne(env : T) = BEGIN iter := env.env.iterate(); WHILE iter.next(name, val) DO IF deep THEN VAR rd : TextRd.T; wr : TextWr.T; BEGIN wr := TextWr.New(); TRY Pickle.Write(wr, val); rd := TextRd.New(TextWr.ToText(wr)); val := Pickle.Read(rd); EXCEPT ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.Copy()", "deep copy of " & name & " failed"); RETURN; END; END; END; EVAL self.env.put(name, val); END; END CopyOne; BEGIN LOCK self.mu DO LOCK env.mu DO CopyOne(env); WHILE rec AND env.par # NIL DO env := env.par; CopyOne(env); END; END; END; END Copy;
PROCEDURE---------------------------------------------------------------------------Defined (self : T; name : TEXT; rec := TRUE) : BOOLEAN = VAR val : REFANY; def : BOOLEAN; BEGIN LOCK self.mu DO def := self.env.get(name, val); IF def THEN RETURN TRUE; ELSIF rec AND self.par # NIL THEN RETURN self.par.defined(name, rec); ELSE RETURN FALSE; END; END; END Defined;
PROCEDURE---------------------------------------------------------------------------GetTypeInternal (self : T; name : TEXT; rec := TRUE) : Type = VAR val : REFANY; def : BOOLEAN; BEGIN def := self.env.get(name, val); IF def THEN TYPECASE val OF IntObj => IF NARROW(val, IntObj).val >= 0 THEN RETURN Type.Nat; ELSE RETURN Type.Int; END; | TEXT => RETURN Type.Text; | TextSeq.T => RETURN Type.Seq; | TextList.T => RETURN Type.List; ELSE RETURN Type.Ref; END; ELSIF rec AND self.par # NIL THEN RETURN self.par.type(name, rec); ELSE RETURN Type.None; END; END GetTypeInternal;
PROCEDURE---------------------------------------------------------------------------GetType (self : T; name : TEXT; rec := TRUE) : Type = BEGIN LOCK self.mu DO RETURN GetTypeInternal(self, name, rec); END; END GetType;
PROCEDURE---------------------------------------------------------------------------IntVal (self : T; name : TEXT; rec := TRUE) : INTEGER = VAR val : REFANY; def : BOOLEAN; BEGIN LOCK self.mu DO def := self.env.get(name, val); IF def THEN TYPECASE val OF IntObj => RETURN NARROW(val, IntObj).val; | TEXT => TRY RETURN Scan.Int(NARROW(val, TEXT)); EXCEPT Lex.Error,FloatMode.Trap => MsgX.Fatal2(self.msgif, "SimpleValueEnv.IntVal", "cannot convert value to Int: " & name); RETURN 0; END; | TextSeq.T => RETURN NARROW(val, TextSeq.T).size(); | TextList.T => RETURN TextList.Length(TextListVal(val)); ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.IntVal", "cannot convert Ref to Int: " & name); RETURN 0; END; ELSIF rec AND self.par # NIL THEN RETURN self.par.intVal(name, rec); ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.IntVal", <*NOWARN*> "no value associated with " & name); RETURN 0; END; END; END IntVal;
PROCEDURE---------------------------------------------------------------------------NatVal (self : T; name : TEXT; rec := TRUE) : CARDINAL = VAR val : REFANY; def : BOOLEAN; BEGIN LOCK self.mu DO def := self.env.get(name, val); IF def THEN TYPECASE val OF IntObj => WITH v = NARROW(val, IntObj).val DO IF v >= 0 THEN RETURN v; ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.NatVal", "cannot convert value to Nat: " & name); RETURN 0; END; END; | TEXT => TRY WITH v = Scan.Int(NARROW(val, TEXT)) DO IF v >= 0 THEN RETURN v; ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.NatVal", "cannot convert value to Nat: " & name); RETURN 0; END; END; EXCEPT Lex.Error,FloatMode.Trap => MsgX.Fatal2(self.msgif, "SimpleValueEnv.NatVal", "cannot convert value to Nat: " & name); RETURN 0; END; | TextSeq.T => RETURN NARROW(val, TextSeq.T).size(); | TextList.T => RETURN TextList.Length(TextListVal(val)); ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.NatVal", "cannot convert Ref to Nat: " & name); RETURN 0; END; ELSIF rec AND self.par # NIL THEN RETURN self.par.natVal(name, rec); ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.NatVal", <*NOWARN*> "no value associated with " & name); RETURN 0; END; END; END NatVal;
PROCEDURE---------------------------------------------------------------------------RefVal (self : T; name : TEXT; rec := TRUE) : REFANY = VAR val : REFANY; def : BOOLEAN; BEGIN LOCK self.mu DO def := self.env.get(name, val); IF def THEN RETURN val; ELSIF rec AND self.par # NIL THEN RETURN self.par.refVal(name, rec); ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.RefVal", <*NOWARN*> "no value associated with " & name); RETURN NIL; END; END; END RefVal;
PROCEDURE---------------------------------------------------------------------------TextValInternal (self : T; name : TEXT; rec := TRUE) : TEXT = VAR val : REFANY; def : BOOLEAN; BEGIN def := self.env.get(name, val); IF def THEN TYPECASE val OF IntObj => WITH v = NARROW(val, IntObj).val DO RETURN Fmt.Int(v); END; | TEXT => RETURN NARROW(val, TEXT); | TextSeq.T => RETURN TextUtils.TextSeqToText(NARROW(val, TextSeq.T)); | TextList.T => RETURN TextUtils.TextSeqToText(ListToSeq(TextListVal(val))); ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.TextVal", <*NOWARN*> "cannot convert Ref to Text: " & name); RETURN NIL; END; ELSIF rec AND self.par # NIL THEN RETURN self.par.textVal(name, rec); ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.TextVal", <*NOWARN*> "no value associated with " & name); RETURN NIL; END; END TextValInternal;
PROCEDURE---------------------------------------------------------------------------TextVal (self : T; name : TEXT; rec := TRUE) : TEXT = BEGIN LOCK self.mu DO RETURN TextValInternal(self, name, rec); END; END TextVal;
PROCEDURE---------------------------------------------------------------------------TextValOrNilInternal (self : T; name : TEXT; rec := TRUE) : TEXT = VAR val : REFANY; def : BOOLEAN; BEGIN def := self.env.get(name, val); IF def THEN TYPECASE val OF IntObj => WITH v = NARROW(val, IntObj).val DO RETURN Fmt.Int(v); END; | TEXT => RETURN NARROW(val, TEXT); | TextSeq.T => RETURN TextUtils.TextSeqToText(NARROW(val, TextSeq.T)); | TextList.T => RETURN TextUtils.TextSeqToText(ListToSeq(TextListVal(val))); ELSE RETURN NIL; END; ELSIF rec AND self.par # NIL THEN RETURN self.par.textValOrNil(name, rec); ELSE RETURN NIL; END; END TextValOrNilInternal;
PROCEDURE---------------------------------------------------------------------------TextValOrNil (self : T; name : TEXT; rec := TRUE) : TEXT = BEGIN LOCK self.mu DO RETURN TextValOrNilInternal(self, name, rec); END; END TextValOrNil;
PROCEDURE---------------------------------------------------------------------------SubstEverythingInternal (self : T; t : TEXT) : TEXT = VAR res := t; BEGIN (* obsolete IF GetTypeInternal(self, "HOME") = Type.Text THEN res := TextUtils.Substitute(res, "{HOME}", TextValInternal(self, "HOME")); END; IF GetTypeInternal(self, "USER") = Type.Text THEN res := TextUtils.Substitute(res, "{USER}", TextValInternal(self, "USER")); END; *) res := TextUtils.SubstEnvVars(res, penv); (* all environment variables have been substituted in val *) TRY res := SubstituteVariablesInternal(res, self); EXCEPT Error(e) => MsgX.Error(self.msgif, "error in variable definition: " & e); END; (* all internal environement variables have been substituted in val *) RETURN res; END SubstEverythingInternal;
PROCEDURE---------------------------------------------------------------------------SubstEverything (self : T; t : TEXT) : TEXT = VAR res := t; BEGIN (* obsolete IF self.type("HOME") = Type.Text THEN res := TextUtils.Substitute(res, "{HOME}", self.textVal("HOME")); END; IF self.type("USER") = Type.Text THEN res := TextUtils.Substitute(res, "{USER}", self.textVal("USER")); END; *) res := TextUtils.SubstEnvVars(res, penv); (* all environment variables have been substituted in val *) TRY res := SubstituteVariables(res, self); EXCEPT Error(e) => MsgX.Error(self.msgif, "error in variable definition: " & e); END; (* all internal environement variables have been substituted in val *) RETURN res; END SubstEverything;
PROCEDURE---------------------------------------------------------------------------SubstTextVal (self : T; name : TEXT; rec := TRUE) : TEXT = VAR res : TEXT; BEGIN res := self.textVal(name, rec); RETURN SubstEverything(self, res); END SubstTextVal;
PROCEDURE---------------------------------------------------------------------------SubstTextValInternal (self : T; name : TEXT; rec := TRUE) : TEXT = VAR res : TEXT; BEGIN res := self.textValInternal(name, rec); RETURN SubstEverythingInternal(self, res); END SubstTextValInternal;
PROCEDURE---------------------------------------------------------------------------SubstTextValOrNil (self : T; name : TEXT; rec := TRUE) : TEXT = VAR res : TEXT; BEGIN res := self.textValOrNil(name, rec); IF res = NIL THEN RETURN NIL END; RETURN SubstEverything(self, res); END SubstTextValOrNil;
PROCEDURE---------------------------------------------------------------------------ListVal (self : T; name : TEXT; rec := TRUE) : TextList.T = VAR val : REFANY; def : BOOLEAN; BEGIN LOCK self.mu DO def := self.env.get(name, val); IF def THEN TYPECASE val OF IntObj => WITH v = Fmt.Int(NARROW(val, IntObj).val) DO RETURN TextList.List1(v); END; | TEXT => RETURN TextList.List1(NARROW(val, TEXT)); | TextSeq.T => RETURN SeqToList(NARROW(val, TextSeq.T)); | TextList.T => RETURN TextListVal(val); ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.ListVal", <*NOWARN*> "cannot convert Ref to List: " & name); RETURN NIL; END; ELSIF rec AND self.par # NIL THEN RETURN self.par.listVal(name, rec); ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.ListVal", <*NOWARN*> "no value associated with " & name); RETURN NIL; END; END; END ListVal;
PROCEDURE---------------------------------------------------------------------------SeqVal (self : T; name : TEXT; rec := TRUE) : TextSeq.T = VAR val : REFANY; def : BOOLEAN; BEGIN LOCK self.mu DO def := self.env.get(name, val); IF def THEN TYPECASE val OF IntObj => WITH v = Fmt.Int(NARROW(val, IntObj).val) DO RETURN TextSeq1(v); END; | TEXT => RETURN TextSeq1(NARROW(val, TEXT)); | TextSeq.T => RETURN NARROW(val, TextSeq.T); | TextList.T => RETURN ListToSeq(TextListVal(val)); ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.SeqVal", <*NOWARN*> "cannot convert Ref to Seq: " & name); RETURN NIL; END; ELSIF rec AND self.par # NIL THEN RETURN self.par.seqVal(name, rec); ELSE MsgX.Fatal2(self.msgif, "SimpleValueEnv.SeqVal", <*NOWARN*> "no value associated with " & name); RETURN NIL; END; END END SeqVal;
PROCEDURE---------------------------------------------------------------------------SetIntVal (self : T; name : TEXT; val : INTEGER) = VAR v : REFANY; def : BOOLEAN; BEGIN LOCK self.mu DO def := self.env.get(name, v); IF def AND self.typeI(name, FALSE) = Type.Int OR self.typeI(name, FALSE) = Type.Nat THEN NARROW(v, IntObj).val := val; ELSE EVAL self.env.put(name, NEW(IntObj).init(val)); END; END; END SetIntVal;
PROCEDURE---------------------------------------------------------------------------SetNatVal (self : T; name : TEXT; val : CARDINAL) = VAR v : REFANY; def : BOOLEAN; BEGIN LOCK self.mu DO def := self.env.get(name, v); IF def AND self.typeI(name, FALSE) = Type.Int OR self.typeI(name, FALSE) = Type.Nat THEN NARROW(v, IntObj).val := val; ELSE EVAL self.env.put(name, NEW(IntObj).init(val)); END; END; END SetNatVal;
PROCEDURE---------------------------------------------------------------------------SetRefVal (self : T; name : TEXT; val : REFANY) = BEGIN LOCK self.mu DO EVAL self.env.put(name, val); END; END SetRefVal;
PROCEDURE---------------------------------------------------------------------------SetTextVal (self : T; name : TEXT; val : TEXT) = BEGIN LOCK self.mu DO EVAL self.env.put(name, val); END; END SetTextVal;
PROCEDURE---------------------------------------------------------------------------SetListVal (self : T; name : TEXT; val : TextList.T) = BEGIN LOCK self.mu DO IF val = NIL THEN EVAL self.env.put(name, nil); (* we must not put NIL into a TextRefTbl.T *) ELSE EVAL self.env.put(name, val); END; END; END SetListVal;
PROCEDURE---------------------------------------------------------------------------SetSeqVal (self : T; name : TEXT; val : TextSeq.T) = BEGIN LOCK self.mu DO EVAL self.env.put(name, val); END; END SetSeqVal;
PROCEDURE---------------------------------------------------------------------------DelVal (self : T; name : TEXT) = VAR val : REFANY; BEGIN LOCK self.mu DO EVAL self.env.delete(name, val); END; END DelVal;
PROCEDURE---------------------------------------------------------------------------ToTextTextTbl (self : T; plain := TRUE; rec := TRUE) : TextTextTbl.T = VAR res : TextTextTbl.Default; name : TEXT; value : TEXT; ref : REFANY; iter : TextRefTbl.Iterator; BEGIN LOCK self.mu DO IF rec AND self.par # NIL THEN res := self.par.toTextTextTbl(plain, rec); ELSE res := NEW(TextTextTbl.Default).init(); END; iter := self.env.iterate(); WHILE iter.next(name, ref) DO value := self.textValOrNilInternal(name, rec); IF plain THEN value := SubstEverythingInternal(self, value); END; EVAL res.put(name, value); END; END; RETURN res; END ToTextTextTbl;
PROCEDURE---------------------------------------------------------------------------SetFromTextTextTbl (self : T; tbl : TextTextTbl.T) : T = VAR name, value : TEXT; iter := tbl.iterate(); BEGIN WHILE iter.next(name, value) DO self.setTextVal(name, value); END; RETURN self; END SetFromTextTextTbl;
PROCEDURE---------------------------------------------------------------------------IntObjInit (self : IntObj; v : INTEGER) : IntObj = BEGIN self.val := v; RETURN self; END IntObjInit;
PROCEDURE---------------------------------------------------------------------------TextSeq1 (t : TEXT) : TextSeq.T = VAR res := NEW(TextSeq.T).init(1); BEGIN res.addhi(t); RETURN res; END TextSeq1;
PROCEDURE---------------------------------------------------------------------------ListToSeq (l : TextList.T) : TextSeq.T = VAR res := NEW(TextSeq.T).init(TextList.Length(l)); act := l; BEGIN WHILE act # NIL DO res.addhi(act.head); act := act.tail; END; RETURN res; END ListToSeq;
PROCEDURE---------------------------------------------------------------------------SeqToList (s : TextSeq.T) : TextList.T = VAR res : TextList.T := NIL; BEGIN FOR i := s.size() - 1 TO 0 DO WITH elem = s.get(i) DO res := TextList.Cons(elem, res); END; END; RETURN res; END SeqToList;
PROCEDURE---------------------------------------------------------------------------SubstituteVariables (t : TEXT; parameters : T) : TEXT RAISES {Error} = VAR i : CARDINAL := 0; j, k, l, m : CARDINAL; pre, suf, name, val : TEXT; vchars := ASCII.Set{':', '?', '!'}; c : CHAR; defVar, defConst : BOOLEAN; defaultValue, defaultVarName, expr : TEXT; BEGIN WHILE TextEx.FindChar(t, '{', i) DO j := i; IF TextEx.FindCharSet(t, vchars, j) AND (j = i + 1) THEN (* found {: or {! or {? *) c := Text.GetChar(t, j); INC(j); k := j; IF TextEx.FindChar(t, '}', k) THEN pre := Text.Sub(t, 0, i); name := Text.Sub(t, j, k - j); (* check for default values, either {:varname?varname}, {:varname:const}, or {:varname?varname:const} *) defaultValue := NIL; l:= 0 ; m := 0; defVar := TextEx.FindChar(name, '?', l); defConst := TextEx.FindChar(name, ':', m); IF defVar AND defConst THEN IF l < m THEN expr := name; name := Text.Sub(expr, 0, l); defaultVarName := Text.Sub(expr, l + 1, m - l -1); IF parameters # NIL THEN defaultValue := parameters.textValOrNil(defaultVarName); IF defaultValue = NIL THEN defaultValue := Text.Sub(expr, m + 1); ELSE defaultValue := SubstituteVariables(defaultValue, parameters); END; ELSE defaultValue := Text.Sub(expr, m + 1); END; ELSE RAISE Error("invalid default value syntax: " & expr); END; ELSIF defVar THEN expr := name; name := Text.Sub(expr, 0, l); defaultVarName := Text.Sub(expr, l + 1); IF parameters # NIL THEN defaultValue := parameters.textValOrNil(defaultVarName); IF defaultValue # NIL THEN defaultValue := SubstituteVariables(defaultValue, parameters); END; ELSE defaultValue := NIL; END; ELSIF defConst THEN expr := name; name := Text.Sub(expr, 0, m); defaultValue := Text.Sub(expr, m + 1); END; (* If there is a default value, it is now contained in defaultValue, and name is adapted appropriately. *) suf := Text.Sub(t, k + 1, LAST(CARDINAL)); IF parameters # NIL THEN IF ( parameters.type(name) = Type.Int OR parameters.type(name) = Type.Nat OR parameters.type(name) = Type.Text OR parameters.type(name) = Type.List OR parameters.type(name) = Type.Seq) THEN val := parameters.substTextVal(name); IF c = '!' THEN IF val = NIL OR Text.Empty(val) THEN val := defaultValue; IF val = NIL OR Text.Empty(val) THEN RAISE Error("mandatory variable " & name & " is empty"); END; END; END; pre := pre & val; ELSE (* no value found for `name' *) IF defaultValue = NIL THEN IF c # '?' THEN RAISE Error("mandatory variable " & name & " undefined"); END; ELSE pre := pre & defaultValue; END; END; ELSE (* no values at all *) IF c # '?' THEN RAISE Error("mandatory variable " & name & " undefined"); END; END; t := pre & suf; i := Text.Length(pre); ELSE (* no matching '}' found *) RAISE Error("syntax error: no matching } in `" & t & "'"); END; ELSE (* no valid begin found *) INC(i); END; END; RETURN t; END SubstituteVariables;
PROCEDURE---------------------------------------------------------------------------SubstituteVariablesInternal (t : TEXT; parameters : T) : TEXT RAISES {Error} = VAR i : CARDINAL := 0; j, k, l, m : CARDINAL; pre, suf, name, val : TEXT; vchars := ASCII.Set{':', '?', '!'}; c : CHAR; defVar, defConst : BOOLEAN; defaultValue, defaultVarName, expr : TEXT; BEGIN WHILE TextEx.FindChar(t, '{', i) DO j := i; IF TextEx.FindCharSet(t, vchars, j) AND (j = i + 1) THEN (* found {: or {! or {? *) c := Text.GetChar(t, j); INC(j); k := j; IF TextEx.FindChar(t, '}', k) THEN pre := Text.Sub(t, 0, i); name := Text.Sub(t, j, k - j); (* check for default values, either {:varname?varname}, {:varname:const}, or {:varname?varname:const} *) defaultValue := NIL; l:= 0 ; m := 0; defVar := TextEx.FindChar(name, '?', l); defConst := TextEx.FindChar(name, ':', m); IF defVar AND defConst THEN IF l < m THEN expr := name; name := Text.Sub(expr, 0, l); defaultVarName := Text.Sub(expr, l + 1, m - l -1); IF parameters # NIL THEN defaultValue := parameters.textValOrNilInternal(defaultVarName); IF defaultValue = NIL THEN defaultValue := Text.Sub(expr, m + 1); ELSE defaultValue := SubstituteVariablesInternal(defaultValue, parameters); END; ELSE defaultValue := Text.Sub(expr, m + 1); END; ELSE RAISE Error("invalid default value syntax: " & expr); END; ELSIF defVar THEN expr := name; name := Text.Sub(expr, 0, l); defaultVarName := Text.Sub(expr, l + 1); IF parameters # NIL THEN defaultValue := parameters.textValOrNilInternal(defaultVarName); IF defaultValue # NIL THEN defaultValue := SubstituteVariablesInternal(defaultValue, parameters); END; ELSE defaultValue := NIL; END; ELSIF defConst THEN expr := name; name := Text.Sub(expr, 0, m); defaultValue := Text.Sub(expr, m + 1); END; (* If there is a default value, it is now contained in defaultValue, and name is adapted appropriately. *) suf := Text.Sub(t, k + 1, LAST(CARDINAL)); IF parameters # NIL THEN IF ( parameters.typeI(name) = Type.Int OR parameters.typeI(name) = Type.Nat OR parameters.typeI(name) = Type.Text OR parameters.typeI(name) = Type.List OR parameters.typeI(name) = Type.Seq) THEN val := parameters.substTextValInternal(name); IF c = '!' THEN IF val = NIL OR Text.Empty(val) THEN val := defaultValue; IF val = NIL OR Text.Empty(val) THEN RAISE Error("mandatory variable " & name & " is empty"); END; END; END; pre := pre & val; ELSE (* no value found for `name' *) IF defaultValue = NIL THEN IF c # '?' THEN RAISE Error("mandatory variable " & name & " undefined"); END; ELSE pre := pre & defaultValue; END; END; ELSE (* no values at all *) IF c # '?' THEN RAISE Error("mandatory variable " & name & " undefined"); END; END; t := pre & suf; i := Text.Length(pre); ELSE (* no matching '}' found *) RAISE Error("syntax error: no matching } in `" & t & "'"); END; ELSE (* no valid begin found *) INC(i); END; END; RETURN t; END SubstituteVariablesInternal;
PROCEDURE---------------------------------------------------------------------------TextListVal (val : REFANY) : TextList.T = BEGIN IF val = nil THEN RETURN NIL; ELSE RETURN NARROW(val, TextList.T); END; END TextListVal;
PROCEDURE---------------------------------------------------------------------------ToText (self : T; rec := TRUE; plain := TRUE) : TEXT = VAR res := ""; name : TEXT; value : TEXT; typet : TEXT; ref : REFANY; iter : TextRefTbl.Iterator; t : Type; BEGIN LOCK self.mu DO iter := self.env.iterate(); WHILE iter.next(name, ref) DO value := self.textValOrNilInternal(name, FALSE); IF plain THEN value := SubstEverythingInternal(self, value); END; t := self.typeI(name); CASE t OF Type.Nat => typet := "Nat"; | Type.Int => typet := "Int"; | Type.Ref => typet := "Ref"; | Type.Seq => typet := "Seq"; | Type.Text => typet := "Text"; | Type.List => typet := "List"; | Type.None => typet := "None"; ELSE typet := "Unknown"; END; res := res & Fmt.F("%-35s = %-35s\n", name & "[" & typet & "]", value); END; IF rec AND self.par # NIL THEN res := res & "----\n" & self.par.toText(rec, plain); END; END; RETURN res; END ToText;
PROCEDURE---------------------------------------------------------------------------Keys (self : T; rec := TRUE) : TextSeq.T = VAR res := NEW(TextSeq.T).init(); name : TEXT; ref : REFANY; iter : TextRefTbl.Iterator; BEGIN LOCK self.mu DO iter := self.env.iterate(); WHILE iter.next(name, ref) DO res.addhi(name); END; IF rec AND self.par # NIL THEN res := TextSeq.Cat(res, self.par.keys(rec)); END; END; RETURN res; END Keys;
PROCEDURE---------------------------------------------------------------------------KeyList (self : T; rec := TRUE) : TextList.T = VAR res : TextList.T := NIL; name : TEXT; ref : REFANY; iter : TextRefTbl.Iterator; BEGIN LOCK self.mu DO iter := self.env.iterate(); WHILE iter.next(name, ref) DO res := TextList.Cons(name, res); END; IF rec AND self.par # NIL THEN res := TextList.AppendD(res, KeyList(self.par, rec)); END; END; RETURN res; END KeyList;
CONST KeywordChars = SET OF CHAR {'A'..'Z', 'a'..'z', '0'..'9', '-', '_'}; KeywordAsciis = ASCII.AlphaNumerics + ASCII.Set{'-', '_'}; NonKeywordAsciis = ASCII.All - KeywordAsciis; TypeBeginChar = '['; CommentChar = '#'; EnvSeparator = "---end-of-environment---"; AllChars = SET OF CHAR{'\000'..'\377'}; NonNewLineChars = AllChars - SET OF CHAR{'-'};---------------------------------------------------------------------------
PROCEDURE---------------------------------------------------------------------------LookAhead (rd : Rd.T) : CHAR RAISES {E} = VAR c : CHAR; BEGIN TRY Lex.Skip(rd); c := Rd.GetChar(rd); Rd.UnGetChar(rd); EXCEPT Rd.Failure => RAISE E("error reading look-ahead char"); | Rd.EndOfFile => RAISE E("error: no look-ahead"); | Thread.Alerted => RAISE E("reading interrupted"); END; RETURN c; END LookAhead;
PROCEDURE---------------------------------------------------------------------------TextListRepr (l : TextList.T) : TEXT = VAR one : TEXT; res : TEXT := ""; BEGIN IF l = NIL THEN RETURN "NIL"; END; WHILE l # NIL DO IF Text.Empty(res) THEN IF l.head = NIL THEN one := TextConv.Encode("", quoted := TRUE); ELSE one := TextConv.Encode(l.head, quoted := TRUE); END; ELSE IF l.head = NIL THEN one := ", " & TextConv.Encode("", quoted := TRUE); ELSE one := ", " & TextConv.Encode(l.head, quoted := TRUE); END; END; res := res & one; l := l.tail; END; RETURN res; END TextListRepr;
PROCEDURE---------------------------------------------------------------------------TextSeqRepr (l : TextSeq.T) : TEXT = VAR elem : TEXT; one : TEXT; res : TEXT := ""; BEGIN IF l.size() = 0 THEN RETURN "EMPTY"; END; FOR i := 0 TO l.size() - 1 DO elem := l.get(i); IF Text.Empty(res) THEN IF elem = NIL THEN one := TextConv.Encode("", quoted := TRUE); ELSE one := TextConv.Encode(elem, quoted := TRUE); END; ELSE IF elem = NIL THEN one := ", " & TextConv.Encode("", quoted := TRUE); ELSE one := ", " & TextConv.Encode(elem, quoted := TRUE); END; END; res := res & one; END; RETURN res; END TextSeqRepr;
PROCEDURE---------------------------------------------------------------------------ReadString (rd : Rd.T) : TEXT RAISES {Rd.Failure, Rd.EndOfFile, Thread.Alerted} = VAR t : TEXT; BEGIN t := TextReadingUtils.GetString(rd); WHILE NOT Text.Empty(t) AND Text.GetChar(t, Text.Length(t) - 1) = '\\' DO t := t & "\"" & RdExtras.GetText(rd, ASCII.Set{}, ASCII.Set{'\"'}, FALSE); END; RETURN t; END ReadString;
PROCEDURE---------------------------------------------------------------------------ReadTextList (rd : Rd.T) : TextList.T RAISES {E} = VAR c : CHAR; t : TEXT; l : TextList.T := NIL; BEGIN TRY c := LookAhead(rd); IF c = '\"' THEN t := TextConv.Decode(ReadString(rd), quoted := FALSE); ELSE t := Lex.Scan(rd, KeywordChars); IF Text.Equal(t, "NIL") THEN RETURN NIL; ELSE RAISE E("malformed list value"); END; END; l := TextList.List1(t); c := LookAhead(rd); WHILE c = ',' DO t := TextConv.Decode(ReadString(rd), quoted := FALSE); l := TextList.AppendD(l, TextList.List1(t)); c := LookAhead(rd); END; EXCEPT Rd.Failure => RAISE E("error reading list value"); | Rd.EndOfFile => RAISE E("error: incomplete list value"); | Thread.Alerted => RAISE E("reading interrupted"); | TextConv.Fail => RAISE E("cannot decode environment: conversion error"); END; RETURN l; END ReadTextList;
PROCEDURE---------------------------------------------------------------------------ReadTextSeq (rd : Rd.T) : TextSeq.T RAISES {E} = VAR c : CHAR; t : TEXT; l := NEW(TextSeq.T).init(); BEGIN TRY c := LookAhead(rd); IF c = '\"' THEN t := TextConv.Decode(ReadString(rd), quoted := FALSE); ELSE t := Lex.Scan(rd, KeywordChars); IF Text.Equal(t, "EMPTY") THEN RETURN l; ELSE RAISE E("malformed sequence value"); END; END; l.addhi(t); c := LookAhead(rd); WHILE c = ',' DO t := TextConv.Decode(ReadString(rd), quoted := FALSE); l.addhi(t); c := LookAhead(rd); END; EXCEPT Rd.Failure => RAISE E("error reading sequence value"); | Rd.EndOfFile => RAISE E("error: incomplete sequence value"); | Thread.Alerted => RAISE E("reading interrupted"); | TextConv.Fail => RAISE E("cannot decode environment: conversion error"); END; RETURN l; END ReadTextSeq;
PROCEDURE---------------------------------------------------------------------------Write (wr : Wr.T; env : T; keys : TextList.T := NIL; rec := TRUE) RAISES {E} = VAR name : TEXT; ref : REFANY; type : Type; val : TEXT; tt : TEXT; skip : BOOLEAN; BEGIN IF keys = NIL THEN keys := KeyList(env, rec); END; LOCK env.mu DO WHILE keys # NIL DO skip := FALSE; name := keys.head; type := env.typeI(name, rec); IF type = Type.Int THEN tt := "[int]"; val := TextConv.Encode(env.textValInternal(name)); ELSIF type = Type.Nat THEN tt := "[nat]"; val := TextConv.Encode(env.textValInternal(name), quoted := FALSE); ELSIF type = Type.Text THEN tt := "[text]"; val := TextConv.Encode(env.textValInternal(name), quoted := TRUE); ELSIF type = Type.List THEN tt := "[list]"; IF env.env.get(name, ref) THEN val := TextListRepr(NARROW(ref, TextList.T)); ELSE val := ""; END; ELSIF type = Type.Seq THEN tt := "[seq]"; IF env.env.get(name, ref) THEN val := TextSeqRepr(NARROW(ref, TextSeq.T)); ELSE val := ""; END; ELSIF type = Type.Ref THEN (* tt := TypeBeginChar & "ref" & TypeEndChar; *) RAISE E("arbitrary reference types are not yet implemented"); ELSE skip := TRUE; END; IF NOT skip THEN TRY Wr.PutText(wr, name & tt & "\t" & val & "\n"); EXCEPT Wr.Failure => RAISE E("write error"); | Thread.Alerted => RAISE E("interrupted writing environment"); END; END; keys := keys.tail; END; END; TRY Wr.PutText(wr, EnvSeparator & "\n"); EXCEPT Wr.Failure => RAISE E("write error"); | Thread.Alerted => RAISE E("interrupted writing environment"); END; END Write;
PROCEDURE---------------------------------------------------------------------------Read (rd : Rd.T; VAR env : T) RAISES {E} = VAR c : CHAR; t, n, vt : TEXT; vl : TextList.T; vs : TextSeq.T; vi : INTEGER; vn : CARDINAL; BEGIN TRY WHILE NOT Rd.EOF(rd) DO c := LookAhead(rd); IF c = CommentChar THEN Lex.Skip(rd, NonNewLineChars); ELSE n := TextReadingUtils.GetTokenOrString( rd, terminate := NonKeywordAsciis); IF Text.Equal(n, EnvSeparator) THEN Lex.Skip(rd); EXIT; END; c := LookAhead(rd); IF c = TypeBeginChar THEN t := TextReadingUtils.GetToken(rd); ELSE t := "[text]"; END; IF Text.Equal(t, "[text]") THEN vt := TextConv.Decode(ReadString(rd), quoted := FALSE); env.setTextVal(n, vt); ELSIF Text.Equal(t, "[int]") THEN vi := Lex.Int(rd); env.setIntVal(n, vi); ELSIF Text.Equal(t, "[nat]") THEN vn := Lex.Int(rd); env.setIntVal(n, vn); ELSIF Text.Equal(t, "[list]") THEN vl := ReadTextList(rd); env.setListVal(n, vl); ELSIF Text.Equal(t, "[seq]") THEN vs := ReadTextSeq(rd); env.setSeqVal(n, vs); ELSE RAISE E("unknown type in environment"); END; END; END; EXCEPT Lex.Error => RAISE E("lex error reading environment " & n); | Rd.Failure => RAISE E("error reading environment"); | Rd.EndOfFile => RAISE E("error: incomplete environment"); | TextConv.Fail => RAISE E("cannot decode environment: conversion error " & n); | Thread.Alerted => RAISE E("reading of environment interrupted"); | FloatMode.Trap => RAISE E("float mode trap while reading environment " & n); END; END Read;
PROCEDURE---------------------------------------------------------------------------WriteFile (fn : TEXT; env : T; keys : TextList.T := NIL; rec := TRUE) RAISES {E} = VAR wr : Wr.T; BEGIN TRY wr := FileWr.Open(fn); Write(wr, env, keys, rec); Wr.Close(wr); EXCEPT Wr.Failure => RAISE E("cannot write environment"); | OSError.E => RAISE E("cannot create or open environment file"); | Thread.Alerted => RAISE E("writing of environment interrupted"); END; END WriteFile;
PROCEDURE---------------------------------------------------------------------------ReadFile (fn : TEXT; VAR env : T) RAISES {E} = VAR rd : FileRd.T; BEGIN TRY rd := FileRd.Open(fn); WHILE NOT Rd.EOF(rd) DO Read(rd, env); END; Rd.Close(rd); EXCEPT Rd.Failure => RAISE E("error reading environment"); | OSError.E => RAISE E("cannot open environment file"); | Thread.Alerted => RAISE E("reading of environment interrupted"); END; END ReadFile;
VAR penv := ProcessEnv.Current(); nil := TextList.List1("NilList"); BEGIN END SimpleValueEnv.