CDMakeProcCommands.mesa (module for ChipNDale)
Copyright © 1985 by Xerox Corporation. All rights reserved.
by Christian Jacobi, May 9, 1985 1:58:40 pm PDT
last edited Christian Jacobi, March 25, 1986 6:27:17 pm PST
DIRECTORY
Ascii,
Atom,
CD,
CDAtomicObjects,
CDCells,
CDCommandOps,
CDDirectory,
CDMakeProcPrivate,
CDMenus,
CDProperties,
CDOrient,
CDSequencer,
CDSymbolicObjects,
Convert,
IO,
RefTab,
Rope,
SymTab,
TerminalIO,
TiogaFileOps,
TiogaOps,
ViewerTools;
CDMakeProcCommands: CEDAR PROGRAM
IMPORTS Ascii, Atom, Convert, CD, CDAtomicObjects, CDCells, CDCommandOps, CDDirectory, CDMenus, CDProperties, CDOrient, CDSequencer, CDSymbolicObjects, IO, RefTab, Rope, SymTab, TerminalIO, TiogaFileOps, TiogaOps, ViewerTools
EXPORTS CDMakeProcPrivate =
BEGIN
Environment: TYPE = REF EnvironmentRep;
EnvironmentRep: PUBLIC TYPE = RECORD [
invocationKey: REF,
rootNode: TiogaFileOps.Ref,
commentNode: TiogaFileOps.Ref, --insert sibling for next comment
modHeaderNode: TiogaFileOps.Ref, --DIRECTORY... to excluding BEGIN
declNode: TiogaFileOps.Ref, --insert sibling for next declaration on module level
procNode: TiogaFileOps.Ref, --insert sibling for next global procedures
procDecNode: TiogaFileOps.Ref, --insert sibling for next local declaration
postDeclNode: TiogaFileOps.Ref, --insert sibling for next declaration on module level
firstModStatementNode: TiogaFileOps.Ref, --reserved for first statement
modStatementNode: TiogaFileOps.Ref, --insert sibling for next mudule statement
modifier: INT ← 0,
directory: SymTab.Ref,
imports: SymTab.Ref,
declaredLayers: ARRAY CD.Layer OF Rope.ROPEALL[NIL],
nameToThing: SymTab.Ref,
thingToName: RefTab.Ref,
globalThingToName: RefTab.Ref,
hasDirGen: BOOLFALSE,
properties: CD.PropList
];
ExpressionProc: TYPE = CDMakeProcPrivate.ExpressionProc;
RegisterExpressionProc: PUBLIC PROC [for: REF, ep: ExpressionProc, tech: CD.Technology ← NIL] =
BEGIN
WITH for SELECT FROM
c: CD.ObjectClass =>
CDProperties.PutProp[c.properties, $MakeCallExpression, NEW[ExpressionProc𡤎p]];
a: ATOM => RegisterExpressionProc[CD.FetchObjectClass[a, tech], ep, tech];
ENDCASE => ERROR
END;
SpecificCallExpression: PROC [env: Environment, ob: CD.Object] RETURNS [r: Rope.ROPENIL] =
BEGIN
x: REF ← CDProperties.GetProp[ob.class.properties, $MakeCallExpression];
WITH x SELECT FROM
rmce: REF ExpressionProc => r ← rmce^[env, ob]
ENDCASE => NULL;
END;
MakeNodeComment: PROC [node: TiogaFileOps.Ref] =
BEGIN
TRUSTED { TiogaOps.PutProp[LOOPHOLE[node], $Comment, NEW[BOOLEANTRUE]] };
END;
Reserve: PROC [env: Environment, key: Rope.ROPE] =
BEGIN
[] ← SymTab.Store[env.nameToThing, key, $reserved];
END;
IncludeDirectory: PUBLIC PROC [env: Environment, module: Rope.ROPE, import: BOOLTRUE] =
BEGIN
[] ← SymTab.Store[env.nameToThing, module, $module];
[] ← SymTab.Insert[env.directory, module, module];
IF import THEN [] ← SymTab.Insert[env.imports, module, module];
END;
MakeDirectory: PROC [env: Environment] =
BEGIN
r: Rope.ROPENIL;
Each: SymTab.EachPairAction =
BEGIN
IF num=0 THEN TiogaFileOps.SetContents[env.modHeaderNode, "DIRECTORY"]
ELSE r ← r.Concat[", "];
r ← r.Concat[key];
num ← num+1;
quit ← FALSE
END;
num: INT ← 0;
[] ← env.directory.Pairs[Each];
IF num>0 THEN {
node: TiogaFileOps.Ref ← TiogaFileOps.InsertNode[env.modHeaderNode, TRUE];
TiogaFileOps.SetContents[node, r.Concat[";"]];
}
ELSE TiogaFileOps.SetContents[env.modHeaderNode, ""];
END;
MakeImportsExpr: PROC [env: Environment] RETURNS [r: Rope.ROPENIL] =
BEGIN
Each: SymTab.EachPairAction =
BEGIN
IF num=0 THEN r ← "IMPORTS "
ELSE r ← r.Concat[", "];
r ← r.Concat[key];
num ← num+1;
quit ← FALSE
END;
num: INT ← 0;
[] ← env.imports.Pairs[Each];
END;
NewEnvironment: PROC [t: CD.Technology] RETURNS [env: Environment] =
BEGIN
env ← NEW[EnvironmentRep];
env.invocationKey ← NEW[INT];
env.rootNode ← TiogaFileOps.CreateRoot[];
env.commentNode ← TiogaFileOps.InsertNode[env.rootNode, TRUE];
TiogaFileOps.SetContents[env.commentNode, "--///temp/ChipNDale/temp.mesa"];
MakeNodeComment[env.commentNode];
env.commentNode ← TiogaFileOps.InsertNode[env.commentNode];
TiogaFileOps.SetContents[env.commentNode, "--created by ChipNDale"];
MakeNodeComment[env.commentNode];
env.modHeaderNode ← TiogaFileOps.InsertNode[env.commentNode];
env.declNode ← TiogaFileOps.InsertNode[env.modHeaderNode];
TiogaFileOps.SetContents[env.declNode, "BEGIN"];
GlobalDeclaration1[env, IO.PutFR["tech: CD.Technology ← CD.FetchTechnology[$%g];", IO.atom[t.key]]];
env.procNode ← TiogaFileOps.InsertNode[env.declNode];
TiogaFileOps.SetContents[env.procNode, ""];
env.postDeclNode ← TiogaFileOps.InsertNode[env.procNode];
TiogaFileOps.SetContents[env.postDeclNode, "table: CDGenerate.Table ← CDGenerate.AssertTable[""USER""];"];
env.firstModStatementNode ← env.modStatementNode ← TiogaFileOps.InsertNode[env.postDeclNode];
env.nameToThing ← SymTab.Create[];
env.directory ← SymTab.Create[];
env.imports ← SymTab.Create[];
env.thingToName ← RefTab.Create[];
env.globalThingToName ← RefTab.Create[];
END;
FinishUp: PROC [env: Environment] =
BEGIN
node: TiogaFileOps.Ref;
subNode: TiogaFileOps.Ref;
fileName: Rope.ROPE ← "///temp/ChipNDale/temp.mesa";
r: Rope.ROPENIL;
MakeDirectory[env];
node ← TiogaFileOps.InsertNode[env.modHeaderNode];
TiogaFileOps.SetContents[node, "Temp: CEDAR PROGRAM"];
subNode ← TiogaFileOps.InsertNode[node, TRUE];
TiogaFileOps.SetContents[subNode, Rope.Cat[MakeImportsExpr[env], " ="]];
r ← "[] ← table.Register[""Temp"", CreateCell];";
TiogaFileOps.SetContents[env.firstModStatementNode, r];
GlobalStatement[env, "END."];
TiogaFileOps.Store[env.rootNode, fileName];
env.rootNode ← env.commentNode ← env.modHeaderNode ← NIL;
env.declNode ← env.procNode ← env.postDeclNode ← NIL;
env.firstModStatementNode ← env.modStatementNode ← NIL;
env.directory ← env.imports ← env.nameToThing ← NIL;
env.thingToName ← NIL;
env.globalThingToName ← NIL;
TerminalIO.WriteRope[fileName];
TerminalIO.WriteRope[" created\n"];
[] ← ViewerTools.MakeNewTextViewer[info: [
file: fileName,
label: fileName,
name: fileName,
iconic: FALSE
]];
END;
MakeProcedureNode: PROC [env: Environment] RETURNS [TiogaFileOps.Ref] =
BEGIN
env.procNode ← TiogaFileOps.InsertNode[env.procNode];
env.procDecNode ← NIL;
RETURN [env.procNode]
END;
MakeDeclarationNode: PROC [env: Environment] RETURNS [TiogaFileOps.Ref] =
BEGIN
env.declNode ← TiogaFileOps.InsertNode[env.declNode];
RETURN [env.declNode]
END;
GlobalDeclaration1: PUBLIC PROC [env: Environment, line: Rope.ROPE] =
BEGIN
node: TiogaFileOps.Ref ← MakeDeclarationNode[env];
TiogaFileOps.SetContents[node, line];
END;
GlobalDeclaration2: PUBLIC PROC [env: Environment, line: Rope.ROPE] =
BEGIN
env.postDeclNode ← TiogaFileOps.InsertNode[env.postDeclNode];
TiogaFileOps.SetContents[env.postDeclNode, line];
END;
GlobalStatement: PUBLIC PROC [env: Environment, line: Rope.ROPE] =
BEGIN
env.modStatementNode ← TiogaFileOps.InsertNode[env.modStatementNode];
TiogaFileOps.SetContents[env.modStatementNode, line];
END;
MakeDirGenerator: PROC [env: Environment] =
BEGIN
IF ~env.hasDirGen THEN {
env.hasDirGen ← TRUE;
GlobalDeclaration2[env, "directory: CDGenerate.Table ← CDGenerate.AssertTable[""DIRECTORY""];"];
}
END;
RequestGlobalIdent: PUBLIC PROC[env: Environment, proposed: Rope.ROPE, whatFor: REFNIL] RETURNS [Rope.ROPE] =
BEGIN
RETURN [RequestIdent[env, proposed, whatFor, TRUE]];
END;
GlobalIdent: PUBLIC PROC [env: Environment, whatFor: REF] RETURNS [Rope.ROPE] =
BEGIN
RETURN [Ident[env, whatFor, TRUE]];
END;
RequestIdent: PROC [env: Environment, proposed: Rope.ROPENIL, whatFor: REFNIL, global: BOOL�LSE] RETURNS [r: Rope.ROPE] =
BEGIN
Trans: Rope.TranslatorType =
BEGIN
IF Ascii.Letter[old] THEN new ← old
ELSE IF Ascii.Digit[old] AND ~first THEN new ← old
ELSE new ← 'x;
first ← FALSE
END;
first: BOOLTRUE;
proposed ← Rope.Translate[base: proposed, translator: Trans];
r ← proposed;
DO
IF SymTab.Insert[env.nameToThing, r, whatFor] THEN {
IF global THEN
[] ← RefTab.Store[env.globalThingToName, whatFor, r];
[] ← RefTab.Store[env.thingToName, whatFor, r];
RETURN;
};
env.modifier ← env.modifier+1;
r ← IO.PutFR["%gx%g", IO.rope[proposed], IO.int[env.modifier]]
ENDLOOP
END;
Ident: PROC [env: Environment, whatFor: REF, global: BOOL] RETURNS [Rope.ROPENIL] =
BEGIN
tab: RefTab.Ref ← IF global THEN env.globalThingToName ELSE env.thingToName;
WITH RefTab.Fetch[tab, whatFor].val SELECT FROM
n: Rope.ROPE => RETURN [n];
ENDCASE =>NULL;
END;
IdentForLocalObject: PROC [env: Environment, ob: CD.Object] RETURNS [name: Rope.ROPE] =
BEGIN
first: CHAR;
name ← Ident[env, ob, FALSE];
IF name#NIL THEN RETURN [name];
name ← CDDirectory.Name[ob];
IF Rope.IsEmpty[name] THEN {
name ← Atom.GetPName[ob.class.objectType]
};
first ← Rope.Fetch[name];
IF Ascii.Letter[first] THEN first ← Ascii.Lower[first];
name ← Rope.Replace[
base: name, start: 0, len: 1,
with: Rope.FromChar[first]
];
name ← RequestIdent[env, name, ob, FALSE];
END;
PosToRope: PUBLIC PROC [pos: CD.Position] RETURNS [r: Rope.ROPE] =
BEGIN
RETURN [IO.PutFR["[x: %g, y: %g]",
IO.int[pos.x],
IO.int[pos.y]
]]
END;
RectToRope: PUBLIC PROC [rect: CD.Rect] RETURNS [r: Rope.ROPE] =
BEGIN
RETURN [IO.PutFR["[x1: %g, y1: %g, x2: %g, y2: %g]",
IO.int[rect.x1],
IO.int[rect.y1],
IO.int[rect.x2],
IO.int[rect.y2]
]]
END;
LayerIdent: PUBLIC PROC [env: Environment, l: CD.Layer] RETURNS [r: Rope.ROPE] =
BEGIN
IF env.declaredLayers[l]=NIL THEN {
r: Rope.ROPE;
name: Rope.ROPE ← RequestIdent[env, Atom.GetPName[CD.LayerKey[l]]];
env.declaredLayers[l] ← name;
r ← IO.PutFR["%g: CD.Layer ← CD.FetchLayer[t: tech, uniqueKey: $%g];",
IO.rope[name],
IO.atom[CD.LayerKey[l]]
];
GlobalDeclaration1[env, r];
};
RETURN [env.declaredLayers[l]]
END;
MapClientPos: PROC [inst: CD.Instance] RETURNS [originPos: CD.Position] =
BEGIN
RETURN [CDOrient.MapPoint[
pointInCell: CD.ClientOrigin[inst.ob],
cellSize: inst.ob.size,
cellInstOrient: inst.orientation,
cellInstPos: inst.location
]]
END;
LocalStatement: PUBLIC PROC [env: Environment, line: Rope.ROPE] =
BEGIN
node: TiogaFileOps.Ref;
node ← TiogaFileOps.InsertAsLastChild[env.procNode];
TiogaFileOps.SetContents[node, line];
END;
ProgramText: PROC [ob: CD.Object, design: CD.Design, env: Environment] =
BEGIN
IF ~CDCells.IsCell[ob] THEN ob ← CDDirectory.Expand[ob, design, NIL].new;
IF ob#NIL AND CDCells.IsCell[ob] THEN {
ir: CD.Rect;
hasInst: BOOLFALSE;
num: INT ← 0;
r: Rope.ROPENIL;
cptr: CD.CellPtr = NARROW[ob.specificRef];
childs: RefTab.Ref ← RefTab.Create[];
node: TiogaFileOps.Ref ← MakeProcedureNode[env];
TiogaFileOps.SetContents[node, "CreateCell: CDGenerate.GeneratorProc ="];
env.procDecNode ← TiogaFileOps.InsertAsLastChild[env.procNode];
TiogaFileOps.SetContents[env.procDecNode, "BEGIN"];
--make declarations
FOR list: CD.InstanceList ← cptr.contents, list.rest WHILE list#NIL DO
x: REF ← RefTab.Fetch[childs, list.first.ob].val;
IF x=NIL THEN {
num ← num+1;
[] ← RefTab.Store[childs, list.first.ob, $first]
}
ELSE IF x=$first THEN {
r: Rope.ROPE ← IdentForLocalObject[env, list.first.ob];
num ← num-1;
[] ← RefTab.Store[childs, list.first.ob, $hasName];
LocalStatement[env, Rope.Cat[r, ": CD.Object;"]];
}
ELSE NULL;
ENDLOOP;
IF num#0 THEN LocalStatement[env, "child: CD.Object;"];
LocalStatement[env, "IF design.technology#tech THEN ERROR;"];
LocalStatement[env, "ob ← CDCells.CreateEmptyCell[];"];
--make instances
FOR list: CD.InstanceList ← cptr.contents, list.rest WHILE list#NIL DO
needInst: BOOLFALSE;
childName: Rope.ROPE ← "child";
pos: CD.Position ← MapClientPos[inst: list.first];
orientationCode: Rope.ROPENIL;
IF list.first.orientation#0 THEN
orientationCode ← IO.PutFR[", orientation: %g", IO.int[list.first.orientation]];
SELECT RefTab.Fetch[childs, list.first.ob].val FROM
$hasName => {
childName ← IdentForLocalObject[env, list.first.ob];
[r, needInst] ← MakeACallExpression[env, list.first.ob];
r ← Rope.Cat[childName, " ← ", r, ";"];
LocalStatement[env, r];
IF needInst THEN [] ← RefTab.Store[childs, list.first.ob, $hasValueMI]
ELSE [] ← RefTab.Store[childs, list.first.ob, $hasValue]
};
$hasValue => {
childName ← IdentForLocalObject[env, list.first.ob];
needInst ← FALSE;
};
$hasValueMI => {
childName ← IdentForLocalObject[env, list.first.ob];
needInst ← TRUE;
};
ENDCASE => {
childName ← "child";
[r, needInst] ← MakeACallExpression[env, list.first.ob];
r ← Rope.Cat[childName, " ← ", r, ";"];
LocalStatement[env, r];
};
IF CDProperties.GetInstanceProp[list.first, $SignalName]#NIL OR CDProperties.GetInstanceProp[list.first, $InstanceName]#NIL THEN needInst ← TRUE;
IF needInst THEN {
hasInst ← TRUE;
r ← Rope.Cat[
IO.PutFR["inst ← CDCells.IncludeOb[cell: ob, ob: %g, position: %g%0g].newInst;",
IO.rope[childName],
IO.rope[PosToRope[pos]],
IO.rope[orientationCode]
]];
}
ELSE {
r ← Rope.Cat[
IO.PutFR["[] ← CDCells.IncludeOb[cell: ob, ob: %g, position: %g%0g];",
IO.rope[childName],
IO.rope[PosToRope[pos]],
IO.rope[orientationCode]
]];
};
LocalStatement[env, r];
IF needInst THEN MakeInstanceStuff[env, list.first];
ENDLOOP;
--interest rect
ir ← CD.InterestRect[ob];
r ← Rope.Cat["CDCells.SetInterestRect[ob, ", RectToRope[ir], "];"];
LocalStatement[env, r];
--include in directory
r ← Rope.Cat["[] ← CDDirectory.Include[design: design, object: ob"];
IF cptr.name#NIL THEN
r ← Rope.Cat[r, ", alternateName: ", RopeToRope[cptr.name]];
r ← Rope.Cat[r, "];"];
LocalStatement[env, r];
LocalStatement[env, "END;"];
LocalStatement[env, ""];
IF hasInst THEN {
env.procDecNode ← TiogaFileOps.InsertNode[env.procDecNode];
TiogaFileOps.SetContents[env.procDecNode, "inst: CD.Instance;"];
};
childs ← NIL;
}
ELSE {
TerminalIO.WriteRope["**could not convert to cell\n"];
ERROR ABORTED;
}
END;
RopeToRope: PUBLIC PROC [r: Rope.ROPE] RETURNS [Rope.ROPE] =
BEGIN
RETURN [Convert.RopeFromRope[r]];
END;
AtomToRope: PUBLIC PROC [env: Environment, a: ATOM] RETURNS [Rope.ROPE] =
BEGIN
r: Rope.ROPE ← Atom.GetPName[a];
leng: INT ← Rope.Length[r];
normalCase: BOOL ← leng>0;
FOR i: INT IN [0..leng) DO
c: CHAR ← Rope.Fetch[r, i];
IF ~(Ascii.Letter[c] OR Ascii.Digit[c]) THEN {normalCase←FALSE; EXIT}
ENDLOOP;
IF normalCase THEN RETURN [Rope.Cat["$", r]];
IncludeDirectory[env, "Atom"];
RETURN [ Rope.Cat["Atom.MakeAtom[", Convert.RopeFromRope[r], "]"] ];
END;
MakeInstanceStuff: PROC[env: Environment, inst: CD.Instance] =
BEGIN
r: Rope.ROPENIL;
HandleProp: PROC [key: ATOM] =
BEGIN
val: Rope.ROPE ← CDCommandOps.ToRope[CDProperties.GetInstanceProp[inst, key]];
IF ~Rope.IsEmpty[val] THEN {
r: Rope.ROPE ← Rope.Cat[
"CDProperties.PutInstanceProp[inst, ",
AtomToRope[env, key],
", ",
RopeToRope[val],
"];"
];
IncludeDirectory[env, "CDProperties"];
LocalStatement[env, r];
};
END;
IF CDSymbolicObjects.IsSymbolicOb[inst.ob] THEN {
l: CD.Layer;
owner: ATOM;
name: Rope.ROPE ← CDSymbolicObjects.GetName[inst];
IF ~Rope.IsEmpty[name] THEN {
r ← Rope.Cat["CDSymbolicObjects.SetName[inst, ", RopeToRope[name], "];"];
LocalStatement[env, r];
};
l ← CDSymbolicObjects.GetLayer[inst];
IF l#CD.undefLayer THEN {
r ← Rope.Cat["CDSymbolicObjects.SetLayer[inst, ", LayerIdent[env, l], "];"];
LocalStatement[env, r];
};
owner ← CDSymbolicObjects.GetOwner[inst];
IF owner#NIL THEN {
r ← Rope.Cat["CDSymbolicObjects.SetOwner[inst, ", AtomToRope[env, owner], "];"];
LocalStatement[env, r];
};
}
ELSE {
HandleProp[$SignalName];
HandleProp[$InstanceName];
}
END;
MakeACallExpression: PROC[env: Environment, ob: CD.Object] RETURNS [r: Rope.ROPENIL, mustHandleInstance: BOOLFALSE] =
BEGIN
r ← SpecificCallExpression[env, ob];
IF Rope.IsEmpty[r] THEN {
IF ob.class.inDirectory THEN {
MakeDirGenerator[env];
r ← Rope.Cat["CDGenerate.FetchNCall[directory, design, ", RopeToRope[CDDirectory.Name[ob]], "]"];
}
ELSE IF CDAtomicObjects.IsAtomicOb[ob] THEN {
IncludeDirectory[env, "CDAtomicObjects"];
r ← "CDAtomicObjects.CreateAtomicOb[classKey: ";
r ← Rope.Cat[r, "$", CDCommandOps.ToRope[ob.class.objectType], ", size: "];
r ← Rope.Cat[r, PosToRope[CD.InterestSize[ob]], ", "];
r ← Rope.Cat[r, "tech: tech, lev: ", LayerIdent[env, ob.layer], "]"];
}
ELSE IF CDSymbolicObjects.IsPin[ob] THEN {
IncludeDirectory[env, "CDSymbolicObjects"];
r ← IO.PutFR["CDSymbolicObjects.CreatePin[%g]", IO.rope[PosToRope[ob.size]]];
mustHandleInstance ← TRUE;
}
ELSE IF CDSymbolicObjects.IsSegment[ob] THEN {
IncludeDirectory[env, "CDSymbolicObjects"];
r ← IO.PutFR["CDSymbolicObjects.CreateSegment[length: %g, dummyWidth: %g]", IO.int[ob.size.y], IO.int[ob.size.x]];
mustHandleInstance ← TRUE;
}
ELSE IF CDSymbolicObjects.IsMark[ob] THEN {
IncludeDirectory[env, "CDSymbolicObjects"];
r ← IO.PutFR["CDSymbolicObjects.CreateMark[dummySize: %g]", IO.int[ob.size.y]];
mustHandleInstance ← TRUE;
}
ELSE IF ob.class.wireTyped THEN {
IncludeDirectory[env, "CDRects"];
r ← "CDRects.CreateRect[size: ";
r ← Rope.Cat[r, PosToRope[CD.InterestSize[ob]]];
r ← Rope.Cat[r, ", l: ", LayerIdent[env, ob.layer], "]"];
}
ELSE {
r ← "--Unknown[]--";
IncludeDirectory[env, "CDRects"];
r ← Rope.Cat[r, "CDRects.CreateRect[size: "];
r ← Rope.Cat[r, PosToRope[CD.InterestSize[ob]]];
r ← Rope.Cat[r, ", l: CD.highLightError]"];
};
};
END;
MakeProgramCommand: PROC [comm: CDSequencer.Command] =
BEGIN
inst: CD.Instance = CDCommandOps.TheInstance[comm, "make program text"];
IF inst#NIL THEN {
env: Environment ← NewEnvironment[comm.design.technology];
Reserve[env, "design"];
Reserve[env, "key"];
Reserve[env, "table"];
Reserve[env, "directory"];
Reserve[env, "data"];
Reserve[env, "tech"];
Reserve[env, "child"];
Reserve[env, "CreateCell"];
Reserve[env, "ob"];
Reserve[env, "inst"];
IncludeDirectory[env, "CDCells"];
IncludeDirectory[env, "CDGenerate"];
IncludeDirectory[env, "CD"];
IncludeDirectory[env, "CDDirectory"];
ProgramText[inst.ob, comm.design, env];
FinishUp[env];
}
END;
--XX SPECIAL OBJECTS XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
--XX INIT XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[] ← CDProperties.RegisterProperty[$MakeCallExpression, $chj];
CDSequencer.ImplementCommand[$MakeProgram, MakeProgramCommand,, doQueue];
CDMenus.CreateEntry[menu: $ProgramMenu, entry: "make text", key: $MakeProgram];
TerminalIO.WriteRope["make program command loaded\n"];
END.