-- MUMParserImpl.mesa of August 3, 1982 6:34 pm DIRECTORY MUMParser USING [], Rope USING [Ref, Int, Substr, Fetch, Size, Concat, FromString], BOPTTY USING [PutLine, PutString, PutRope, NewLine], BOPKeys USING [SP]; MUMParserImpl: PROGRAM IMPORTS Rope, BOPTTY EXPORTS MUMParser = BEGIN OPEN MUMParser, BOPTTY, BOPKeys; Empty: PROCEDURE [TP: TextPt] RETURNS [BOOLEAN] = INLINE {RETURN [TP.Length = 0]}; First: PROCEDURE [TP: TextPt] RETURNS [C: CHAR] = INLINE {RETURN [IF Empty [TP] THEN ETX ELSE Rope.Fetch [TP.Text, TP.Pos]]}; Next: PROCEDURE [TP: TextPt] RETURNS [NTP: TextPt] = INLINE {RETURN [IF Empty [TP] THEN TP ELSE [TP.Text, TP.Pos + 1, TP.Length - 1]]}; CutText: PROCEDURE [TP: TextPt, L: Rope.Int] RETURNS [NTP: TextPt] = INLINE {RETURN [[TP.Text, TP.Pos, L]]}; CatText: PROCEDURE [TPA, TPB: TextPt] RETURNS [NTP: TextPt] = INLINE {RETURN [[TPA.Text, TPA.Pos, TPB.Length + (TPB.Pos - TPA.Pos)]]}; GetRope: PROCEDURE [TP: TextPt] RETURNS [R: Rope.Ref] = INLINE {RETURN [Rope.Substr [TP.Text, TP.Pos, TP.Length]]}; Parse: PUBLIC PROCEDURE [TP: TextPt] RETURNS [EX: RefExpr, RTP: TextPt] = BEGIN [EX, RTP] _ ParseExpr [TP, MaxPrio]; RTP _ SkipBlank [RTP]; IF EX # NIL AND First [RTP] # ETX THEN SyntaxWarning [EX, RTP, "Extra characters - ignored"]; END; MaxPrio: INTEGER = 20; Priority: TYPE = INTEGER [0..MaxPrio]; ApplyPrio: Priority = 0; SyntaxMessage: PROC [EX: RefExpr, RTP: TextPt, Class, Mess: LONG STRING] = BEGIN R: Rope.Ref; Bg: Rope.Int; IF EX # NIL THEN {EXS: TextPt = EX.Source; IF RTP.Pos - EXS.Pos < 30 THEN R _ Rope.Substr [EXS.Text, EXS.Pos, RTP.Pos - EXS.Pos] ELSE R _ Rope.Substr [EXS.Text, RTP.Pos - 30, 30]}; R _ Rope.Concat [R, Rope.Concat [Rope.FromString ["<>"], Rope.Substr [RTP.Text, RTP.Pos, MIN [RTP.Length, 40 - Rope.Size [R]]]]]; NewLine []; PutLine ["---- "]; PutRope [R]; PutLine ["---- "]; PutString [Class]; PutString [Mess] END; SyntaxError: PROC [EX: RefExpr, RTP: TextPt, Mess: LONG STRING] = BEGIN SyntaxMessage [EX, RTP, "Error: ", Mess]; IF EX # NIL THEN EX.Good _ FALSE END; SyntaxWarning: PROC [EX: RefExpr, RTP: TextPt, Mess: LONG STRING] = BEGIN SyntaxMessage [EX, RTP, "Warning: ", Mess] END; ParsePrimary: PUBLIC PROCEDURE [TP: TextPt] RETURNS [EX: RefExpr, RTP: TextPt] = BEGIN RTP _ SkipBlank [TP]; [EX, RTP] _ ParseRecordExpr [RTP, Value]; IF EX # NIL THEN RETURN; [EX, RTP] _ ParseProc [RTP]; IF EX # NIL THEN RETURN; [EX, RTP] _ ParseGroup [RTP]; IF EX # NIL THEN RETURN; [EX, RTP] _ ParseString [RTP]; IF EX # NIL THEN RETURN; [EX, RTP] _ ParseNum [RTP]; IF EX # NIL THEN RETURN; [EX, RTP] _ ParseId [RTP]; IF EX # NIL THEN RETURN; [EX, RTP] _ ParseUnaryOp [RTP] END; ParseUnaryOp: PROCEDURE [TP: TextPt] RETURNS [OP: REF UnaryOp Expr, RTP: TextPt] = BEGIN C: CHAR; RTP _ SkipBlank [TP]; C _ First [RTP]; FOR Op: UnaryOp IN UnaryOp DO IF C = UnaryOpChar [Op] THEN RETURN [OP: NEW [UnaryOp Expr _ [Good: TRUE, Source: CutText [TP, 1], Body: UnaryOp [Op: Op]]], RTP: Next [TP]] ENDLOOP; RETURN [NIL, TP]; END; ParseApplyExpr: PUBLIC PROCEDURE [TP: TextPt] RETURNS [EX: RefExpr, RTP: TextPt] = BEGIN EXB: RefExpr; [EX, RTP] _ ParsePrimary [TP]; IF EX = NIL THEN RETURN [NIL, TP]; [EXB, RTP] _ ParseApplyExpr [RTP]; IF EXB # NIL THEN EX _ NEW [Expr Expr _ [Good: TRUE, Source: CatText [EX.Source, EXB.Source], Body: Expr [Op: Apply, SA: EX, SB: EXB]]] ELSE IF EX.Kind = UnaryOp THEN SyntaxError [EX, RTP, "Unary operator not followed by operand"] END; ParseExpr: PUBLIC PROCEDURE [TP: TextPt, Prio: Priority] RETURNS [EX: RefExpr, RTP: TextPt] = BEGIN EXB: RefExpr; OP: REF BinaryOp Expr; CurAss, NewAss: BOOLEAN _ TRUE; CurPrio, NewPrio: Priority _ ApplyPrio; [EX, RTP] _ ParseApplyExpr [TP]; IF EX = NIL OR Prio = ApplyPrio THEN RETURN; DO [OP, RTP] _ ParseBinaryOp [RTP, Prio - 1]; IF OP = NIL THEN RETURN; [NewPrio, NewAss] _ OpProps [OP.Op]; IF CurPrio = NewPrio THEN {IF NOT (CurAss AND NewAss) THEN SyntaxError [OP, RTP, "Mixed associative and non-associative operators"]}; CurPrio _ NewPrio; CurAss _ NewAss; [EXB, RTP] _ ParseExpr [RTP, CurPrio]; IF EXB = NIL THEN SyntaxError [OP, RTP, "Binary operator not followed by operand"]; EX _ NEW [Expr Expr _ [Good: TRUE, Source: CatText [EX.Source, IF EXB = NIL THEN OP.Source ELSE CatText [OP.Source, EXB.Source]], Body: Expr [Op: OP.Op, SA: EX, SB: EXB]]] ENDLOOP END; ParseBinaryOp: PROC [TP: TextPt, MaxP: Priority] RETURNS [OP: REF BinaryOp Expr, RTP: TextPt] = BEGIN C: CHAR; RTP _ SkipBlank [TP]; C _ First [RTP]; FOR Op: BinaryOp IN BinaryOp DO IF C = BinaryOpChar [Op] AND OpProps [Op].Priority <= MaxP THEN RETURN [OP: NEW [BinaryOp Expr _ [Good: TRUE, Source: CutText [RTP, 1], Body: BinaryOp [Op: Op]]], RTP: Next [RTP]] ENDLOOP; RETURN [NIL, TP] END; SkipBlank: PROC [TP: TextPt] RETURNS [NTP: TextPt] = {RETURN [IF First [TP] = SP THEN Next [TP] ELSE TP]}; ParseNum: PROC [TP: TextPt] RETURNS [EX: REF Int Expr, RTP: TextPt] = BEGIN C: CHAR; Base: CARDINAL [2..16]; RTP _ SkipBlank [TP]; IF First [RTP] IN ['0..'9] THEN BEGIN EX _ NEW [Int Expr _ [Good: TRUE, Source: RTP, Body: Int [Val: 0]]]; DO RTP _ Next [RTP]; IF First [RTP] NOT IN ['0..'9] THEN EXIT ENDLOOP; C _ First [RTP]; Base _ IF C = 'B OR C = 'b THEN 2 ELSE IF C = 'C OR C = 'c THEN 8 ELSE 10; FOR P: Rope.Int IN [EX.Source.Pos..RTP.Pos) DO C _ Rope.Fetch [EX.Source.Text, P]; IF EX.Good THEN {IF C NOT IN ['0..'0 + Base) THEN SyntaxError [EX, RTP, "Invalid Digit"]; IF EX.Val > (LAST [LONG CARDINAL] - (C - '0))/Base THEN SyntaxError [EX, RTP, "Number Too Large"]} ELSE EX.Val _ EX.Val * Base + (C - '0); ENDLOOP; IF Base # 10 THEN RTP _ Next [RTP]; EX.Source.Length _ RTP.Pos - EX.Source.Pos END ELSE EX _ NIL END; IsLetter: PROC [C: CHAR] RETURNS [BOOLEAN] = INLINE {RETURN [C IN ['a..'z] OR C IN ['A..'Z]]}; IsLetterDigit: PROC [C: CHAR] RETURNS [BOOLEAN] = INLINE {RETURN [C IN ['a..'z] OR C IN ['A..'Z] OR C IN ['0..'9]]}; ParseId: PROC [TP: TextPt] RETURNS [EX: REF Id Expr, RTP: TextPt] = BEGIN C: CHAR; RTP _ SkipBlank [TP]; IF IsLetter [First [RTP]] THEN BEGIN EX _ NEW [Id Expr _ [Good: TRUE, Source: RTP, Body: Id [Chars: ]]]; DO RTP _ Next [RTP]; IF NOT IsLetterDigit [First [RTP]] THEN EXIT ENDLOOP; EX.Source.Length _ RTP.Pos - EX.Source.Pos; EX.Chars _ GetRope [EX.Source]; END ELSE EX _ NIL END; ParseString: PROC [TP: TextPt] RETURNS [EX: REF String Expr, RTP: TextPt] = BEGIN C: CHAR; RTP _ SkipBlank [TP]; IF First [RTP] = '" THEN BEGIN BegChunk: Rope.Int; EX _ NEW [String Expr _ [Good: TRUE, Source: RTP, Body: String [Chars: NIL]]]; DO RTP _ Next [RTP]; BegChunk _ RTP.Pos; WHILE NOT Empty [RTP] AND First [RTP] # '" DO RTP _ Next [RTP] ENDLOOP; IF Empty [RTP] THEN SyntaxError [EX, RTP, "Missing final quote"]; EX.Chars _ Rope.Concat [EX.Chars, Rope.Substr [TP.Text, BegChunk, RTP.Pos - BegChunk]]; RTP _ Next [RTP]; RTP _ SkipBlank [RTP]; IF First [RTP] # '" THEN EXIT ENDLOOP; EX.Source.Length _ EX.Source.Pos - RTP.Pos END ELSE EX _ NIL END; ValueOrMold: TYPE = {Value, Mold}; ParseRecordExpr: PROC [TP: TextPt, Usage: ValueOrMold] RETURNS [EX: REF RecExpr Expr, RTP: TextPt] = BEGIN C: CHAR; Found: BOOLEAN; RTP _ SkipBlank [TP]; IF First [RTP] = '[ THEN BEGIN T, LR: LIST OF FieldExpr _ NIL; Field: FieldExpr; RTPId: TextPt; EX _ NEW [RecExpr Expr _ [Good: TRUE, Source: RTP, Body: RecExpr [LS: NIL]]]; RTP _ Next [RTP]; DO [Field.Name, RTPId] _ ParseId [RTP]; RTPId _ SkipBlank [RTPId]; C _ First [RTPId]; IF C = ': THEN RTP _ Next [RTPId] ELSE IF C = '] OR C = ', OR C = '! THEN IF Usage = Mold THEN RTP _ RTPId ELSE Field.Name _ NIL ELSE Field.Name _ NIL; [Field.Val, RTP] _ ParseExpr [RTP]; RTP _ SkipBlank [RTP]; IF First [RTP] = '! THEN [Field.Mold, RTP] _ ParseMold [Next [RTP]]; LR _ CONS [Field, LR]; RTP _ SkipBlank [RTP]; IF First [RTP] # ', THEN EXIT; RTP _ Next [RTP] ENDLOOP; RTP _ SkipBlank [RTP]; EX.Source.Length _ RTP.Pos - EX.Source.Pos; IF First [RTP] = '] THEN {EX.Source.Length _ EX.Source.Length + 1; RTP _ Next [RTP]} ELSE {SyntaxError [EX, RTP, "Right Bracket inserted"]}; WHILE LR # NIL DO T _ EX.LS; EX.LS _ LR; LR _ LR.rest; EX.LS.rest _ T ENDLOOP END ELSE EX _ NIL END; ParseGroup: PROC [TP: TextPt] RETURNS [EX: RefExpr, RTP: TextPt] = BEGIN TP _ SkipBlank [TP]; IF First [TP] = '( THEN BEGIN RTP _ Next [RTP]; [EX, RTP] _ ParseExpr [RTP, MaxPrio]; IF EX = NIL THEN {EX _ NEW [Id Expr _ [Good: FALSE, Source: TP, Body: Id [Chars: NIL]]]; SyntaxError [EX, RTP, "Expression expected"]}; RTP _ SkipBlank [RTP]; IF First [RTP] = ') THEN RTP _ Next [RTP] ELSE SyntaxError [EX, RTP, "Right parenthesis inserted"]; EX.Source.length _ RTP.Pos - EX.Source.Pos; END ELSE {EX _ NIL; RTP _ TP} END; ParseMold: PROC [TP: TextPt] RETURNS [EX: RefExpr, RTP: TextPt] = BEGIN [EX, RTP] _ ParseId [TP]; IF EX # NIL THEN RETURN; [EX, RTP] _ ParseRecordExpr [TP, Mold] END; ParseProc: PROC [TP: TextPt] RETURNS [EX: REF Proc Expr, RTP: TextPt] = BEGIN EX _ NIL; END; ParseChar: PUBLIC PROCEDURE [TP: TextPt, C: CHAR] RETURNS [Found: BOOLEAN, RTP: TextPt] = BEGIN RTP _ SkipBlank [TP]; IF First [RTP] = C THEN RETURN [TRUE, Next [RTP]] ELSE RETURN [FALSE, RTP] END; END... N-- MUMBLE PARSER IMPLEMENTATION - - - - - - - - - - - - - - - - - - - - - - - - ParserImpl -- To do: revise for viewers -- ApplyPrio is the priority of the "application" operator, which applies an unary operator or function to an expression, and is denoted by simple justaposition of the two. -- Notifies the user that an error or warning has been found in the object EX or in the remaining text RTP. Assumes EX.Source.Length may be still undefined, and uses RTP.Pos - EX.Source.Pos instead. -- Notifies the user of a grave syntax error. The Good flag of EX is set to FALSE. -- Prints a syntax warning. -- Parses a i.e. an identifier, number, string, unary operator or closed expression (delimited by (), [] or {}). Returns NIL if not found. -- Parses an i.e. an which is zero or more unary operators or primaries applied to a primary. Returns NIL if not found. -- Parses a i.e. an whose outermost binary operator has priority Prio. Returns NIL if not found. -- Parses an isolated character C, ignoring blanks. Returns NIL if not found. Ê– "Mesa" style˜šÑbcoZ™ZJ˜—šÏc0œ˜1JšÐchÐbcž™—J˜JšÏk œ ¡œ ¡œ8¡œ¡œ3¡œ¡œ˜®Jšœ¡œ¡œ¡œ¡œ ¡œ¡œ ¡œ ˜jIprocšÏnœ¡ œ¡œ ¡œ¡œ¡œ¡œ¡œ˜TKš¢œ¡ œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ ¡œ¡œ˜ŠKš¢œ¡ œ¡œ ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ ¡œ˜”Kš¢œ¡ œ¡œ ¡œ ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ˜nKš¢œ¡ œ¡œ¡œ ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ ˜ˆKš¢œ¡ œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ˜uKš.¢œ¡œ¡ œ¡œ ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ#¡œ˜„šœ ¡œ¡œ¡œ˜=JšÐco  £ ™¬—šœ˜Jš £K £ £  £" £  £ ™Ç—šL¢ œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œÏoÐko¡œ ¡œ ¡œ¡œ¡œ¡œ¤¥¡œ¡œ ¡œ¡œ¡œ¡œ¡œI¡œ¡œ¡œ¡œ¡œ7¡œ@¡œ˜âJš£3 £  £  £™S—š ¢ œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ˜£Jš£™—š¢ œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ˜Jš £l £ £ £  £™•—Kš|¢ œ¡œ¡ œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ˜‰š@¢ œ¡ œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ ¡œ ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ-¡œ)¡œF¡œ¡œ¡œ¡œ¡œ¡œ¡œ˜ÓJš£  £h £™—šT¢œ¡œ¡ œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ&¡œ$¡œ ¡œO¡œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ=¡œ˜ËJš£  £C £  £™t—Kšˆ¢ œ¡œ¡ œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ$¡œ¡œ3¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ&¡œ ¡œ¡œ ¡œ¡œ ¡œ ¡œ¡œ¡œh¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ4¡œ¡œ"¡œ!¡œ(¡œ¡œ¡œ¡œ¡œ(¡œ ¡œ ¡œ)¡œ¡œ¡œ¡œ¡œ ¡œ¡œ˜àKšB¢ œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ-¡œ'¡œE¡œ¡œ ¡œ¡œ¡œ¡œ¡œ˜ƒKš¢ œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ˜nKš¶¢œ¡œ¡œ ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ ¡œ¡œ¡œ ¡œ ¡œ ¡œ ¡œ ¡œ¡œ¡œ¡œ ¡œ¡œ ¡œ ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ ¡œ¡œ ¡œ¡œ ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ!¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ"¡œ ¡œ¡œ¡œ¡œ ¡œ ¡œ¡œ ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ˜ÂKš¢œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ ˜`Kš ¢ œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ ¡œ¡œ¡œ ˜vKšN¢œ¡œ¡œ ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ ¡œ ¡œ ¡œ ¡œ ¡œ¡œ¡œ¡œ¡œ ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ˜ãKšx¢ œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ%¡œ¡œ¡œ ¡œ¡œ ¡œ ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ ¡œ ¡œ ¡œ¡œ¡œ¡œ¡œ$¡œ¡œ¡œ¡œ¡œ ¡œ ¡œ¡œ ¡œ¡œ¡œ¡œ ¡œ ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ˜ÒJšœ ¡œ˜"KšÚ¢œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ ¡œ;¡œ¡œ¡œ ¡œ¡œ¡œ ¡œ ¡œ ¡œ*¡œ2¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ ¡œ¡œ ¡œ¡œ¡œ¡œ ¡œ ¡œ ¡œ ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ ¡œ ¡œ ¡œ¡œ¡œ&¡œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ˜ü Kšb¢ œ¡œ¡œ ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ"¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ˜ÄKš&¢ œ¡œ¡œ ¡œ¡œ ¡œ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ ¡œ˜²š¢ œ¡œ¡œ ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ˜dJš£  £ £™N—Kš0¢ œ¡œ¡ œ¡œ ¡œ¡œ¡œ ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ¡œ˜æJš¡œ˜—…—*Ò>?