Lab 4 : Type Checking and Semantic Analysis

The aim of this lab is to perform a semantic analysis of the internal form we built in the previous lab, especially type checking.

Checking static semantics

Static semantics concern things that a context-free grammar can’t handle. Examples of this include checking that:

  • Variables are declared this was actually already done for you in parser.y. Ideally it should have been done in this phase, since that is where it conceptually belongs. Why isn’t it?

  • Operands have compatible types.

  • The number of arguments and their types at sub-routine calls are consistent with declarations.

  • All functions return something.

In this way we try to follow the semantic rules that have been stipulated for the language, for instance:

“If both operands of arithmetic operations such as addition, subtraction or multiplication are integers, the result should also be an integer.”

Type information

Information for type checking comes from:

  • the symbol table, where we store, for example, the type of a variable, a list of formal parameters for a procedure, what type a function returns, etc.

  • synthesised attributes, e.g. for arithmetic expressions. As operands of arithmetic operations may be arbitrary arithmetic expressions, we must synthesise information on their types. Where possible, this is done in the AST class constructors, but there are places where the type can’t be known at node creation time, and in these cases it has to be done during this phase.

In DIESEL we have to store, for example, the type of arithmetic expressions so that type checking can be performed on expressions of the form (1 + 2) div (4 / 2) (which actually results in a type conflict). You can, of course, synthesise the type information and throw it away when you have checked the semantics, but then you may be forced to traverse the tree each time you need to determine the type of an expression.

Type conversion

It may also be useful to allow the type checking to extend the AST. The semantics in DIESEL allow us, for example, to add floating-point numbers to integers. Since we did not concern ourselves with type checking while constructing the AST, we are very likely to have partial trees like the one shown on the left of the figure below, for example. The value in the nodes’ type field has been written at each node. Even when doing type checking we can see that we will have to change the integer in x to a floating-point number before performing the addition. If we let the tree stay as it is, we will have to take the addition’s type into consideration when deciding how to generate quadruples for the right-hand side child. Alternatively we have to know the types of the operands when we generate quadruples for addition. To make quadruple generation as simple as possible, we add a type translation node (ast_cast) which has a child of integer type but which is itself of floating-point type. This is known as casting the integer number to real type and is an example of type conversion. Since DIESEL is a rather simple language with few data types, this is the only kind of type conversion we will need to worry about. In a language which allows pointers to objects (like C++), type casting and conversion can be a whole science in itself. Anyway, after semantic analysis which includes adding this node we get the tree on the right:

Example of type casting.

Quadruple generation for the modified tree will thus be simpler. All addition- and variable- nodes can now be translated to the same sequence of quadruples regardless of the context.

Type specifications for DIESEL

Here is the grammar (in reverse order) for DIESEL, with a description of which type restrictions apply for each production (Note that the extra error productions you were supposed to add in the previous lab are not included here). We perform type checking one block at a time, by use of a recursive call type_check() which is passed from the root downwards in the AST representing the block. You will need to keep in mind which AST structures these productions correspond to (you should have gained a good understanding of that in the previous lab), and implement type checking and synthesizing where appropriate. Keep a copy of parser.y handy while reading this. The tokens generally appear as their DIESEL symbols rather than their token names, e.g. ( instead of T_LEFTPAR, due to space considerations.

The numbers are just for ease of reference. bison notation actually groups several right-hand sides together under one left-hand side.

  1. <id>T_IDENT

    If the identifier is not declared, an error message is issued. The semantic action for this production can be found in parser.y, and has already been written. Ideally this check should take place at the same time as the rest of the type checking instead of at AST creation time. Why doesn’t it? The same comment applies for the following seven productions.

  2. <array_id><id>

    If the identifier is not declared as an array, an error message is issued.

  3. <func_id><id>

    If the identifier is not declared as a function, an error message is issued.

  4. <proc_id><id>

    If the identifier is not declared as a procedure, an error message is issued.

  5. <rvar_id><id>

    If the identifier is not declared as a variable, parameter, or constant, an error message is issued. This production signifies any variable-like identifier that can appear on the right-hand side of an assignment statement.

  6. <lvar_id><id>

    If the identifier is not declared as a variable or parameter, an error message is issued. This production signifies any variable-like identifier that can appear on the left-hand side of an assignment statement. Comparing it to the previous production we see that a constant can’t be assigned to, which makes sense. This duality is also reflected below in the productions for lvariable and rvariable, respectively.

  7. <const_id><id>

    If the identifier is not declared as a constant, an error message is issued.

  8. <type_id><id>

    If the identifier is not declared as a name type, an error message is issued.

  9. <integer>T_INTNUM

  10. <real>T_REALNUM

  11. <func_call><func_id> ( <opt_expr_list> )

    The formal parameters (in the symbol table) must agree with the actual ones (in the opt_expr_list), both in type and number. The result must be the same type as the function’s return type.

  12. <factor>( <expr> )

  13. <factor>NOT <factor>

    If the operand is not of integer type, an error message is issued. The result is of integer type.

  14. <factor><real>

  15. <factor><integer>

  16. <factor><func_call>

  17. <factor><rvariable>

  18. <term><factor>

  19. <term><term> MOD <factor>

    Both operands must be of integer type. The result is of integer type.

  20. <term><term> DIV <factor>

    Both operands must be of integer type. The result is of integer type.

  21. <term><term> / <factor>

    The result is of real type. Both operands must be of real type or be transformed to it.

  22. <term><term> * <factor>

    The result has the same type as both the operands, possibly after an operand has been transformed.

  23. <term><term> AND <factor>

    Both operands must be of integer type. The result is of integer type.

  24. <simple_expr><term>

  25. <simple_expr> - <term>

    This is unary minus, as opposed to binary minus below.

  26. <simple_expr>+ <term>

  27. <simple_expr><simple_expr> - <term>

    The result has the same type as both the operands, possibly after an operand has been transformed.

  28. <simple_expr><simple_expr> + <term>

    The result has the same type as both the operands, possibly after an operand has been transformed.

  29. <simple_expr><simple_expr> OR <term>

    Both operands must be of integer type. The result is of integer type.

  30. <expr><simple_expr>

  31. <expr><expr> > <simple_expr>

    The result is of integer type. Both operands have the same type, possibly after type transformation.

  32. <expr><expr> < <simple_expr>

    The result is of integer type. Both operands have the same type, possibly after type transformation.

  33. <expr><expr> <> <simple_expr>

    The result is of integer type. Both operands have the same type, possibly after type transformation.

  34. <expr><expr> = <simple_expr>

    The result is of integer type. Both operands have the same type, possibly after type transformation.

  35. <expr_list><expr>

  36. <expr_list><expr_list> , <expr>

  37. <opt_expr_list>

  38. <opt_expr_list><expr_list>

  39. <else_part>

  40. <else_part>ELSE <stmt_list>

  41. <elsif>ELSIF <expr> THEN <stmt_list>

    The predicate must be of integer type.

  42. <elsif_list>

  43. <elsif_list><elsif_list> <elsif>

  44. <rvariable><rvar_id>

  45. <rvariable><array_id>  ``[ <expr> ]

    If the type of <expr> is different from the index type of <array_id>, an error message is issued.

  46. <lvariable><lvar_id>

  47. <lvariable><array_id> [ <expr> ]

    If the type of <expr> is different from the index type of <array_id>, an error message is issued.

  48. <stmt>

  49. <stmt>RETURN

    Can only be executed in procedures.

  50. <stmt>RETURN <expr>

    Can only be executed in functions. The type of <expr> must agree with the type of the function.

  51. <stmt><lvariable> := <expr>

    It is not allowed to assign a real expression to an integer variable. Real variables can, however, be assigned integer expressions after type transformation. Note the use of lvariable here.

  52. <stmt><proc_id> ( <opt_expr_list> )

    All actual parameters must agree in type and number with the formal parameters.

  53. <stmt>WHILE <expr> DO <stmt_list> END

    The predicate must be of integer type.

  54. <stmt>IF <expr> THEN <stmt_list> <elsif_list> <else_part> END

    The predicate must be of integer type.

  55. <stmt_list><stmt>

  56. <stmt_list><stmt_list> ; <stmt>

  57. <comp_stmt>BEGIN <stmt_list> END

  58. <param>T_IDENT : <type_id>

  59. <parm_list><param>

  60. <parm_list><param_list> ; <param>

  61. <opt_param_list>

  62. <opt_param_list>( <param_list> )

  63. <func_head>FUNCTION T_IDENT

  64. <proc_head>PROCEDURE T_IDENT

  65. <func_decl><func_head> <opt_param_list> : <type_id> ; <const_part> <variable_part>

  66. <proc_decl><proc_head> <opt_param_list> ; <const_part> <variable_part>

  67. <subprog_decl><func_decl> <subprog_part> <comp_stmt> ;

    A function must have at least one RETURN statement. This particular check has already been written in the skeleton program. Take a look at it.

  68. <subprog_decl><proc_decl> <subprog_part> <comp_stmt> ;

    When this production is reduced, we have created an AST for one block, i.e., it is time to start type checking. This is already written, but currently commented out, in parser.y. Take a look.

  69. <subprog_decls><subprog_decl>

  70. <subprog_decls><subprog_decls> <subprog_decl>

  71. <subprog_part>

  72. <subprog_part><subprog_decls>

  73. <var_decl>T_IDENT : <type_id> ;

  74. <var_decl>T_IDENT : ARRAY [ <integer> ] OF <type_id> ;

  75. <var_decl>T_IDENT : ARRAY [ <const_id> ] OF <type_id> ;

    The index must be of integer type. This is already written for you.

  76. <var_decls><var_decl>

  77. <var_decls><var_decls> <var_decl>

  78. <variable_part>

  79. <variable_part>VAR <var_decls>

  80. <const_decl>T_IDENT = <integer> ;

  81. <const_decl>T_IDENT = <real> ;

  82. <const_decl>T_IDENT = T_STRINGCONST ;

  83. <const_decl>T_IDENT = <const_id> ;

  84. <const_decls><const_decl>

  85. <const_decls><const_decls> <const_decl>

  86. <const_part>

  87. <const_part>CONST <const_decls>

  88. <proghead>PROGRAM T_IDENT

  89. <prog_decl><proghead> ; <const_part> <variable_part>

  90. <program><prog_decl> <subprog_part> <comp_stmt> .

Implementation

Your job is to implement type checking and semantic analysis by editing the file semantic.cc (and, in the quite likely case of your adding internal methods, the file semantic.hh as well).

In our implementation, type checking is done for one block at a time, and is started by a call from parser.y in the appropriate production to do_typecheck() in the semantic class. This call will perform some block-specific initialisation, and then call the method type_check() in the root node of the AST we are checking. This call will propagate down through the AST and as nodes are passed, their types are checked and their attributes are synthesized. The order of traversal is important, since you obviously can’t synthesize information about a node until you have information about its children.

The semantic class is a standalone class which is really only used as a wrapper for type checking. It has a global instantiation, called type_checker, which is declared in semantic.cc. The actual type_check() methods are part of the AST nodes, but their implementations lie in the semantic.cc file. This makes it easy to modify the type checking implementation (by replacing this file with another) without having to rewrite the entire AST class hierarchy.

Some of the methods have already been implemented; look at them to get an idea of what you are supposed to do. Most of them you are to complete yourself (look for Your code here comments). In some cases it might be convenient to add new methods to treat AST nodes which are very similar. If you do so, you will also need to add your methods to the semantic.hh file.

Files that you have to change

semantic.hh and semantic.cc

contain type-checking code implementation for the AST nodes as well as the declaration and implementation of the semantic class. These are the files you’re going to edit in this lab. They deal with type checking, type synthesizing, and parameter checking.

Other files of interest

All these files are the same as in lab 3:

parser.y

is the input file to bison. This is the file you edited in the last lab; it contains the call to do_typecheck().

ast.hh

contains the definitions for the AST nodes.

ast.cc

contains (part of) the implementations of the AST nodes.

optimize.hh and optimize.cc

contain optimizing code. You won’t need to edit these files in this lab.

quads.hh and quads.cc

contain quad generation code. You won’t need to edit these files in this lab.

codegen.hh and codegen.cc

contain assembler generation code. You won’t need to edit these files until the last lab.

error.hh, error.cc, symtab.hh, symbol.cc, symtab.cc, and scanner.l

use your versions from the earlier labs.

main.cc

this is the compiler wrapper, parsing flags and the like.

Makefile

and diesel use the same files as in the last lab.

Declarations and routines available in ast.hh, semantic.hh, and semantic.cc

The recursive type_check() method common to all the AST classes takes no arguments, and should return the type of the node it is called in. The declarations can be found in ast.hh. You will have to implement most of them yourself.

The semantic class (currently) only contains two methods:

class semantic

Public Functions

void check_parameters(ast_id*, ast_expr_list*)

Compare formal vs. actual parameters in function/procedure calls.

It is also responsible for making sure that parameters are type-checked. You will have to write this method yourself, and also figure out from where to call it.

void do_typecheck(symbol *env, ast_stmt_list *body)

Initiate type checking of a block of code.

Performs some initialisation before starting recursive type checking of a block.

The function has already been written for you.

Parameters
  • env – the symbol representing the block to be checked (i.e., a function or procedure symbol)

  • body – an ast_stmt_list node representing the block body to be checked.

Program specification

  1. Your program must synthesize type information correctly.

  2. Your program must give error messages at semantic conflicts.

  3. Your program must ensure that formal and actual parameters match.

  4. Your program must make sure that functions return a value and that procedures do not.

  5. Your program should extend the AST with type transformation nodes where appropriate.

  6. Type checking should be done one block at a time. Apart from checking for undeclared or wrongly declared identifiers and the call to do_typecheck(), no type checking code is to reside in parser.y.

Debugging help

As before, you can send all AST nodes to cout. The types of expressions are written out in the printout. Look at it to make sure your nodes are given the correct type and that you add cast nodes in the right places.

How do I know it’s correct?

Compare the AST before and after type checking has been performed. Study the node types and the cast nodes that were added.

Reporting your work

  • For the demonstration, run the test program for the two test cases semtest1.d and semtest2.d and discuss your code.

  • Hand in your code as described in Handing in Results.

Example execution

Running make lab4 will take the sample input and compare it to the sample output. You should test your code on more examples than this.

./diesel -b -p -f -y -a ../testpgm/semtest1.d
Listing 11 trace/semtest1.trace
Symbol table will be printed after compilation.
An AST will be printed for each block.
No optimization will be done.
No quads will be generated.

Unoptimized AST for "INDEX"
Statement list (preceding, last_stmt)
+-Statement list (preceding, last_stmt)
| +-NULL
| +-Assignment (left, right)
|   +-Id (J) [INTEGER]
|   +-Integer [10]
+-While (condition, body)
  +-Greater than (left, right) [INTEGER]
  | +-Id (J) [INTEGER]
  | +-Integer [0]
  +-Statement list (preceding, last_stmt)
    +-Statement list (preceding, last_stmt)
    | +-Statement list (preceding, last_stmt)
    | | +-NULL
    | | +-Assignment (left, right)
    | |   +-Indexed (id, index)
    | |   | +-Id (I_ARR) [INTEGER]
    | |   | +-Sub (left, right) [INTEGER]
    | |   |   +-Add (left, right) [INTEGER]
    | |   |   | +-Integer [1]
    | |   |   | +-Id (I) [INTEGER]
    | |   |   +-Integer [1]
    | |   +-Id (J) [INTEGER]
    | +-Assignment (left, right)
    |   +-Indexed (id, index)
    |   | +-Id (I_ARR) [INTEGER]
    |   | +-Indexed (id, index)
    |   |   +-Id (I_ARR) [INTEGER]
    |   |   +-Id (I) [INTEGER]
    |   +-Sub (left, right) [INTEGER]
    |     +-Indexed (id, index)
    |     | +-Id (I_ARR) [INTEGER]
    |     | +-Id (I) [INTEGER]
    |     +-Id (J) [INTEGER]
    +-Assignment (left, right)
      +-Id (J) [INTEGER]
      +-Sub (left, right) [INTEGER]
        +-Id (J) [INTEGER]
        +-Integer [1]

Unoptimized AST for "MAX"
Statement list (preceding, last_stmt)
+-NULL
+-If (condition, then, elsif, else)
  +-Greater than (left, right) [INTEGER]
  | +-Id (A) [INTEGER]
  | +-Id (B) [INTEGER]
  +-Statement list (preceding, last_stmt)
  | +-NULL
  | +-Return (value)
  |   +-Id (A) [INTEGER]
  +-Elsif list (preceding, last_elsif)
  | +-NULL
  | +-Elsif (condition, body)
  |   +-Greater than (left, right) [INTEGER]
  |   | +-Id (B) [INTEGER]
  |   | +-Id (A) [INTEGER]
  |   +-Statement list (preceding, last_stmt)
  |     +-NULL
  |     +-Return (value)
  |       +-Id (B) [INTEGER]
  +-Statement list (preceding, last_stmt)
    +-NULL
    +-Return (value)
      +-Id (A) [INTEGER]

Unoptimized AST for "DO_ZERO"
Statement list (preceding, last_stmt)
+-Statement list (preceding, last_stmt)
| +-NULL
| +-Assignment (left, right)
|   +-Id (Z) [INTEGER]
|   +-Id (ZERO) [INTEGER]
+-Procedure call (procedure, arguments)
  +-Id (WRITE) [VOID]
  +-Expression list (preceding, last_expr)
    +-NULL
    +-Id (Z) [INTEGER]

Unoptimized AST for "NASTY"
Statement list (preceding, last_stmt)
+-Statement list (preceding, last_stmt)
| +-Statement list (preceding, last_stmt)
| | +-Statement list (preceding, last_stmt)
| | | +-Statement list (preceding, last_stmt)
| | | | +-Statement list (preceding, last_stmt)
| | | | | +-Statement list (preceding, last_stmt)
| | | | | | +-NULL
| | | | | | +-Assignment (left, right)
| | | | | |   +-Id (I) [INTEGER]
| | | | | |   +-Unary minus (expr) [INTEGER]
| | | | | |     +-Add (left, right) [INTEGER]
| | | | | |       +-Integer [1]
| | | | | |       +-Integer [3]
| | | | | +-Assignment (left, right)
| | | | |   +-Id (I) [INTEGER]
| | | | |   +-Id (J) [INTEGER]
| | | | +-Assignment (left, right)
| | | |   +-Id (X) [REAL]
| | | |   +-Cast [REAL]
| | | |     +-Unary minus (expr) [INTEGER]
| | | |       +-Id (I) [INTEGER]
| | | +-Assignment (left, right)
| | |   +-Id (I) [INTEGER]
| | |   +-Idiv (left, right) [INTEGER]
| | |     +-Id (I) [INTEGER]
| | |     +-Id (J) [INTEGER]
| | +-Assignment (left, right)
| |   +-Id (X) [REAL]
| |   +-Divide (left, right) [REAL]
| |     +-Cast [REAL]
| |     | +-Id (I) [INTEGER]
| |     +-Cast [REAL]
| |       +-Id (J) [INTEGER]
| +-Procedure call (procedure, arguments)
|   +-Id (DO_ZERO) [VOID]
|   +-NULL
+-If (condition, then, elsif, else)
  +-And (left, right) [INTEGER]
  | +-Equal (left, right) [INTEGER]
  | | +-Id (X) [REAL]
  | | +-Cast [REAL]
  | |   +-Id (I) [INTEGER]
  | +-Less than (left, right) [INTEGER]
  |   +-Id (I) [INTEGER]
  |   +-Id (J) [INTEGER]
  +-Statement list (preceding, last_stmt)
  | +-Statement list (preceding, last_stmt)
  | | +-Statement list (preceding, last_stmt)
  | | | +-Statement list (preceding, last_stmt)
  | | | | +-NULL
  | | | | +-Assignment (left, right)
  | | | |   +-Id (I) [INTEGER]
  | | | |   +-Function call (function, arguments) [INTEGER]
  | | | |     +-Id (MAX) [INTEGER]
  | | | |     +-Expression list (preceding, last_expr)
  | | | |       +-Expression list (preceding, last_expr)
  | | | |       | +-NULL
  | | | |       | +-Id (I) [INTEGER]
  | | | |       +-Id (Y) [REAL]
  | | | +-Assignment (left, right)
  | | |   +-Id (J) [INTEGER]
  | | |   +-Integer [0]
  | | +-Assignment (left, right)
  | |   +-Id (X) [REAL]
  | |   +-Cast [REAL]
  | |     +-Integer [0]
  | +-Assignment (left, right)
  |   +-Id (Y) [REAL]
  |   +-Cast [REAL]
  |     +-Integer [0]
  +-NULL
  +-NULL

Unoptimized AST for global level
Statement list (preceding, last_stmt)
+-Statement list (preceding, last_stmt)
| +-Statement list (preceding, last_stmt)
| | +-Statement list (preceding, last_stmt)
| | | +-Statement list (preceding, last_stmt)
| | | | +-Statement list (preceding, last_stmt)
| | | | | +-NULL
| | | | | +-Assignment (left, right)
| | | | |   +-Id (A) [INTEGER]
| | | | |   +-Integer [1]
| | | | +-Assignment (left, right)
| | | |   +-Id (B) [INTEGER]
| | | |   +-Integer [2]
| | | +-Assignment (left, right)
| | |   +-Id (X) [REAL]
| | |   +-Cast [REAL]
| | |     +-Integer [1]
| | +-Assignment (left, right)
| |   +-Id (Y) [REAL]
| |   +-Real [3.14]
| +-Procedure call (procedure, arguments)
|   +-Id (INDEX) [VOID]
|   +-Expression list (preceding, last_expr)
|     +-NULL
|     +-Id (A) [INTEGER]
+-Procedure call (procedure, arguments)
  +-Id (NASTY) [VOID]
  +-Expression list (preceding, last_expr)
    +-Expression list (preceding, last_expr)
    | +-Expression list (preceding, last_expr)
    | | +-Expression list (preceding, last_expr)
    | | | +-NULL
    | | | +-Idiv (left, right) [INTEGER]
    | | |   +-Integer [1]
    | | |   +-Integer [2]
    | | +-Integer [5]
    | +-Divide (left, right) [REAL]
    |   +-Cast [REAL]
    |   | +-Integer [1]
    |   +-Cast [REAL]
    |     +-Integer [2]
    +-Add (left, right) [REAL]
      +-Real [8]
      +-Cast [REAL]
        +-Integer [7]
7GLOBAL.4VOID7INTEGER4REAL4READ5WRITE7INT-ARG5TRUNC8REAL-ARG8SEMTEST11A1B1X1Y5I_ARR5INDEX1I1J3MAX5NASTY1I1J7NASTY_17NASTY_27DO_ZERO4OREZ4ZERO1Z
-----------------------------------------------------------------------------------------------------------------------------------------------^ (pool_pos = 143)

Symbol table (size = 31):
Pos  Name      Lev Hash Back Offs Type      Tag
-----------------------------------------------
  0: GLOBAL.     0   -1  159    0 GLOBAL.   SYM_PROC      lbl = -1 ar_size = 0  
  1: VOID        0   -1   82    0 VOID      SYM_NAMETYPE  
  2: INTEGER     0   -1  462    0 VOID      SYM_NAMETYPE  
  3: REAL        0   -1  324    0 VOID      SYM_NAMETYPE  
  4: READ        0   -1  316    0 INTEGER   SYM_FUNC      lbl = 0  ar_size = 0  
  5: WRITE       0   -1  139    0 VOID      SYM_PROC      lbl = 1  ar_size = 0  
  6: INT-ARG     0   -1  210    0 INTEGER   SYM_PARAM     
  7: TRUNC       0   -1  332    0 INTEGER   SYM_FUNC      lbl = 2  ar_size = 0  
  8: REAL-ARG    0   -1  427    0 REAL      SYM_PARAM     
  9: SEMTEST1    0   -1  502    0 VOID      SYM_PROC      lbl = 3  ar_size = 112
 10: A           1   -1   65    0 INTEGER   SYM_VAR       
 11: B           1   -1   66    8 INTEGER   SYM_VAR       
 12: X           1   -1   88   16 REAL      SYM_VAR       
 13: Y           1   -1   89   24 REAL      SYM_VAR       
 14: I_ARR       1   -1   45   32 INTEGER   SYM_ARRAY     card = 10  
 15: INDEX       1   -1  216    0 VOID      SYM_PROC      lbl = 4  ar_size = 8  
 16: I           2   -1   73    0 INTEGER   SYM_PARAM     
 17: J           2   -1   74    0 INTEGER   SYM_VAR       
 18: MAX         1   -1   70    0 INTEGER   SYM_FUNC      lbl = 5  ar_size = 0  
 19: A           2   -1   65    0 INTEGER   SYM_PARAM     
 20: X           2   -1   88    8 REAL      SYM_PARAM     prec = A           
 21: NASTY       1   -1   47    0 VOID      SYM_PROC      lbl = 6  ar_size = 88 
 22: I           2   -1   73    0 INTEGER   SYM_PARAM     
 23: J           2   -1   74    8 INTEGER   SYM_PARAM     prec = I           
 24: X           2   -1   88   16 REAL      SYM_PARAM     prec = J           
 25: Y           2   -1   89   24 REAL      SYM_PARAM     prec = X           
 26: NASTY_1     2   -1   95    0 INTEGER   SYM_VAR       
 27: NASTY_2     2   -1   96    8 INTEGER   SYM_ARRAY     card = 10  
 28: DO_ZERO     2   -1  338    0 VOID      SYM_PROC      lbl = 7  ar_size = 8  
 29: OREZ        3   -1    0    0 INTEGER   SYM_CONST     value = 48
 30: ZERO        3   -1  128    0 INTEGER   SYM_CONST     value = 48
 31: Z           3   -1   90    0 INTEGER   SYM_VAR       
./diesel -b -p -f -y -a ../testpgm/semtest2.d
Listing 12 trace/semtest2.trace
No optimization will be done.
Generating assembler for procedure "INDEX"
Type conflict, line 17, col 1: A function must return a value.
Type conflict, line 25, col 6: Can't assign a real value to an integer variable.
Type conflict, line 27, col 11: Type discrepancy between formal and actual parameters.
Type conflict, line 28, col 7: Can't assign a real value to an integer variable.
Type conflict, line 30, col 1: Procedures may not return a value.
Type conflict, line 34, col 7: More actual than formal parameters.
Type conflict, line 35, col 1: More formal than actual parameters.
Type conflict, line 36, col 7: Type discrepancy between formal and actual parameters.
Found 8 errors. Compilation aborted.

Note

Your col value might differ slightly from the ones shown above depending on how you implemented your column counter in your scanner.