Changeset 35fc819 for doc/theses


Ignore:
Timestamp:
Dec 13, 2025, 4:56:22 PM (6 days ago)
Author:
Peter A. Buhr <pabuhr@…>
Branches:
master
Children:
5d300ba
Parents:
67748f9
Message:

more array proofreading

Location:
doc/theses/mike_brooks_MMath
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • doc/theses/mike_brooks_MMath/array.tex

    r67748f9 r35fc819  
    22\label{c:Array}
    33
    4 Arrays in C are possibly the single most misunderstood and incorrectly used feature in the language, resulting in the largest proportion of runtime errors and security violations.
     4Arrays in C are possibly the single most misunderstood and incorrectly used feature in the language \see{\VRef{s:Array}}, resulting in the largest proportion of runtime errors and security violations.
    55This chapter describes the new \CFA language and library features that introduce a length-checked array type, @array@, to the \CFA standard library~\cite{Cforall}.
    66
     
    1313\label{s:ArrayIntro}
    1414
    15 The new \CFA array is declared by instantiating the generic @array@ type,
    16 much like instantiating any other standard-library generic type (such as \CC @vector@),
    17 though using a new style of generic parameter.
     15The new \CFA array is declared by instantiating the generic @array@ type, much like instantiating any other standard-library generic type (such as \CC @vector@), using a new style of generic parameter.
    1816\begin{cfa}
    1917@array( float, 99 )@ x;                                 $\C[2.5in]{// x contains 99 floats}$
     
    2422void f( @array( float, 42 )@ & p ) {}   $\C{// p accepts 42 floats}$
    2523f( x );                                                                 $\C{// statically rejected: type lengths are different, 99 != 42}$
     24
    2625test2.cfa:3:1 error: Invalid application of existing declaration(s) in expression.
    2726Applying untyped:  Name: f ... to:  Name: x
     
    3736g( x, 0 );                                                              $\C{// T is float, N is 99, dynamic subscript check succeeds}$
    3837g( x, 1000 );                                                   $\C{// T is float, N is 99, dynamic subscript check fails}$
     38
    3939Cforall Runtime error: subscript 1000 exceeds dimension range [0,99) $for$ array 0x555555558020.
    4040\end{cfa}
     
    110110forall( T & | sized(T) )
    111111T * alloc() {
    112         return @(T *)@malloc( @sizeof(T)@ );
     112        return @(T *)@malloc( @sizeof(T)@ );    // C malloc
    113113}
    114114\end{cfa}
     
    132132The loops follow the familiar pattern of using the variable @dim@ to iterate through the arrays.
    133133Most importantly, the type system implicitly captures @dim@ at the call of @f@ and makes it available throughout @f@ as @N@.
    134 The example shows @dim@ adapting into a type-system managed length at the declarations of @x@, @y@, and @result@, @N@ adapting in the same way at @f@'s loop bound, and a pass-thru use of @dim@ at @f@'s declaration of @ret@.
     134The example shows @dim@ adapting into a type-system managed length at the declarations of @x@, @y@, and @result@; @N@ adapting in the same way at @f@'s loop bound; and a pass-thru use of @dim@ at @f@'s declaration of @ret@.
    135135Except for the lifetime-management issue of @result@, \ie explicit @free@, this program has eliminated both the syntactic and semantic problems associated with C arrays and their usage.
    136136The result is a significant improvement in safety and usability.
     
    148148\end{itemize}
    149149
    150 \VRef[Figure]{f:TemplateVsGenericType} shows @N@ is not the same as a @size_t@ declaration in a \CC \lstinline[language=C++]{template}.
     150\VRef[Figure]{f:TemplateVsGenericType} shows @N@ is not the same as a @size_t@ declaration in a \CC \lstinline[language=C++]{template}.\footnote{
     151The \CFA program requires a snapshot declaration for \lstinline{n} to compile, as described at the end of \Vref{s:ArrayTypingC}.}
    151152\begin{enumerate}[leftmargin=*]
    152153\item
    153154The \CC template @N@ can only be a compile-time value, while the \CFA @N@ may be a runtime value.
    154155\item
    155 \CC does not allow a template function to be nested, while \CFA lets its polymorphic functions to be nested.
     156\CC does not allow a template function to be nested, while \CFA allows polymorphic functions be nested.
    156157Hence, \CC precludes a simple form of information hiding.
    157158\item
     
    176177% mycode/arrr/thesis-examples/check-peter/cs-cpp.cpp, v10
    177178\end{enumerate}
    178 The \CC template @array@ type mitigates points \VRef[]{p:DimensionPassing} and \VRef[]{p:ArrayCopy}, but it is also trying to accomplish a similar mechanism to \CFA @array@.
     179The \CC template @std::array@ tries to accomplish a similar mechanism to \CFA @array@.
     180It is an aggregate type with the same semantics as a @struct@ holding a C-style array \see{\VRef{s:ArraysCouldbeValues}}, which mitigates points \VRef[]{p:DimensionPassing} and \VRef[]{p:ArrayCopy}.
    179181
    180182\begin{figure}
    181 \begin{tabular}{@{}l@{\hspace{20pt}}l@{}}
     183\begin{tabular}{@{}ll@{}}
    182184\begin{c++}
    183185
     
    187189}
    188190int main() {
    189 
    190         int ret[10], x[10];
    191         for ( int i = 0; i < 10; i += 1 ) x[i] = i;
    192         @copy<int, 10 >( ret, x );@
    193         for ( int i = 0; i < 10; i += 1 )
     191        const size_t  n = 10;   // must be constant
     192        int ret[n], x[n];
     193        for ( int i = 0; i < n; i += 1 ) x[i] = i;
     194        @copy<int, n >( ret, x );@
     195        for ( int i = 0; i < n; i += 1 )
    194196                cout << ret[i] << ' ';
    195197        cout << endl;
     
    203205                for ( i; N ) ret[i] = x[i];
    204206        }
    205 
    206         const int n = promptForLength();
     207        size_t  n;
     208        sin | n;
    207209        array( int, n ) ret, x;
    208210        for ( i; n ) x[i] = i;
     
    228230When the argument lengths themselves are statically unknown,
    229231the static check is conservative and, as always, \CFA's casting lets the programmer use knowledge not shared with the type system.
    230 \begin{tabular}{@{\hspace{0.5in}}l@{\hspace{1in}}l@{}}
    231 \lstinput{90-97}{hello-array.cfa}
    232 &
    233 \lstinput{110-117}{hello-array.cfa}
    234 \end{tabular}
    235 
    236 \noindent
    237 This static check's full rules are presented in \VRef[Section]{s:ArrayTypingC}.
     232\lstinput{90-96}{hello-array.cfa}
     233This static check's rules are presented in \VRef[Section]{s:ArrayTypingC}.
    238234
    239235Orthogonally, the \CFA array type works within generic \emph{types}, \ie @forall@-on-@struct@.
    240236The same argument safety and the associated implicit communication of array length occurs.
    241237Preexisting \CFA allowed aggregate types to be generalized with type parameters, enabling parameterizing of element types.
    242 This has been extended to allow parameterizing by dimension.
    243 Doing so gives a refinement of C's ``flexible array member''~\cite[\S~6.7.2.1.18]{C11}.
     238This feature is extended to allow parameterizing by dimension.
     239Doing so gives a refinement of C's ``flexible array member''~\cite[\S~6.7.2.1.18]{C11}:
    244240\begin{cfa}
    245241struct S {
     
    264260\end{cfa}
    265261This ability to avoid casting and size arithmetic improves safety and usability over C flexible array members.
    266 Finally, inputs and outputs are given at the bottom for different sized schools.
     262Finally, inputs and outputs are given on the right for different sized schools.
    267263The example program prints the courses in each student's preferred order, all using the looked-up display names.
    268264
    269265\begin{figure}
    270 \begin{cquote}
    271 \lstinput{50-55}{hello-accordion.cfa}
     266\begin{lrbox}{\myboxA}
     267\begin{tabular}{@{}l@{}}
     268\lstinput{50-55}{hello-accordion.cfa} \\
    272269\lstinput{90-98}{hello-accordion.cfa}
    273 \ \\
    274 @$ cat school1@
    275 \lstinput{}{school1}
    276 
    277 @$ ./a.out < school1@
    278 \lstinput{}{school1.out}
    279 
    280 @$ cat school2@
    281 \lstinput{}{school2}
    282 
    283 @$ ./a.out < school2@
    284 \lstinput{}{school2.out}
    285 \end{cquote}
     270\end{tabular}
     271\end{lrbox}
     272
     273\begin{lrbox}{\myboxB}
     274\begin{tabular}{@{}l@{}}
     275@$ cat school1@ \\
     276\lstinputlisting{school1} \\
     277@$ ./a.out < school1@ \\
     278\lstinputlisting{school1.out} \\
     279@$ cat school2@ \\
     280\lstinputlisting{school2} \\
     281@$ ./a.out < school2@ \\
     282\lstinputlisting{school2.out}
     283\end{tabular}
     284\end{lrbox}
     285
     286\setlength{\tabcolsep}{10pt}
     287\begin{tabular}{@{}ll@{}}
     288\usebox\myboxA
     289&
     290\usebox\myboxB
     291\end{tabular}
    286292
    287293\caption{\lstinline{School} Example, Input and Output}
     
    290296
    291297When a function operates on a @School@ structure, the type system handles its memory layout transparently.
    292 \lstinput{30-37}{hello-accordion.cfa}
     298\lstinput{30-36}{hello-accordion.cfa}
    293299In the example, function @getPref@ returns, for the student at position @is@, what is the position of their @pref@\textsuperscript{th}-favoured class?
    294300
     
    296302\section{Dimension Parameter Implementation}
    297303
    298 The core of the preexisting \CFA compiler already had the ``heavy equipment'' needed to provide the feature set just reviewed (up to bugs in cases not yet exercised).
     304The core of the preexisting \CFA compiler already has the ``heavy equipment'' needed to provide the feature set just reviewed (up to bugs in cases not yet exercised).
    299305To apply this equipment in tracking array lengths, I encoded a dimension (array's length) as a type.
    300306The type in question does not describe any data that the program actually uses at runtime.
     
    323329\begin{itemize}[leftmargin=*]
    324330\item
    325         Resolver provided values for a used declaration's type-system variables, gathered from type information in scope at the usage site.
    326 \item
    327         The box pass, encoding information about type parameters into ``extra'' regular parameters/arguments on declarations and calls.
     331        Resolver provided values for a declaration's type-system variables, gathered from type information in scope at the usage site.
     332\item
     333        The box pass, encoding information about type parameters into ``extra'' regular parameters and arguments on declarations and calls.
    328334        Notably, it conveys the size of a type @foo@ as a @__sizeof_foo@ parameter, and rewrites the @sizeof(foo)@ expression as @__sizeof_foo@, \ie a use of the parameter.
    329335\end{itemize}
     
    331337The rules for resolution had to be restricted slightly, in order to achieve important refusal cases.
    332338This work is detailed in \VRef[Section]{s:ArrayTypingC}.
    333 However, the resolution--boxing scheme, in its preexisting state, was already equipped to work on (desugared) dimension parameters.
     339However, the resolution--boxing scheme, in its preexisting state, is equipped to work on (desugared) dimension parameters.
    334340The following discussion explains the desugaring and how correctly lowered code results.
    335341
     
    357363\end{enumerate}
    358364The chosen solution is to encode the value @N@ \emph{as a type}, so items 1 and 2 are immediately available for free.
    359 Item 3 needs a way to recover the encoded value from a (valid) type (and to reject invalid types occurring here).
     365Item 3 needs a way to recover the encoded value from a (valid) type and to reject invalid types.
    360366Item 4 needs a way to produce a type that encodes the given value.
    361367
     
    416422        The type @thing(N)@ is (replaced by @void *@, but thereby effectively) gone.
    417423\item
    418         The @sout...@ expression (being an application of the @?|?@ operator) has a regular variable (parameter) usage for its second argument.
     424        The @sout...@ expression has a regular variable (parameter) usage for its second argument.
    419425\item
    420426        Information about the particular @thing@ instantiation (value 10) is moved, from the type, to a regular function-call argument.
     
    455461\begin{cfa}
    456462enum { n = 42 };
    457 float x[@n@];   // or just 42
    458 float (*xp1)[@42@] = &x;    // accept
    459 float (*xp2)[@999@] = &x;   // reject
     463float x[@n@];   $\C{// or just 42}$
     464float (*xp1)[@42@] = &x;    $\C{// accept}$
     465float (*xp2)[@999@] = &x;   $\C{// reject}$
    460466warning: initialization of 'float (*)[999]' from incompatible pointer type 'float (*)[42]'
    461467\end{cfa}
    462468When a variable is involved, C and \CFA take two different approaches.
    463 Today's C compilers accept the following without warning.
     469Today's C compilers accept the following without a warning.
    464470\begin{cfa}
    465471static const int n = 42;
     
    482488The way the \CFA array is implemented, the type analysis for this case reduces to a case similar to the earlier C version.
    483489The \CFA compiler's compatibility analysis proceeds as:
    484 \begin{itemize}[parsep=0pt]
     490\begin{itemize}[leftmargin=*,parsep=0pt]
    485491\item
    486492        Is @array( float, 999 )@ type-compatible with @array( float, n )@?
     
    510516        in order to preserve the length information that powers runtime bound-checking.}
    511517Therefore, the need to upgrade legacy C code is low.
    512 Finally, if this incompatibility is a problem onboarding C programs to \CFA, it is should be possible to change the C type check to a warning rather than an error, acting as a \emph{lint} of the original code for a missing type annotation.
     518Finally, if this incompatibility is a problem onboarding C programs to \CFA, it should be possible to change the C type check to a warning rather than an error, acting as a \emph{lint} of the original code for a missing type annotation.
    513519
    514520To handle two occurrences of the same variable, more information is needed, \eg, this is fine,
     
    516522int n = 42;
    517523float x[@n@];
    518 float (*xp)[@n@] = x;   // accept
     524float (*xp)[@n@] = x;   $\C{// accept}$
    519525\end{cfa}
    520526where @n@ remains fixed across a contiguous declaration context.
    521 However, intervening dynamic statement cause failures.
     527However, intervening dynamic statements can cause failures.
    522528\begin{cfa}
    523529int n = 42;
    524530float x[@n@];
    525 @n@ = 999; // dynamic change
    526 float (*xp)[@n@] = x;   // reject
    527 \end{cfa}
    528 However, side-effects can occur in a contiguous declaration context.
     531@n@ = 999; $\C{// dynamic change}$
     532float (*xp)[@n@] = x;   $\C{// reject}$
     533\end{cfa}
     534As well, side-effects can even occur in a contiguous declaration context.
    529535\begin{cquote}
    530536\setlength{\tabcolsep}{20pt}
     
    536542void f() {
    537543        float x[@n@] = { g() };
    538         float (*xp)[@n@] = x;   // reject
     544        float (*xp)[@n@] = x;                   // reject
    539545}
    540546\end{cfa}
     
    544550int @n@ = 42;
    545551void g() {
    546         @n@ = 99;
     552        @n@ = 999;              // accept
    547553}
    548554
     
    553559The issue here is that knowledge needed to make a correct decision is hidden by separate compilation.
    554560Even within a translation unit, static analysis might not be able to provide all the information.
    555 However, if the example uses @const@, the check is possible.
     561However, if the example uses @const@, the check is possible even though the value is unknown.
    556562\begin{cquote}
    557563\setlength{\tabcolsep}{20pt}
     
    563569void f() {
    564570        float x[n] = { g() };
    565         float (*xp)[n] = x;   // reject
     571        float (*xp)[n] = x;             // accept
    566572}
    567573\end{cfa}
     
    571577@const@ int n = 42;
    572578void g() {
    573         @n = 99@; // allowed
     579        @n = 999@;              // reject
    574580}
    575581
     
    749755\end{comment}
    750756
    751 The conservatism of the new rule set can leave a programmer needing a recourse, when needing to use a dimension expression whose stability argument is more subtle than current-state analysis.
     757The conservatism of the new rule set can leave a programmer requiring a recourse, when needing to use a dimension expression whose stability argument is more subtle than current-state analysis.
    752758This recourse is to declare an explicit constant for the dimension value.
    753759Consider these two dimension expressions, whose uses are rejected by the blunt current-state rules:
     
    755761void f( int @&@ nr, @const@ int nv ) {
    756762        float x[@nr@];
    757         float (*xp)[@nr@] = &x;   // reject: nr varying (no references)
     763        float (*xp)[@nr@] = &x;                 // reject: nr varying (no references)
    758764        float y[@nv + 1@];
    759         float (*yp)[@nv + 1@] = &y;   // reject: ?+? unpredictable (no functions)
     765        float (*yp)[@nv + 1@] = &y;             // reject: ?+? unpredictable (no functions)
    760766}
    761767\end{cfa}
    762768Yet, both dimension expressions are reused safely.
    763 The @nr@ reference is never written, not volatile meaning no implicit code (load) between declarations, and control does not leave the function between the uses.
     769The @nr@ reference is never written, no implicit code (load) between declarations, and control does not leave the function between the uses.
    764770As well, the build-in @?+?@ function is predictable.
    765771To make these cases work, the programmer must add the follow constant declarations (cast does not work):
     
    768774        @const int nx@ = nr;
    769775        float x[nx];
    770         float (*xp)[nx] = & x;   // accept
     776        float (*xp)[nx] = & x;                  // accept
    771777        @const int ny@ = nv + 1;
    772778        float y[ny];
    773         float (*yp)[ny] = & y;   // accept
     779        float (*yp)[ny] = & y;                  // accept
    774780}
    775781\end{cfa}
     
    808814\end{cfa}
    809815Dimension hoisting already existed in the \CFA compiler.
    810 But its was buggy, particularly with determining, ``Can hoisting the expression be skipped here?'', for skipping this hoisting is clearly desirable in some cases.
     816However, it was buggy, particularly with determining, ``Can hoisting the expression be skipped here?'', for skipping this hoisting is clearly desirable in some cases.
    811817For example, when a programmer has already hoisted to perform an optimization to prelude duplicate code (expression) and/or expression evaluation.
    812818In the new implementation, these cases are correct, harmonized with the accept/reject criteria.
     
    820826\item
    821827Flexible-stride memory:
    822 this model has complete independence between subscripting ordering and memory layout, offering the ability to slice by (provide an index for) any dimension, \eg slice a plane, row, or column, \eg @c[3][*][*]@, @c[3][4][*]@, @c[3][*][5]@.
     828this model has complete independence between subscript ordering and memory layout, offering the ability to slice by (provide an index for) any dimension, \eg slice a row, column, or plane, \eg @c[3][4][*]@, @c[3][*][5]@, @c[3][*][*]@.
    823829\item
    824830Fixed-stride memory:
     
    839845Style 3 is the inevitable target of any array implementation.
    840846The hardware offers this model to the C compiler, with bytes as the unit of displacement.
    841 C offers this model to its programmer as pointer arithmetic, with arbitrary sizes as the unit.
     847C offers this model to programmers as pointer arithmetic, with arbitrary sizes as the unit.
    842848Casting a multidimensional array as a single-dimensional array/pointer, then using @x[i]@ syntax to access its elements, is still a form of pointer arithmetic.
    843849
    844 Now stepping into the implementation of \CFA's new type-1 multidimensional arrays in terms of C's existing type-2 multidimensional arrays, it helps to clarify that even the interface is quite low-level.
    845 A C/\CFA array interface includes the resulting memory layout.
    846 The defining requirement of a type-2 system is the ability to slice a column from a column-finest matrix.
    847 The required memory shape of such a slice is fixed, before any discussion of implementation.
    848 The implementation presented here is how the \CFA array-library wrangles the C type system, to make it do memory steps that are consistent with this layout while not affecting legacy C programs.
     850To step into the implementation of \CFA's new type-1 multidimensional arrays in terms of C's existing type-2 multidimensional arrays, it helps to clarify that the interface is low-level, \ie a C/\CFA array interface includes the resulting memory layout.
     851Specifically, the defining requirement of a type-2 system is the ability to slice a column from a column-finest matrix.
     852Hence, the required memory shape of such a slice is fixed, before any discussion of implementation.
     853The implementation presented here is how the \CFA array-library wrangles the C type system to make it do memory steps that are consistent with this layout while not affecting legacy C programs.
    849854% TODO: do I have/need a presentation of just this layout, just the semantics of -[all]?
    850855
     
    874879\lstinput[aboveskip=0pt]{145-145}{hello-md.cfa}
    875880The nontrivial slicing in this example now allows passing a \emph{noncontiguous} slice to @print1d@, where the new-array library provides a ``subscript by all'' operation for this purpose.
    876 In a multi-dimensional subscript operation, any dimension given as @all@ is a placeholder, \ie ``not yet subscripted by a value'', waiting for such a value, implementing the @ar@ trait.
     881In a multi-dimensional subscript operation, any dimension given as @all@ is a placeholder, \ie ``not yet subscripted by a value'', waiting for a value implementing the @ar@ trait.
    877882\lstinput{150-151}{hello-md.cfa}
    878883
  • doc/theses/mike_brooks_MMath/programs/hello-accordion.cfa

    r67748f9 r35fc819  
    3131int getPref( @School( C, S ) & school@, int is, int pref ) {
    3232        for ( ic; C ) {
    33                 if ( pref == @school.preferences@[ic][is]; ) return ic; $\C{// offset calculation implicit}$
     33                if ( pref == @school.preferences@[ic][is] ) return ic; $\C{// offset calculation implicit}$
    3434        }
    35         assert( false );
     35        assert( false );        // must find a match
    3636}
    3737
     
    8989
    9090        for ( is; ns ) {
    91                 sout | school.student_ids[is] | ": " | nonl;
     91                sout | school.student_ids[is] | ": ";
    9292                for ( pref; 1 ~= nc ) {
    9393                        int ic = getPref( school, is, pref );
  • doc/theses/mike_brooks_MMath/programs/hello-array.cfa

    r67748f9 r35fc819  
    8989
    9090forall( [M], [N] )
    91 void bad( array(float, M) &x,
    92                 array(float, N) &y ) {
     91void f( array(float, M) &x, array(float, N) &y ) {
    9392        f( x, x );              $\C[0.5in]{// ok}$
    9493        f( y, y );              $\C{// ok}$
    95 
    9694        f( x, y );              $\C{// error}\CRT$
     95        if ( M == N ) f( x, @(array( float, M ) &)@y ); $\C{// ok}\CRT$
    9796}
    98 
    9997#endif
    10098
     
    109107
    110108forall( [M], [N] )
    111 void bad_fixed( array( float, M ) & x,
    112                 array( float, N ) & y ) {
     109void f( array( float, M ) & x, array( float, N ) & y ) {
    113110        f( x, x );              $\C[0.5in]{// ok}$
    114111        f( y, y );              $\C{// ok}$
    115         if ( M == N )
    116                 f( x, @(array( float, M ) &)@y ); $\C{// ok}\CRT$
     112        if ( M == N ) f( x, @(array( float, M ) &)@y ); $\C{// ok}\CRT$
    117113}
    118114
     
    132128
    133129forall( [M], [N] )
    134 void bad_ok_only( array(float, M) &x,
    135                 array(float, N) &y ) {
     130void bad_ok_only( array(float, M) &x, array(float, N) &y ) {
    136131        f( x, x );
    137132        f( y, y );
  • doc/theses/mike_brooks_MMath/programs/school1

    r67748f9 r35fc819  
    113 courses, 2 students
    2 c\s     90111111 90222222
    3 ENGL101        3        2
    4 PHYS101        1        3
    5 CHEM101        2        1
     2c\s              90111111 90222222
     3ENGL101                 3               2
     4PHYS101                 1               3
     5CHEM101                2               1
  • doc/theses/mike_brooks_MMath/programs/school1.out

    r67748f9 r35fc819  
    1 90111111: PHYS101 CHEM101 ENGL101
    2 90222222: CHEM101 ENGL101 PHYS101
     190111111:
     2PHYS101 CHEM101 ENGL101
     390222222:
     4CHEM101 ENGL101 PHYS101
  • doc/theses/mike_brooks_MMath/programs/school2

    r67748f9 r35fc819  
    115 courses, 3 students
    2 c\s     90111111 90222222 90333333
    3 ENGL101        3        2        3
    4 PHYS101        1        3        4
    5 CHEM101        2        1        5
    6 PHIL101        4        5        2
    7 MATH499        5        4        1
     2c\s              90111111 90222222 90333333
     3ENGL101                 3               2               3
     4PHYS101                 1               3               4
     5CHEM101                2               1               5
     6PHIL101                   4               5               2
     7MATH499                 5               4               1
  • doc/theses/mike_brooks_MMath/programs/school2.out

    r67748f9 r35fc819  
    1 90111111: PHYS101 CHEM101 ENGL101 PHIL101 MATH499
    2 90222222: CHEM101 ENGL101 PHYS101 MATH499 PHIL101
    3 90333333: MATH499 PHIL101 ENGL101 PHYS101 CHEM101
     190111111:
     2PHYS101 CHEM101 ENGL101 PHIL101 MATH499
     390222222:
     4CHEM101 ENGL101 PHYS101 MATH499 PHIL101
     590333333:
     6MATH499 PHIL101 ENGL101 PHYS101 CHEM101
Note: See TracChangeset for help on using the changeset viewer.