<<>> <> <> <> DIRECTORY BasicTime, FS, IO, IOClasses, MakeDo, MakeDoParsers, RefTab, Rope, SymTab; MakeDoParsersImpl: CEDAR MONITOR IMPORTS FS, IO, IOClasses, MakeDo, RefTab, Rope, SymTab EXPORTS MakeDoParsers = BEGIN OPEN MakeDo, MakeDoParsers; GotoSyntaxError: ERROR = CODE; parses: RefTab.Ref ~ RefTab.Create[]; EnumerateWithSuffix: PUBLIC PROC [table: SymTab.Ref, suffix: ROPE, consume: PROC [ROPE]] ~ { Pass: PROC [key: ROPE, val: REF ANY] RETURNS [quit: BOOL _ FALSE] --SymTab.EachPairAction-- ~ { consume[key.Concat[suffix]]; RETURN [FALSE]}; IF table.Pairs[Pass] THEN ERROR; RETURN}; FlushCache: PUBLIC ENTRY PROC ~ { ENABLE UNWIND => NULL; parses.Erase[]; RETURN}; GetParseData: PUBLIC ENTRY PROC [sourceNode: Node, sourceType: SourceType] RETURNS [ParseData] ~ { ENABLE UNWIND => NULL; RETURN InnerGetParseData[sourceNode, sourceType]}; InnerGetParseData: INTERNAL PROC [sourceNode: Node, sourceType: SourceType] RETURNS [pd: ParseData] ~ { NoteDep: PROC [fileName: ROPE] = {NoteConfigDep[fileName, FALSE]}; NoteCDep: PROC [fileName: ROPE] = {NoteConfigDep[fileName, TRUE]}; NoteConfigDep: PROC [fileName: ROPE, isStaticRequest: BOOL] = { new: BOOL ~ SymTab.Store[IF isStaticRequest THEN pd.refdFiles ELSE pd.refdModules, fileName, $T]; IF new AND isStaticRequest THEN pd.refdFileSeq _ CONS[fileName, pd.refdFileSeq]; RETURN}; cur: Time; exists: BOOL; sourceName: ROPE; pd _ NARROW[parses.Fetch[sourceNode].val]; IF pd=NIL THEN { pd _ NEW [ParseDataRep _ [ source: sourceNode, sourceType: sourceType, refdModules: SymTab.Create[case: FALSE], refdFiles: SymTab.Create[case: FALSE] ]]; IF NOT parses.Insert[sourceNode, pd] THEN ERROR}; IF sourceType # pd.sourceType THEN ERROR; cur _ InnerGetCreated[sourceNode]; IF pd.stamp=cur THEN RETURN [IF pd.stamp#notExistTime THEN pd ELSE NIL]; pd.resultType _ Unknown; pd.refdModules.Erase[]; pd.refdFiles.Erase[]; pd.refdFileSeq _ NIL; pd.stamp _ cur; IF pd.stamp=notExistTime THEN RETURN [NIL]; sourceName _ sourceNode.PublicPartsOfNode[].name; SELECT pd.sourceType FROM Unknown => pd.resultType _ Unknown; Mesa => {isDefs: BOOL; [exists, isDefs] _ EnumerateMesaDependancies[sourceName, NIL, NoteDep]; pd.resultType _ IF isDefs THEN MobOnly ELSE MobAndC}; Config => { exists _ EnumerateConfigDependancies[sourceName, NIL, NoteConfigDep]; pd.resultType _ MobAndC}; PlainC => { exists _ EnumerateCFileDependancies[sourceName, NoteCDep]; pd.resultType _ O}; Scheme => { exists _ TRUE; [] _ FS.FileInfo[sourceName !FS.Error => {exists _ FALSE; CONTINUE}]; pd.resultType _ SxCAndO}; Cluster => { exists _ EnumerateClusterDependancies[sourceName, NoteCDep]; pd.resultType _ SxCAndO}; ENDCASE => ERROR; IF NOT exists THEN {pd.resultType _ Unknown; RETURN [NIL]}; RETURN [pd]}; ClusterBreak: PROC [char: CHAR] RETURNS [IO.CharClass] ~ { SELECT char FROM <= ' => RETURN [sepr]; '(, '), ';, '" => RETURN [break]; ENDCASE => RETURN [other]}; EnumerateClusterDependancies: PROC [sourceName: ROPE, consume: PROC [fileName: ROPE]] RETURNS [exists: BOOLEAN] ~ { source: IO.STREAM _ NIL; Token: TYPE ~ RECORD [k: {n, q} _ n, r: ROPE]; Equal: PROC [t: Token, r: ROPE] RETURNS [BOOL] ~ INLINE {RETURN [t.k=n AND t.r.Equal[r, FALSE]]}; GetToken: PROC RETURNS [Token] ~ { DO --until we've passed the comments c: CHAR; DO --pass whitespace c _ source.GetChar[]; IF NOT c IN [0C .. ' ] THEN EXIT; ENDLOOP; SELECT c FROM '( => RETURN [[n, "("]]; ') => RETURN [[n, ")"]]; '; => [] _ source.GetLineRope[]; '" => {source.Backup[c]; RETURN [[q, source.GetRopeLiteral[]]]}; ENDCASE => {source.Backup[c]; RETURN [[n, source.GetTokenRope[ClusterBreak].token]]}; ENDLOOP; }; ParseCluster: PROC ~ { op: Token ~ GetToken[]; decl: Token ~ GetToken[]; IF NOT (Equal[op, "("] AND Equal[decl, "declare-cluster"]) THEN GotoSyntaxError; DO next: Token ~ GetToken[]; SELECT TRUE FROM Equal[next, ")"] => EXIT; Equal[next, "("] => { kind: Token ~ GetToken[]; xs: RECORD [pre, post: ROPE] ~ SELECT TRUE FROM Equal[kind, "passive"] => [NIL, ".o"], Equal[kind, "cedar"], Equal[kind, "cedar-sun4"] => ["sun4/", ".c2c.o"], Equal[kind, "cedar-sun4o3"] => ["sun4-o3/", ".c2c.o"], Equal[kind, "provide"], Equal[kind, "assume"] => ["", ".sx.o"], ENDCASE => ERROR GotoSyntaxError[]; DO elt: Token ~ GetToken[]; SELECT TRUE FROM elt.k=q => consume[Rope.Cat[xs.pre, elt.r, xs.post]]; Equal[elt, ")"] => EXIT; ENDCASE => GotoSyntaxError; ENDLOOP; }; next.k=q => consume[next.r.Concat[".sx.o"]]; ENDCASE => GotoSyntaxError; ENDLOOP; RETURN}; source _ FS.StreamOpen[fileName: sourceName, accessOptions: $read !FS.Error => {source _ NIL; CONTINUE}]; exists _ source # NIL; IF NOT exists THEN RETURN; [] _ source.GetIndex[]; ParseCluster[! GotoSyntaxError => { SIGNAL Warning[IO.PutFR["Syntax error in %g; parse aborted at %g", IO.rope[sourceName], [integer[source.GetIndex[]]]]]; CONTINUE}; IO.EndOfStream => { SIGNAL Warning[IO.PutFR["Premature end-of-file in %g; parse aborted at %g", IO.rope[sourceName], [integer[source.GetIndex[]]]]]; CONTINUE}]; source.Close[!IO.Error => CONTINUE]; RETURN}; EnumerateMesaDependancies: PROC [sourceName, symTail: ROPE, consume: PROC [fileName: ROPE]] RETURNS [exists, isDefs: BOOLEAN] = BEGIN Consume: PROC [fileName: ROPE] ~ { consume[fileName.Cat[symTail]]; -- use .MOB -- RETURN; }; NextToken: PROC RETURNS [ROPE] = {RETURN [source.GetTokenRope[Break].token]}; ParseDirectory: PROC RETURNS [next: ROPE] = BEGIN firstClause: BOOL _ TRUE; ParseClause: PROC RETURNS [next: ROPE] = BEGIN fileName: ROPE _ "?"; next _ NextToken[]; IF firstClause THEN {firstClause _ FALSE; IF next.Equal[";"] THEN RETURN}; IF NOT Letter[next.Fetch[0]] THEN ERROR GotoSyntaxError; fileName _ next; next _ NextToken[]; IF next.Equal[":"] THEN BEGIN next _ NextToken[]; IF next.Equal["TYPE"] THEN { next _ NextToken[]; IF Letter[next.Fetch[0]] AND NOT next.Equal["USING"] THEN next _ NextToken[]; } ELSE IF next.Equal["FROM"] THEN { fileName _ source.GetRopeLiteral[]; next _ NextToken[]; } ELSE ERROR GotoSyntaxError; END; IF next.Equal["USING"] THEN BEGIN IF NOT (next _ NextToken[]).Equal["["] THEN ERROR GotoSyntaxError; WHILE NOT (next _ NextToken[]).Equal["]"] DO NULL ENDLOOP; next _ NextToken[]; END; Consume[fileName]; END; IF (next _ NextToken[]).Equal["DIRECTORY"] THEN BEGIN DO IF (next _ ParseClause[]).Equal[";"] THEN EXIT; IF NOT next.Equal[","] THEN ERROR GotoSyntaxError; ENDLOOP; next _ NextToken[]; END; IF NOT Letter[next.Fetch[0]] THEN ERROR GotoSyntaxError; END; ParseMesaHead: PROC RETURNS [next: ROPE] ~ { next _ NextToken[]; IF NOT next.Equal[":"] THEN ERROR GotoSyntaxError; next _ NextToken[]; IF next.Equal["CEDAR"] THEN next _ NextToken[]; SELECT TRUE FROM next.Equal["DEFINITIONS"] => isDefs _ TRUE; next.Equal["PROGRAM"] => isDefs _ FALSE; next.Equal["MONITOR"] => isDefs _ FALSE; ENDCASE => ERROR GotoSyntaxError; }; ParseDirectoryAndHead: PROC ~ { [] _ ParseDirectory[]; [] _ ParseMesaHead[]; RETURN}; source: IO.STREAM _ NIL; source _ FS.StreamOpen[fileName: sourceName, accessOptions: $read !FS.Error => {source _ NIL; CONTINUE}]; exists _ source # NIL; isDefs _ TRUE; IF NOT exists THEN RETURN; source _ IOClasses.CreateCommentFilterStream[source]; [] _ source.GetIndex[]; ParseDirectoryAndHead[! GotoSyntaxError => { SIGNAL Warning[IO.PutFR["Syntax error in %g; parse aborted at %g", IO.rope[sourceName], [integer[source.GetIndex[]]]]]; CONTINUE}; IO.EndOfStream => { SIGNAL Warning[IO.PutFR["Premature end-of-file in %g; parse aborted at %g", IO.rope[sourceName], [integer[source.GetIndex[]]]]]; CONTINUE}]; source.Close[!IO.Error => CONTINUE]; RETURN; END; EnumerateConfigDependancies: PROC [sourceName, symTail: ROPE, consume: PROC [fileName: ROPE, isStaticRequest: BOOL]] RETURNS [exists: BOOLEAN] = { locals: LIST OF LIST OF ROPE _ NIL; AddDef: PROC [name: ROPE] = {locals.first _ CONS[name, locals.first]}; MaybeConsume: PROC [moduleName: ROPE] = { FOR l: LIST OF LIST OF ROPE _ locals, l.rest WHILE l # NIL DO FOR m: LIST OF ROPE _ l.first, m.rest WHILE m # NIL DO IF m.first.Equal[moduleName] THEN RETURN; ENDLOOP; ENDLOOP; Consume[moduleName, FALSE]}; Consume: PROC [fileName: ROPE, isStatic: BOOL] ~ { consume[fileName.Cat[symTail], isStatic]; -- use .MOB -- RETURN; }; ConsumeSimple: PROC [fileName: ROPE, isStatic: BOOL] ~ { consume[fileName, isStatic]; -- use fileName directly -- NULL; }; NextToken: PROC RETURNS [ROPE] = {RETURN [source.GetTokenRope[Break].token]}; ParseConfigDescription: PROC = { next: ROPE _ ParseCDirectory[]; next _ ParseCPacking[next]; next _ ParseConfiguration[next]; IF NOT next.Equal["."] THEN ERROR GotoSyntaxError; }; ParseConfiguration: PROC [first: ROPE] RETURNS [next: ROPE] = { IF NOT Letter[first.Fetch[0]] THEN ERROR GotoSyntaxError; IF NOT (next _ NextToken[]).Equal[":"] THEN ERROR GotoSyntaxError; IF NOT IsConfig[next _ NextToken[]] THEN ERROR GotoSyntaxError; next _ ParseConfigurationRemains[]; }; ParseConfigurationRemains: PROC RETURNS [next: ROPE] = { next _ ParseCHeadRemains[]; IF NOT (next.Equal["="] OR next.Equal["~"]) THEN ERROR GotoSyntaxError; ParseCBody[]; next _ NextToken[]; }; ParseCBody: PROC = { next: ROPE _ NextToken[]; curly: BOOLEAN; IF next.Equal["BEGIN"] THEN curly _ FALSE ELSE IF next.Equal["{"] THEN curly _ TRUE ELSE ERROR GotoSyntaxError; locals _ CONS[NIL, locals]; next _ NextToken[]; DO semiSeen: BOOL _ FALSE; next _ ParseCStatement[next]; WHILE next.Equal[";"] DO next _ NextToken[]; semiSeen _ TRUE; ENDLOOP; IF next.Equal[IF curly THEN "}" ELSE "END"] THEN EXIT; IF NOT semiSeen THEN ERROR GotoSyntaxError; ENDLOOP; locals _ locals.rest; }; ParseCStatement: PROC [first: ROPE] RETURNS [next: ROPE] = { next _ first; IF next.Equal["["] THEN BEGIN next _ ParseItemList[FALSE]; IF NOT next.Equal["]"] THEN ERROR GotoSyntaxError; next _ NextToken[]; IF NOT next.Equal["_"] THEN ERROR GotoSyntaxError; next _ ParseCExpression[]; END ELSE BEGIN lhs, rhs: ROPE; named: BOOL _ FALSE; lhs _ rhs _ next; IF named _ (next _ NextToken[]).Equal[":"] THEN BEGIN IF IsConfig[rhs _ NextToken[]] THEN { AddDef[lhs]; next _ ParseConfigurationRemains[]; RETURN}; next _ NextToken[]; END; IF next.Equal["_"] THEN BEGIN AddDef[lhs]; next _ ParseCExpression[]; END ELSE BEGIN IF named THEN AddDef[lhs]; MaybeConsume[rhs]; IF next.Equal["["] THEN BEGIN IF NOT (next _ NextToken[]).Equal["]"] THEN next _ EatIDList[next, FALSE]; IF NOT next.Equal["]"] THEN ERROR GotoSyntaxError; next _ ParseCLinks[]; END ELSE IF next.Equal["LINKS"] THEN { SIGNAL Warning[IO.PutFR["[] missing before %g in %g", IO.int[source.GetIndex[]], IO.rope[sourceName]]]; next _ ParseCLinks[next]; }; END; END; }; ParseCExpression: PROC RETURNS [next: ROPE] = { next _ ParseCRightSide[]; WHILE next.Equal["PLUS"] DO next _ ParseCRightSide[]; ENDLOOP; WHILE next.Equal["THEN"] DO next _ ParseCRightSide[]; ENDLOOP; }; ParseCRightSide: PROC RETURNS [next: ROPE] = { next _ ParseItem[NIL, TRUE]; IF next.Equal["["] THEN BEGIN IF NOT (next _ NextToken[]).Equal["]"] THEN next _ EatIDList[next, FALSE]; IF NOT next.Equal["]"] THEN ERROR GotoSyntaxError; next _ ParseCLinks[]; END; }; ParseCHeadRemains: PROC RETURNS [next: ROPE] = { next _ ParseCLinks[]; next _ ParseImports[next]; next _ ParseCExports[next]; next _ ParseStatics[next]; next _ ParseDynamics[next]; next _ ParseControlClause[next]; }; ParseCLinks: PROC [first: ROPE _ NIL] RETURNS [next: ROPE] = { next _ IF first # NIL THEN first ELSE NextToken[]; IF NOT next.Equal["LINKS"] THEN RETURN; IF NOT (next _ NextToken[]).Equal[":"] THEN ERROR GotoSyntaxError; next _ NextToken[]; IF NOT (next.Equal["CODE"] OR next.Equal["FRAME"]) THEN ERROR GotoSyntaxError; next _ NextToken[]; }; ParseImports: PROC [first: ROPE] RETURNS [next: ROPE] = { IF NOT first.Equal["IMPORTS"] THEN RETURN [first]; next _ ParseItemList[FALSE]; }; ParseCExports: PROC [first: ROPE] RETURNS [next: ROPE] = { IF NOT first.Equal["EXPORTS"] THEN RETURN [first]; next _ ParseItemList[FALSE]; }; ParseStatics: PROC [first: ROPE] RETURNS [next: ROPE] = { IF NOT first.Equal["STATIC"] THEN RETURN [first]; IF NOT (next _ NextToken[]).Equal["REQUESTS"] THEN ERROR GotoSyntaxError; next _ ParseStringList[TRUE]; }; ParseDynamics: PROC [first: ROPE] RETURNS [next: ROPE] = { IF NOT first.Equal["DYNAMIC"] THEN RETURN [first]; IF NOT (next _ NextToken[]).Equal["REQUESTS"] THEN ERROR GotoSyntaxError; next _ ParseStringList[FALSE]; }; ParseControlClause: PROC [first: ROPE] RETURNS [next: ROPE] = { IF NOT first.Equal["CONTROL"] THEN RETURN [first]; next _ EatIDList[NIL, FALSE]; }; ParseItemList: PROC [notice: BOOLEAN] RETURNS [next: ROPE] = { DO next _ ParseItem[NIL, notice]; IF NOT next.Equal[","] THEN EXIT; ENDLOOP; }; ParseItem: PROC [first: ROPE, notice: BOOLEAN] RETURNS [next: ROPE] = { lhs, rhs: ROPE; named: BOOL _ FALSE; lhs _ rhs _ IF first = NIL THEN NextToken[] ELSE first; IF named _ (next _ NextToken[]).Equal[":"] THEN BEGIN rhs _ NextToken[]; next _ NextToken[]; END; IF notice THEN { IF named THEN AddDef[lhs]; MaybeConsume[rhs]}; }; ParseStringList: PROC [notice: BOOLEAN] RETURNS [next: ROPE] = { DO next _ ParseString[notice]; IF NOT next.Equal[","] THEN EXIT; ENDLOOP; }; ParseString: PROC [notice: BOOLEAN] RETURNS [next: ROPE] = { string: ROPE; string _ source.GetRopeLiteral[ !IO.Error => ERROR GotoSyntaxError]; next _ NextToken[]; IF notice THEN ConsumeSimple[string, TRUE]; }; ParseCDirectory: PROC RETURNS [next: ROPE] = { firstClause: BOOL _ TRUE; ParseClause: PROC RETURNS [next: ROPE] = { moduleName: ROPE _ "?"; fileName: ROPE _ "?"; next _ NextToken[]; IF firstClause THEN {firstClause _ FALSE; IF next.Equal[";"] THEN RETURN}; IF NOT Letter[next.Fetch[0]] THEN ERROR GotoSyntaxError; moduleName _ fileName _ next; next _ NextToken[]; IF next.Equal[":"] THEN BEGIN next _ NextToken[]; IF next.Equal["TYPE"] THEN { next _ NextToken[]; IF Letter[next.Fetch[0]] THEN next _ NextToken[]; } ELSE IF next.Equal["FROM"] THEN { fileName _ source.GetRopeLiteral[]; next _ NextToken[]; } ELSE ERROR GotoSyntaxError; END; Consume[fileName, FALSE]; AddDef[moduleName]; }; locals _ CONS[NIL, locals]; IF NOT (next _ NextToken[]).Equal["DIRECTORY"] THEN RETURN; DO IF (next _ ParseClause[]).Equal[";"] THEN EXIT; IF NOT next.Equal[","] THEN ERROR GotoSyntaxError; ENDLOOP; next _ NextToken[]; }; ParseCPacking: PROC [first: ROPE] RETURNS [next: ROPE] = { next _ first; WHILE next.Equal["PACK"] DO next _ EatIDList[NIL, FALSE]; IF NOT next.Equal[";"] THEN ERROR GotoSyntaxError; next _ NextToken[]; ENDLOOP; }; EatIDList: PROC [first: ROPE, notice: BOOLEAN] RETURNS [next: ROPE] = { next _ IF first = NIL THEN NextToken[] ELSE first; DO IF NOT Letter[next.Fetch[0]] THEN ERROR GotoSyntaxError; IF notice THEN Consume[next, FALSE]; next _ NextToken[]; IF NOT next.Equal[","] THEN EXIT; next _ NextToken[]; ENDLOOP; }; source: IO.STREAM _ NIL; source _ FS.StreamOpen[fileName: sourceName, accessOptions: $read !FS.Error => {source _ NIL; CONTINUE}]; exists _ source # NIL; IF NOT exists THEN RETURN; source _ IOClasses.CreateCommentFilterStream[source]; [] _ source.GetIndex[]; ParseConfigDescription[! GotoSyntaxError => { SIGNAL Warning[IO.PutFR["Syntax error in %g; parse aborted at %g", [rope[sourceName]], [integer[source.GetIndex[]]]]]; source.Close[]; GOTO Done }; IO.EndOfStream => { SIGNAL Warning[IO.PutFR["Premature end-of-file in %g; parse aborted at %g", IO.rope[sourceName], [integer[source.GetIndex[]]]]]; source.Close[]; GOTO Done }; ]; EXITS Done => NULL; }; EnumerateCFileDependancies: PROC [sourceName: ROPE, consume: PROC [fileName: ROPE]] RETURNS [exists: BOOLEAN] ~ { <> <<>> <> <> <> ParseCFile: PROC = { <> < ERROR GotoSyntaxError];>> <<--- repeat>> <> <> <> <> <> <> <> <> <> <<>> IncludeSep: ROPE ~ """<"; WhiteSpace: ROPE ~ " "; -- that is a tab and a blank -- WhiteSpaceOrSep: ROPE ~ "\t ""<"; SharpSign: CHAR ~ '#; IncludeString: ROPE ~ "include"; Consume: PROC [string: ROPE, sepChar: CHAR] ~ { IF sepChar # '" THEN RETURN; IF string.SkipTo[0, "/"] # string.Length[] THEN RETURN; consume[string]; -- use name directly; it contains the extension -- }; WHILE NOT IO.EndOf[source] DO <> currentLine: ROPE; lineLength: INT; currentLine _ IO.GetLineRope[source]; IF currentLine = NIL THEN LOOP; lineLength _ currentLine.Length[]; WHILE currentLine.Fetch[lineLength-1] = '\\ DO currentLine _ currentLine.Cat[IO.GetLineRope[source ! IO.EndOfStream => ERROR GotoSyntaxError]]; lineLength _ currentLine.Length[]; ENDLOOP; IF currentLine.Fetch[0] = SharpSign THEN { tokStart, tokEnd: INT; tokStart _ currentLine.SkipOver[1, WhiteSpace]; IF tokStart = lineLength THEN LOOP; tokEnd _ currentLine.SkipTo[tokStart+1, WhiteSpaceOrSep]; IF NOT Rope.Equal[currentLine.Substr[tokStart, tokEnd-tokStart], IncludeString] THEN LOOP; BEGIN ENABLE {IO.EndOfStream => ERROR GotoSyntaxError}; matchSep: ROPE; sepChar: CHAR; tokStart _ currentLine.SkipTo[tokEnd, IncludeSep]; sepChar _ currentLine.Fetch[tokStart]; matchSep _ IF sepChar = '" THEN """" ELSE ">"; tokEnd _ currentLine.SkipTo[tokStart+1, matchSep]; Consume[currentLine.Substr[tokStart+1, tokEnd-(1+tokStart)], sepChar]; END; }; ENDLOOP; }; source: IO.STREAM _ NIL; source _ FS.StreamOpen[fileName: sourceName, accessOptions: $read !FS.Error => {source _ NIL; CONTINUE}]; exists _ source # NIL; IF NOT exists THEN RETURN; { ParseCFile[! GotoSyntaxError => { SIGNAL Warning[IO.PutFR["Syntax error in %g", [rope[sourceName]]]]; <> GOTO Done }; IO.EndOfStream => { SIGNAL Warning[IO.PutFR["Premature end-of-file in %g; parse aborted at %g", IO.rope[sourceName], [integer[source.GetIndex[]]]]]; GOTO Done }; ]; EXITS Done => NULL; }; source.Close[]; }; IsConfig: PROC [word: ROPE] RETURNS [is: BOOLEAN] = {is _ word.Equal["CONFIGURATION"] OR word.Equal["CONFIG"]}; Break: PROC [char: CHAR] RETURNS [IO.CharClass] --IO.BreakProc-- = {RETURN [SELECT char FROM IN ['a .. 'z], IN ['A .. 'Z], IN ['0 .. '9] => other, IO.SP, '\n, '\r, '\l, IO.TAB, IO.LF, IO.FF => sepr, ENDCASE => break]}; Letter: PROC [c: CHAR] RETURNS [letter: BOOLEAN] = INLINE {letter _ (c IN ['a .. 'z]) OR (c IN ['A .. 'Z])}; END.