Product Documentation
Cadence SKILL Language User Guide
Product Version IC23.1, September 2023

17


Programming Examples

See the following sections for programming examples:

List Manipulation

A list is a linear sequence of Cadence® SKILL language data objects. The elements of a list can have any data type, including symbols or other lists. The printed presentation for a SKILL list uses a matching pair of parentheses to enclose the printed representations of the list elements. The trListIntersection and trListUnion functions illustrate

The trListUnion function also illustrates the nconc function, which destroys all but its last argument. In this case, the first argument is a new, anonymous list created by the setof function.

procedure( trListIntersection( list1 list2 )
setof( element list1
member( element list2 )
) ; setof
) ; procedure
procedure( trListUnion( list1 list2 )
nconc(
setof( element list2
!member( element list1)
) ; setof
list1
) ; nconc
) ; procedure
trListIntersection( ‘(a b c) ‘(b c d)) => (b c)
trListUnion( list(1 2 3) list(3 4 5 6)) => ( 4 5 6 1 2 3)

Symbol Manipulation

A symbol is the primary data structure within SKILL. A SKILL symbol has four data slots: the name, the value, the function definition, and the property list. Except for the name slot, all slots can be empty.

The trReplaceSymbolsWithValues function makes a copy of an arbitrary SKILL expression, in which all references to a symbol are replaced by the symbol’s value.

a = "one" b = "two" c = "three"
testCase = '( 1 2 ( a b ) )
trReplaceSymbolsWithValues( testCase ) => (1 2 ("one" "two"))
testCase = '(1 ( a ( c )) b )
trReplaceSymbolsWithValues( testCase ) => (1 ("one" ("three")) "two")

The trReplaceSymbolsWithValues illustrates

The listp function determines whether the expression is a list.

The symbolp function determines whether the expression is a list.

The symeval function retrieves the value of the expression, provided it is a symbol.

In the general case, the cond function recursively descends into the car of the expression and the cdr of the expression and builds a list from the results.

procedure( trReplaceSymbolsWithValues( expression )
cond(
( null( expression ) nil )
( symbolp( expression ) symeval( expression ) )
( listp( expression )
cons(
trReplaceSymbolsWithValues( car( expression ))
trReplaceSymbolsWithValues( cdr( expression ))
)
)
( t expression )
) ; cond
) ; procedure
x = 5
a = 1
trReplaceSymbolsWithValues( ‘(x a))
=> (5 1)

Sorting a List of Points

The trPointLowerLeftp function indicates whether pt1 is located to the lower left of pt2. This function illustrates

The trSortPointList function returns a list of points sorted destructively and illustrates

Computing the Center of a Bounding Box

The trBBoxCenter function returns the point at the center of a bounding box and illustrates

Computing the Area of a Bounding Box

The trBBoxArea function returns the area of a bounding box and illustrates

Computing a Bounding Box Centered at a Point

The trDot function returns bounding box coordinates with a given point as its center and illustrates

Computing the Union of Several Bounding Boxes

The trBBoxUnion function returns the smallest bounding box coordinates containing all the boxes in a given list and illustrates

Computing the Intersection of Bounding Boxes

The trBBoxIntersection function illustrates

Prime Factorizations

A prime factorization of an integer is a list of pairs and is an example of an association list. The first element of each pair is a prime number that divides the number and the second element is the exponent to which the prime is to be raised. Each such pair is termed a prime-exponent pair.

pf1 = '( ( 2 3 ) ( 3 5 ))
pf2 = '( ( 3 2 ) ( 5 2 ) ( 7 3 ))
pf3 = '( ( 3 2 ) ( 5 4 ) ( 7 2 )) pf4 = '( ( 3 6 ) ( 7 3 ) ( 11 2 ) ( 5 1 ))

The assoc function is used to determine whether a prime number occurs in a prime factorization. It returns either the prime-exponent pair or nil. For example:

assoc( 2 pf1 ) => ( 2 3 )
assoc( 7 pf2 ) => ( 7 3 )

Evaluating a Prime Factorization

To evaluate the prime factorization means to perform the arithmetic operations implied:

For example, evaluating the prime factorization

( ( 3 2 ) ( 5 2 ) ( 7 3 ))

is equivalent to evaluating

3**2 * 5**2 * 7**3 

The trTimes functions multiplies a list of numbers together. It handles two cases that the times function does not handle.

The trTimes function illustrates

The trEvalPF function evaluates the prime factorizations. For example:

pf1 = '( ( 2 3 ) ( 3 5 ))
pf2 = '( ( 3 2 ) ( 5 2 ) ( 7 3 ))
trEvalPF( pf1 ) => 1944
trEvalPF( pf2 ) => 77175

The trEvalPF function illustrates

Computing the Prime Factorization

The trLargestExp function returns the largest x such that divisor ** x <= number and illustrates

The trPF function returns the prime factorization a number. For example:

trPF( 1003 )     => ((59 1) (17 1))
trPF( 10003 ) => ((1429 1) (7 1))
trPF( 100003 ) => ((100003 1))
trPF( 123456 )   => ((643 1) (3 1) (2 6))

The trPF function illustrates

Multiplying Two Prime Factorizations

The trPFMult function returns the prime factorization of the product of two prime factorizations, pf1 and pf2. The trPFMult function uses the following algorithm to construct the resultant prime factorization.

  1. Those prime-exponent pairs whose primes occur in only one of the prime factorizations lists are carried across unaltered into the resultant prime factorization.
  2. For those primes that have entries in both prime factorizations, a prime-exponent pair using the prime is included with an exponent equal to the sum of prime’s exponent in either prime factorization.

The trPFMult function illustrates

Using Prime Factorizations to Compute the GCD

The trPFGCD function returns the prime factorization of the greatest common denominator (GCD) of two prime factorizations. This function illustrates

procedure( trPFGCD( pf1 pf2 )
let( ( pePair2 )
foreach( mapcan pePair1 pf1
pePair2 = assoc( car( pePair1 ) pf2 )
when( pePair2
;; build a list containing a single prime-exponent pair.
;; The mapcan option to the foreach function
;; destructively appends these lists together.
‘( (
,car( pePair1 )
,min( cadr( pePair1 ) cadr( pePair2 ) )
) )
) ; when
) ; foreach
) ; let
) ; procedure

The trGCD function illustrates finding the greatest common denominator (GCD) of two numbers by

procedure( trGCD( num1 num2 )
trEvalPF( trPFGCD( trPF( num1 ) trPF( num2 )) )
) ; procedure

Fibonacci Function

This example illustrates a recursive implementation of the Fibonacci function, implemented directly from the mathematical definition.

procedure( fibonacci(n) 
if( (n == 1 || n == 2) then 1
else fibonacci(n-1) + fibonacci(n-2)
))
fibonacci(3)         => 2
fibonacci(6)         => 8

The same example implemented in SKILL using LISP syntax looks like the following:

(defun fibonacci (n)
    (cond
((or (equal n 1) (equal n 2)) 1)
(t (plus (fibonacci (difference n 1))
(fibonacci (difference n 2))))
)
)

Factorial Function

This is the recursive implementation of the factorial function

procedure( factorial( n )
if( zerop( n ) then
1
else
n*factorial( n-1)
) ; if
) ; procedure

This is an iterative implementation

procedure( factorial( n )
let( ( ( f 1 ))
for( i 1 n
f = f*i
) ; for
f ;;; return the value of f
) ; let
) ; procedure

Exponential Function

This function computes e to the power x by summing terms of the power series expansion of the mathematical function. It uses the factorial function.

procedure( e( x )
let( ((sum 1.0))
for( n 1 10
sum = sum + (1.0/factorial(n)) * x**n
) ; for
sum ;;; return the value of sum.
) ; let
) ; procedure

To get a sense of the accuracy of this implementation of the e function, observe

e( log( 10 ) ) => 9.999702 ;; should be 10.0

Counting Values in a List

The trCountValues function tallies the number of times each distinct value occurs as a top-level element of a list. It prints a report and returns an association list that pairs each unique value with it’s count. Two implementations are presented. The results are equivalent except for ordering.

The first implementation of the trCountValues function illustrates

The second implementation of the trCountValues function illustrates

Counting Characters in a String

The trCountCharacters function counts the occurrences of characters in a string.

The trCountCharacters illustrates

Regular Expression Pattern Matching

The following functions take the regular expression pattern matching functions rexMatchp and rexMatchList provided by SKILL and build two new functions shMatchp and shMatchList, which provide a simple shell-filename-like pattern matching facility.

The rules:

The function sh2ed is used to build a regular expression that is passed to the rex functions.

(defun sh2ed (s)
(let ((sh_chars (parseString s ""))
(ed_chars (tconc nil "^")))
(while sh_chars
(case (car sh_chars)
("*" (tconc ed_chars ".") (tconc ed_chars "*"))
("?" (tconc ed_chars "."))
("." (tconc ed_chars "\\") (tconc ed_chars "."))
(t (tconc ed_chars (car sh_chars))))
(setq sh_chars (cdr sh_chars)))
(tconc ed_chars "$")
(buildString (car ed_chars) "")))
(defun shMatchp (pattern target)
(rexMatchp (sh2ed pattern) target))
(defun shMatchList (pattern targets)
(rexMatchList (sh2ed pattern) targets))
shMatchp("*.out" "a.out")        => t
shMatchp("test.??" "test.il") => t
shMatchp("*.??" "test.out") => nil
shMatchList("*test.?" '("ALUtest.1" "data.in" "MEMtest.13" "test.5"))=> ("ALUtest.1" "test.5")

Geometric Constructions

Here is an extensive example of a SKILL++ Object Layer application.

Application Domain

A geometric construction is a collection of points and lines you build up from an initial collection of points. The initial collection of points are called free points. You can add points and lines to the collection through various familiar constraints. You can constrain

When you move any one of the free points, the application propagates the change to all the constrained points and lines

Example

  1. You specify the free points P and Q.
  2. You construct the line PQ passing through P and Q.
  3. You specify the free points R and S.
  4. You construct the line RS passing through R and S.
  5. You construct the intersection point Z of the line PQ and the line RS.

When you move any of the points P, Q, R, or S the lines PQ and RS and the point Z move accordingly.

Implementation

The implementation uses the SKILL++ Object System to define several classes and generic functions. The following sections discuss

To focus on SKILL++ language issues, the implementation does not address graphics. Instead, you non-graphically

  1. Call a SKILL function repeatedly to specify several free points.
  2. Call other SKILL functions to construct the dependent points and lines.
  3. Enter a SKILL expression to change the coordinates of one of the free points.
  4. Call a SKILL function to propagate the change through the constrainted points and lines.
  5. Call a SKILL function to describe one of the constrained points or lines.

Classes

The implementation uses the SKILL++ Object System to define several classes in the following class hierarchy.

GeometricObject Class

The GeometricObject class represents all the objects in the construction. It defines the constraints slot. This slot lists all the other objects which need to be notfied when the object updates.

Point Class

The Point class represents a point with slots x and y.

Line Class

The Line class represents a line with the slots A, B, and C. These are the coefficients in the line’s equation

Ax+By+C = 0

IntersectionTwoLines_Point Class

The IntersectionTwoLines_Point class is a subclass of the Point class and represents a point that lies on two intersecting lines. It includes two slots that store the lines.

TwoPoints_Line Class

The TwoPoints_Line class is a subclass of the Line class and represents a line passing through two points. The class defines two slots to store the points.

ParallelLineThroughPoint_Line Class

The ParallelLineThroughPoint_Line class is a subclass of the Line class and represents a line that passes through a point parallel to another line.

To specify a free point, you instantiate the Point class. To specify a constrained point or line, you instantiate the associated subclass.

Generic Functions

The implementation uses several generic functions.

The defgeneric declarations for these generic functions each a declare default method that raises an error.

Describing the Methods by Class

The following tables describe, for each generic function, all the methods by class. In the following tables,

Connect Methods

Connect generic Function
Class Method description

GeometricObject

Add a dependent object to the list of dependent objects.

Point

No method for this class.

Line

No method for this class.

IntersectionTwoLines_Point

No method for this class.

TwoPoints_Line

No method for this class.

etc.

Source Code

;;; toplevel( 'ils )
defgeneric( Connect ( obj constraint )    error( "Connect is a subclass responsibility\n" )
) ; defgeneric
defgeneric( Update ( obj )    error( "Update is a subclass responsibility\n" )
) ; defgeneric
defgeneric( Validate ( obj )    error( "Validate is a subclass responsibility\n" )
) ; defgeneric
defgeneric( Describe ( obj )    error( "Describe is a subclass responsibility\n" )
) ; defgeneric
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;; GeometricObject
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
defclass( GeometricObject    ()
(
( constraints
@initform nil
)
)
) ; defclass
defmethod( Connect (( obj GeometricObject ) constraint )    when( !member( constraint obj->constraints )
obj->constraints = cons( constraint obj->constraints )
) ; when
) ; defmethod
defmethod( Update (( obj GeometricObject ))    printf( "Updating constraints %L for %L\n"
obj->constraints obj )
Validate( obj )
foreach( constraint obj->constraints
Update( constraint )
) ; foreach
t
) ; defmethod
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;; Point
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
defclass( Point ( GeometricObject )    (
( name @initarg name )
( x @initarg x );;; x-coordinate
( y @initarg y );;; y-coordinate
)
) ; defclass
defmethod( Describe (( obj Point ))    printf( "%s at %n:%n\n"
className( classOf( obj )) obj->x obj->y )
) ;defmethod
defmethod( Validate ((obj Point))    t
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;; IntersectionTwoLines_Point
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
defclass( IntersectionTwoLines_Point ( Point )
(
( L1 @initarg L1 )
( L2 @initarg L2 )
)
) ; defclass
defmethod( Describe (( obj IntersectionTwoLines_Point ))    callNextMethod( obj );;; generic point description
printf( "…intersection of\n")
Describe( obj->L1 )
Describe( obj->L2 )
) ;defmethod
procedure( make_IntersectionTwoLines_Point( line1 line2 )
let( ( point )
point = makeInstance( 'IntersectionTwoLines_Point
?L1 line1
?L2 line2
)
Update( point )
Connect( line1 point )
Connect( line2 point )
point
) ; let
) ; procedure
defmethod( Validate (( obj IntersectionTwoLines_Point ))    let( ( A1 B1 C1 A2 B2 C2 x y )
A1 = obj->L1->A
B1 = obj->L1->B
C1 = obj->L1->C
A2 = obj->L2->A
B2 = obj->L2->B
C2 = obj->L2->C
x = obj->x
y = obj->y
when( A1*x+B1*y+C1 != 0.0 || A2*x+B2*y+C2 != 0.0
error( "Invalid IntersectionTwoLines_Point\n" )
) ; when
t
) ; let
) ; defmethod
defmethod( Update (( obj IntersectionTwoLines_Point ))
;; check to see if my two lines have values …
printf( "Figure out my x & y from lines %L %L\n"
obj->L1 obj->L2 )
let( ( A1 B1 C1 A2 B2 C2 det )
A1 = obj->L1->A
B1 = obj->L1->B
C1 = obj->L1->C
A2 = obj->L2->A
B2 = obj->L2->B
C2 = obj->L2->C
det = A1*B2-A2*B1
when( det == 0
error( "Can not intersect two parallel lines\n" )
)
obj->x = ((-C1)*B2-(-C2)*B1)*1.0/det
obj->y = (A1*(-C2)-A2*(-C1))*1.0/det
) ; let
callNextMethod( obj )
) ; defmethod
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;; Line
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
defclass( Line ( GeometricObject )    ( ;;;; Ax+By+C = 0
( A )
( B )
( C )
)
) ; defclass
defmethod( Describe (( obj Line ))    printf( "%s %nx+%ny+%n=0\n"
className( classOf( obj ))
obj->A obj->B obj->C
)
) ; defmethod
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;; TwoPoints_Line
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
defclass( TwoPoints_Line ( Line )    (
( P1 @initarg P1 )
( P2 @initarg P2 )
)
) ; defclass
defmethod( Describe (( obj TwoPoints_Line ))    callNextMethod( obj )
printf( "…containing\n" )
Describe( obj->P1 )
Describe( obj->P2 )
) ; defmethod
procedure( make_TwoPoints_Line( p1 p2 )    let( ( line )
line = makeInstance( 'TwoPoints_Line
?P1 p1 ?P2 p2 )
Update( line )
Connect( p1 line )
Connect( p2 line )
line
) ; let
) ; procedure
defmethod( Validate (( obj TwoPoints_Line ))
let( (x1 y1 x2 y2 A B C)
x1 = obj->P1->x
x2 = obj->P2->x
y1 = obj->P1->y
y2 = obj->P2->y
A = obj->A
B = obj->B
C = obj->C
if( A*x1+B*y1+C != 0.0 then
error( "Invalid TwoPoints_Line\n" ))
if( A*x2+B*y2+C != 0.0 then
error( "Invalid TwoPoints_Line\n" ))
t
) ; let
) ; defmethod
defmethod( Update (( obj TwoPoints_Line ))    let( (x1 y1 x2 y2 m b)
x1 = obj->P1->x
x2 = obj->P2->x
y1 = obj->P1->y
y2 = obj->P2->y
if( x2-x1 != 0
then
m = (y2-y1)*1.0/(x2-x1)
b = y2-m*x2
obj->A = -m
obj->B = 1
obj->C = -b
else
obj->A = 1.0
obj->B = 0.0
obj->C = -x1
) ; if
) ; let
callNextMethod( obj )
) ; defmethod
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;; ParallelLineThroughPoint_Line
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
defclass( ParallelLineThroughPoint_Line ( Line )
(
( P @initarg P)
( L @initarg L)
)
) ; defclass
defmethod( Validate (( obj ParallelLineThroughPoint_Line ))    let( (x1 y1 A B C LA LB LC)
x1 = obj->P->x
y1 = obj->P->y
LA = obj->L->A
LB = obj->L->B
LC = obj->L->C
A = obj->A
B = obj->B
C = obj->C
when( A*LB-LA*B != 0.0 || A*x1+B*y1+C != 0
error( "Invalid ParallelLineThroughPoint_Line\n" ))
t
) ; let
) ; defmethod
defmethod( Describe (( obj ParallelLineThroughPoint_Line ))    callNextMethod( obj )
printf( "…Containing\n" )
Describe( obj->P )
printf( "…Parallel to\n" )
Describe( obj->L )
) ; defmethod
procedure( make_ParallelLineThroughPoint_Line( point line )
let( ( parallel_line )
parallel_line = makeInstance(
'ParallelLineThroughPoint_Line
?P point
?L line
)
Update( parallel_line )
Connect( point parallel_line )
Connect( line parallel_line )
parallel_line
) ; let
) ; procedure
defmethod( Update (( obj ParallelLineThroughPoint_Line ))    let( ( A B C x1 y1 )
A = obj->L->A
B = obj->L->B
C = obj->L->C
x1 = obj->P->x
y1 = obj->P->y
obj->A = A
obj->B = B
obj->C = -(A*x1+B*y1)
) ; let
callNextMethod( obj )
) ; defmethod

Example 1

This example

Example 2

This example

Extending the Implementation

Consider the following extensions:

Examples of How to use Custom Specializers in SKILL++ Methods

An example of using SKILL++ custom specializers to allow defining a method specialized on the database object type. Standard SKILL++ methods are specialized on the class of an object passed to the generic function, but this allows extending it to further divide based on some attribute of a non-SKILL++ object.

This example implements abFigArea(obj) that calculates the area of various types of database object, You can see the individual methods further down the code. It implements calculations for most (not guaranteed to be complete) figure object types in Virtuoso. It also includes abFigPerimeter(obj) to show an example of a second generic function using the same specializers.

To call:

abFigArea(dbId) => area (in micron^2)
abFigPerimeter(dbId) => length of perimeter (in microns)

Limitations: does not currently compute the perimeter of an arc (It is difficult to compute the length for elliptic arcs).

***************************************************
SCCS Info: @(#) abAreaMethod.ils 12/17/20.11:01:03 1.2
*/
;------------------------------------------------------------------------
; First define a class to store the information needed to match an
; object against the custom specializer
;------------------------------------------------------------------------
defclass(abDbFigSpecializer ()
    (
        (objType @initarg objType)
    )
)
;------------------------------------------------------------------------
; This method is called when a defmethod using a custom specializer
; is used. This uses an eqv specializer, so if the defmethod is
; of this form: defmethod(methodName ((obj (abDbFig "ellipse"))) ...)
; then it will create an instance of the specializer class to
; store the database objType in.
; ilGenerateSpecializer is passed the generic function object, the symbol
; which is the first term of the specializer in the defmethod, and then
; a list of the arguments (all literal; no evaluation is performed)
;------------------------------------------------------------------------
defmethod(ilGenerateSpecializer (_gf (_spec (eqv 'abDbFig)) args)
    makeInstance('abDbFigSpecializer ?objType car(args))
)
;------------------------------------------------------------------------
; This method is used when the generic function is called. It
; tries against each specializer object that there is a method
; defined for, and this method determines whether the specializer
; matches the objType of the database object passed in.
; The method is passed the generic function object, the object for the
; specializer being checked against, and the actual object for the argument
; to the method.
; Note that the method should check that the argument is the expected
; object type as well as checking the values match - this avoids situations
; where retrieving (in this case) ->objType fails because the object has no
; slot objType.
;------------------------------------------------------------------------
defmethod(ilArgMatchesSpecializer (_gf (spec abDbFigSpecializer) (arg dbobject))
    spec->objType==arg->objType
)
;------------------------------------------------------------------------
; Must define a method to check whether two specializer objects are 
; equivalent. Failure to do this will lead to errors if you redefine
; methods using the custom specializer
;------------------------------------------------------------------------
defmethod(ilEquivalentSpecializers (_gf (spec1 abDbFigSpecializer)
        (spec2 abDbFigSpecializer))
    spec1->objType==spec2->objType
)
/**************************************************************************
*                                                                         *
*                            abAreaOfBBox(bBox)                           *
*                                                                         *
*       A utility function to calculate the area of a bBox. Several       *
* objects are rectangular, so having a common function makes this simpler *
*                                                                         *
**************************************************************************/
defun(abAreaOfBBox (bBox)
    destructuringBind(((llx lly) (urx ury)) bBox
        (urx-llx)*(ury-lly)
    )
)
/***************************************************************
*                                                              *
*                  abAreaOfPolygon(pointList)                  *
*                                                              *
*    A utility function to calulate the area of a polygon.     *
*          Effectively integrates around the polygon.          *
*                                                              *
***************************************************************/
defun(abAreaOfPolygon (pointList)
    let(((sum 0) firstPt lastPt dx)
        firstPt=lastPt=car(pointList)
        foreach(point cdr(pointList)
             dx=xCoord(point)-xCoord(lastPt)
             sum=sum+dx*(yCoord(point)+yCoord(lastPt))
             lastPt=point
        )
        sum=sum+((xCoord(firstPt)-xCoord(lastPt))*
                (yCoord(firstPt)+yCoord(lastPt)))
        0.5*abs(sum)
    )
)
/***************************************************************
*                                                              *
*                   abPerimeterOfBBox(bBox)                    *
*                                                              *
*   A utility function to calculate the perimeter of a bBox    *
*                                                              *
***************************************************************/
defun(abPerimeterOfBBox (bBox)
    destructuringBind(((llx lly) (urx ury)) bBox
        2*(urx-llx+ury-lly)
    )
)
/************************************************************************
*                                                                       *
*         abPerimeterOfPolygon(pointList @optional (closed t))          *
*                                                                       *
*   Utility function to compute the perimeter of a polygon pointList.   *
*    By default this is a closed polygon, but if the second argument    *
* is passed as nil, will find the length of a line with that pointList. *
*                                                                       *
************************************************************************/
defun(abPerimeterOfPolygon (pointList @optional (closed t))
    let(((sum 0) firstPt lastPt)
        firstPt=lastPt=car(pointList)
        foreach(point cdr(pointList)
            sum=sum+sqrt(
                (xCoord(lastPt)-xCoord(point))**2 +
                (yCoord(lastPt)-yCoord(point))**2
            )
            lastPt=point
        )
        when(closed
            sum=sum+sqrt(
                (xCoord(lastPt)-xCoord(firstPt))**2 +
                (yCoord(lastPt)-yCoord(firstPt))**2
            )
        )
        sum
    )
)
;------------------------------------------------------------------------
; Some of the methods need PI, so this associates the math
; constants with the abFigMaths symbol (so can use abFigMaths.PI
; for example)
;------------------------------------------------------------------------
defMathConstants('abFigMaths)
/************************************************************************
*                                                                       *
*                            abFigArea(obj)                             *
*                                                                       *
*   The generic function - pass in a database object and compute the    *
* area. The generic function handles anything that isn't recognised and *
*             tries to give a vaguely useful error message              *
*                                                                       *
************************************************************************/
defgeneric(abFigArea (obj)
    let((objType)
        objType=
            if(dbIsId(obj) then
                strcat(type(obj) " - " obj~>objType)
            else
                type(obj)
            )
        error("abFigArea: unrecognized object %L (%s)\n" obj objType)
    )
)
/*********************************************************************
*                                                                    *
*                         abFigPerimeter(obj)                        *
*                                                                    *
* The generic function for perimeter - pass in a database object and *
*  compute the length of the perimeter. The generic function simply  *
*       errors gracefully when passed an unrecognised object.        *
*                                                                    *
*********************************************************************/
defgeneric(abFigPerimeter (obj)
    let((objType)
        objType=
            if(dbIsId(obj) then
                strcat(type(obj) " - " obj~>objType)
            else
                type(obj)
            )
        error("abFigPerimeter: unrecognized object %L (%s)\n" obj objType)
    )
)
;------------------------------------------------------------------------
; Now all the methods specialized on each objType.
; First, the abFigArea methods
;------------------------------------------------------------------------
;------------------------------------------------------------------------
; Just use the bBox of these objects to compute the area
;------------------------------------------------------------------------
defmethod(abFigArea ((obj (abDbFig "rect")))
    abAreaOfBBox(obj~>bBox)
)
defmethod(abFigArea ((obj (abDbFig "inst")))
    abAreaOfBBox(obj~>bBox)
)
defmethod(abFigArea ((obj (abDbFig "mosaic")))
    abAreaOfBBox(obj~>bBox)
)
defmethod(abFigArea ((obj (abDbFig "figGroup")))
    abAreaOfBBox(obj~>bBox)
)
defmethod(abFigArea ((obj (abDbFig "stdVia")))
    abAreaOfBBox(obj~>bBox)
)
defmethod(abFigArea ((obj (abDbFig "customVia")))
    abAreaOfBBox(obj~>bBox)
)
;------------------------------------------------------------------------
; Polygons, boundaries, blockages and halos have a point list
; that we can compute the area from
;------------------------------------------------------------------------
defmethod(abFigArea ((obj (abDbFig "polygon")))
    abAreaOfPolygon(obj~>points)
)
defmethod(abFigArea ((obj (abDbFig "areaBlockage")))
    abAreaOfPolygon(obj~>points)
)
defmethod(abFigArea ((obj (abDbFig "layerBlockage")))
    abAreaOfPolygon(obj~>points)
)
defmethod(abFigArea ((obj (abDbFig "PRBoundary")))
    abAreaOfPolygon(obj~>points)
)
defmethod(abFigArea ((obj (abDbFig "areaBoundary")))
    abAreaOfPolygon(obj~>points)
)
defmethod(abFigArea ((obj (abDbFig "snapBoundary")))
    abAreaOfPolygon(obj~>points)
)
defmethod(abFigArea ((obj (abDbFig "clusterBoundary")))
    abAreaOfPolygon(obj~>points)
)
defmethod(abFigArea ((obj (abDbFig "areaHalo")))
    abAreaOfPolygon(obj~>points)
)
defmethod(abFigArea ((obj (abDbFig "layerHalo")))
    abAreaOfPolygon(obj~>points)
)
;------------------------------------------------------------------------
; Few individual objects which are more interesting
;------------------------------------------------------------------------
defmethod(abFigArea ((obj (abDbFig "donut")))
    let((inner outer)
        outer=abFigMaths.PI*obj~>outerRadius**2
        inner=abFigMaths.PI*obj~>innerRadius**2
        outer-inner
    )
)
defmethod(abFigArea ((obj (abDbFig "ellipse")))
    ; pi*a*b where a and b are the radii of the ellipse
    destructuringBind(((llx lly) (urx ury)) obj~>bBox
        abFigMaths.PI*(urx-llx)*(ury-lly)/4.0
    )
)
defmethod(abFigArea ((obj (abDbFig "path")))
    abAreaOfPolygon(dbGetPathBoundary(obj))
)
defmethod(abFigArea ((obj (abDbFig "pathSeg")))
    abAreaOfPolygon(obj~>boundary)
)
;------------------------------------------------------------------------
; Finally a bunch of objects with no area
;------------------------------------------------------------------------
defmethod(abFigArea ((_obj (abDbFig "line")))
    0.0
)
defmethod(abFigArea ((_obj (abDbFig "dot")))
    0.0
)
defmethod(abFigArea ((_obj (abDbFig "arc")))
    0.0
)
defmethod(abFigArea ((_obj (abDbFig "ruler")))
    0.0
)
;------------------------------------------------------------------------
; could use the bBox, but area doesn't really make sense for labels
;------------------------------------------------------------------------
defmethod(abFigArea ((_obj (abDbFig "label")))
    0.0
)
defmethod(abFigArea ((_obj (abDbFig "textDisplay")))
    0.0
)
;------------------------------------------------------------------------
; Now the abFigPerimeter methods
;------------------------------------------------------------------------
;------------------------------------------------------------------------
; Just use the bBox of these objects to compute the perimeter
;------------------------------------------------------------------------
defmethod(abFigPerimeter ((obj (abDbFig "rect")))
    abPerimeterOfBBox(obj~>bBox)
)
defmethod(abFigPerimeter ((obj (abDbFig "inst")))
    abPerimeterOfBBox(obj~>bBox)
)
defmethod(abFigPerimeter ((obj (abDbFig "mosaic")))
    abPerimeterOfBBox(obj~>bBox)
)
defmethod(abFigPerimeter ((obj (abDbFig "figGroup")))
    abPerimeterOfBBox(obj~>bBox)
)
defmethod(abFigPerimeter ((obj (abDbFig "stdVia")))
    abPerimeterOfBBox(obj~>bBox)
)
defmethod(abFigPerimeter ((obj (abDbFig "customVia")))
    abPerimeterOfBBox(obj~>bBox)
)
;------------------------------------------------------------------------
; Polygons, boundaries, blockages and halos have a point list
; that we can compute the perimeter from
;------------------------------------------------------------------------
defmethod(abFigPerimeter ((obj (abDbFig "polygon")))
    abPerimeterOfPolygon(obj~>points)
)
defmethod(abFigPerimeter ((obj (abDbFig "areaBlockage")))
    abPerimeterOfPolygon(obj~>points)
)
defmethod(abFigPerimeter ((obj (abDbFig "layerBlockage")))
    abPerimeterOfPolygon(obj~>points)
)
defmethod(abFigPerimeter ((obj (abDbFig "PRBoundary")))
    abPerimeterOfPolygon(obj~>points)
)
defmethod(abFigPerimeter ((obj (abDbFig "areaBoundary")))
    abPerimeterOfPolygon(obj~>points)
)
defmethod(abFigPerimeter ((obj (abDbFig "snapBoundary")))
    abPerimeterOfPolygon(obj~>points)
)
defmethod(abFigPerimeter ((obj (abDbFig "clusterBoundary")))
    abPerimeterOfPolygon(obj~>points)
)
defmethod(abFigPerimeter ((obj (abDbFig "areaHalo")))
    abPerimeterOfPolygon(obj~>points)
)
defmethod(abFigPerimeter ((obj (abDbFig "layerHalo")))
    abPerimeterOfPolygon(obj~>points)
)
;------------------------------------------------------------------------
; Few individual objects which are more interesting
;------------------------------------------------------------------------
defmethod(abFigPerimeter ((obj (abDbFig "donut")))
    let((inner outer)
        outer=2*abFigMaths.PI*obj~>outerRadius
        inner=2*abFigMaths.PI*obj~>innerRadius
        outer+inner
    )
)
defmethod(abFigPerimeter ((obj (abDbFig "ellipse")))
    ;--------------------------------------------------------------------
    ; there is no exact formula for the perimeter of an ellipse
    ; so instead use Ramanujan's approximation
    ;--------------------------------------------------------------------
    destructuringBind(((llx lly) (urx ury)) obj~>bBox
        let(((a (urx-llx)/2.0) (b (ury-lly)/2.0) h)
            h=(a-b)**2/(a+b)**2
            abFigMaths.PI*(a+b)*(1+3*h/(10+sqrt(4-3*h)))
        )
    )
)
defmethod(abFigPerimeter ((obj (abDbFig "path")))
    abPerimeterOfPolygon(dbGetPathBoundary(obj))
)
defmethod(abFigPerimeter ((obj (abDbFig "pathSeg")))
    abPerimeterOfPolygon(obj~>boundary)
)
;------------------------------------------------------------------------
; Lines and rulers - the perimeter is just one side
; I guess it could be double this, maybe?
;------------------------------------------------------------------------
defmethod(abFigPerimeter ((obj (abDbFig "line")))
    abPerimeterOfPolygon(obj~>points nil)
)
defmethod(abFigPerimeter ((obj (abDbFig "ruler")))
    abPerimeterOfPolygon(obj~>points nil)
)
;------------------------------------------------------------------------
; This is left to an exercise for the reader (mainly because it is
; pretty difficult - there is no exact formula for this)
;------------------------------------------------------------------------
defmethod(abFigPerimeter ((_obj (abDbFig "arc")))
    0.0
)
;------------------------------------------------------------------------
; Finally a bunch of objects with no perimeter
;------------------------------------------------------------------------
defmethod(abFigPerimeter ((_obj (abDbFig "dot")))
    0.0
)
defmethod(abFigPerimeter ((_obj (abDbFig "label")))
    0.0
)
defmethod(abFigPerimeter ((_obj (abDbFig "textDisplay")))
    0.0
)

Simple Code Example Using a Generic Function Proxy

This example (not as complete as the abFigArea/abFigPerimeter example) uses a different strategy of defining a proxy for the ilGenericFunction and using that to specialize the custom specializer methods. This proxy class must be specified via the ?genericFunctionClass argument to defgeneric when defining a generic function. The disadvantage of this approach is that it could disguise how the method are actually being specialized - in this case just a string representing the objType is used to distinguish each method, and that may not be as clear that something special is happening. Therefore, it is probably best to use the approach above (if using custom specializers at all).

;------------------------------------------------------------------------
; define a proxy for the generic function object
; Any generic functions defined using this will then trigger the
; right custom specializer methods
;------------------------------------------------------------------------
defclass(CCSproxyGF (ilGenericFunction)
  ()
)
;------------------------------------------------------------------------
; A class to hold the specializer details
;------------------------------------------------------------------------
defclass(CCSdbobjectSpecializer ()
  (
    (objType @initarg objType)
  )
)
defmethod(ilGenerateSpecializer ((gf CCSproxyGF) spec _args)
  ;----------------------------------------------------------------------
  ; Don't need any arguments - just use the specializer (which can
  ; be a string or a symbol)
  ;----------------------------------------------------------------------
  makeInstance('CCSdbobjectSpecializer ?objType get_string(spec))
)
defmethod(ilEquivalentSpecializers ((gf CCSproxyGF) 
    (spec1 CCSdbobjectSpecializer) (spec2 CCSdbobjectSpecializer))
  spec1->objType==spec2->objType
)
defmethod(ilArgMatchesSpecializer ((gf CCSproxyGF) (spec CCSdbobjectSpecializer)
    (arg dbobject))
  spec->objType==arg->objType
)
;------------------------------------------------------------------------
; Note that the generic function is defined indicating that it uses a
; different class other than ilGenericFunction. This means that the
; custom specializer methods above will be used
;------------------------------------------------------------------------
defgeneric(CCSgetFullObjType (obj) ?genericFunctionClass CCSproxyGF
  when(dbobjectp(obj)
    obj->objType
  )
)
;------------------------------------------------------------------------
; Methods using this approach. Note that this is not terribly transparent
; that they are dealing with database objects
;------------------------------------------------------------------------
defmethod(CCSgetFullObjType ((obj ("rect")))
  "rectangle"
)
defmethod(CCSgetFullObjType ((obj ("inst")))
  "instance"
)

Return to top
 ⠀
X