Semantic Analysis: Difference between revisions

From Wiki**3

Root (talk | contribs)
Root (talk | contribs)
 
(34 intermediate revisions by the same user not shown)
Line 24: Line 24:


{{CollapsedCode|File typed_node.h|
{{CollapsedCode|File typed_node.h|
<source lang="c++">
<syntaxhighlight lang="c++">
#ifndef __CDK15_AST_TYPEDNODE_NODE_H__
#pragma once
#define __CDK15_AST_TYPEDNODE_NODE_H__


#include <cdk/ast/basic_node.h>
#include <cdk/ast/basic_node.h>
Line 66: Line 65:


} // cdk
} // cdk
 
</syntaxhighlight>
#endif
</source>
}}
}}


Line 74: Line 71:


{{CollapsedCode|File expression_node.h|
{{CollapsedCode|File expression_node.h|
<source lang="c++">
<syntaxhighlight lang="c++">
#ifndef __CDK15_AST_EXPRESSIONNODE_NODE_H__
#pragma once
#define __CDK15_AST_EXPRESSIONNODE_NODE_H__


#include <cdk/ast/typed_node.h>
#include <cdk/ast/typed_node.h>
Line 98: Line 94:


} // cdk
} // cdk
 
</syntaxhighlight>
#endif
</source>
}}
}}


Line 106: Line 100:


{{CollapsedCode|File lvalue_node.h|
{{CollapsedCode|File lvalue_node.h|
<source lang="c++">
<syntaxhighlight lang="c++">
#ifndef __CDK15_LVALUE_NODE_H__
#pragma once
#define __CDK15_LVALUE_NODE_H__


#include <cdk/ast/typed_node.h>
#include <cdk/ast/typed_node.h>
Line 127: Line 120:


} // cdk
} // cdk
 
</syntaxhighlight>
#endif
</source>
}}
}}


=== Declarations and definitions ===
=== Declarations and definitions ===
Declarations and definitions can be seen as typed nodes since they declare entities that bear types. The precise definition of these nodes depends on the language and, thus, the nodes are not provided by the CDK. In general, though, they all have to be able to store one or more names (the entity or entities) being declared/defined (variables, functions, and so on) and, possibly, other information (e.g., access qualifiers).


== Representing and manipulating types ==
== Representing and Manipulating Types ==


Types are used to characterize the memory used by the various language entities (described by one or more AST nodes).
Types are used to characterize the memory used by the various language entities (described by one or more AST nodes).
Line 146: Line 138:
* '''reference_type''' -- this class is used to describe reference/pointer types.
* '''reference_type''' -- this class is used to describe reference/pointer types.
* '''structured_type''' -- this class allows for the definition of complex (i.e., hierarchical) data types: it is suitable for describing tuples or structures/classes.
* '''structured_type''' -- this class allows for the definition of complex (i.e., hierarchical) data types: it is suitable for describing tuples or structures/classes.
* '''functional_type''' -- this class allows for the definition of types for function objects: it is suitable for describing input/output types for functions.
* '''tensor_type''' -- this class allows for the definition of types for tensor objects (multidimensional arrays).


=== Class cdk::basic_type ===
=== Class cdk::basic_type ===
In addition to providing a base representation for all type references, it also provides two operators for comparing any two types.
In addition to providing a base representation for all type references, it also provides two operators for comparing any two types.
{{CollapsedCode|File basic_type.h|
{{CollapsedCode|File basic_type.h|
<source lang="c++">
<syntaxhighlight lang="c++">
#ifndef __CDK15_TYPES_BASIC_TYPE_H__
#pragma once
#define __CDK15_TYPES_BASIC_TYPE_H__


#include <cdk/types/typename_type.h>
#include <cdk/types/typename_type.h>
#include <cstdlib>
#include <cstdlib>
#include <memory>
#include <memory>
#include <string>


namespace cdk {
namespace cdk {


   /**
   /** This class represents a general type concept. */
  * This class represents a general type concept.
  */
   class basic_type {
   class basic_type {
     size_t _size = 0; // in bytes
     size_t _size = 0; // in bytes
     typename_type _name = TYPE_UNSPEC;
     typename_type _name = TYPE_UNSPEC;


   public:
   protected:
    struct explicit_call_disabled {};
 
  protected:


     basic_type() :
     basic_type() : _size(0), _name(TYPE_UNSPEC) { }
        _size(0), _name(TYPE_UNSPEC) {
     basic_type(size_t size, typename_type name) : _size(size), _name(name) { }
    }
     basic_type(size_t size, typename_type name) :
        _size(size), _name(name) {
    }


     virtual ~basic_type() noexcept = 0;
     virtual ~basic_type() noexcept = 0;


   public:
   public:
    size_t size() const { return _size; }
    typename_type name() const { return _name; }


    size_t size() const {
  public:
      return _size;
     virtual std::string to_string() const = 0;
    }
     typename_type name() const {
      return _name;
    }
 
   };
   };


Line 197: Line 185:


} // cdk
} // cdk
 
</syntaxhighlight>
#endif
</source>
}}
}}


=== Class cdk::primitive_type ===
=== Class cdk::primitive_type ===
{{CollapsedCode|File primitive_type.h|
{{CollapsedCode|File primitive_type.h|
<source lang="c++">
<syntaxhighlight lang="c++">
#ifndef __CDK15_TYPES_PRIMITIVE_TYPE_H__
#pragma once
#define __CDK15_TYPES_PRIMITIVE_TYPE_H__


#include <cdk/types/typename_type.h>
#include <cdk/types/typename_type.h>
Line 214: Line 199:
namespace cdk {
namespace cdk {


   /**
   /** Primitive (i.e., non-structured non-indirect) types. */
  * Primitive (i.e., non-structured non-indirect) types.
  */
   class primitive_type: public basic_type {
   class primitive_type: public basic_type {
   public:
   public:
     primitive_type() :
     explicit primitive_type(explicit_call_disabled, size_t size, typename_type name) :
        basic_type(0, TYPE_UNSPEC) {
    }
    primitive_type(size_t size, typename_type name) :
         basic_type(size, name) {
         basic_type(size, name) {
     }
     }
     ~primitive_type() = default;
     ~primitive_type() = default;
  public:
    static auto create(size_t size, typename_type name) {
      return std::make_shared<primitive_type>(explicit_call_disabled(), size, name);
    }
    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<primitive_type>(type);
    }
  public:
    std::string to_string() const {
      if (name() == TYPE_INT) return "integer";
      if (name() == TYPE_DOUBLE) return "double";
      if (name() == TYPE_STRING) return "string";
      if (name() == TYPE_VOID) return "void";
      return "UNKNOWN-TYPE:" + std::to_string(name());
    }
   };
   };


} // cdk
} // cdk
 
</syntaxhighlight>
#endif
</source>
}}
}}


=== Class cdk::reference_type ===
=== Class cdk::reference_type ===
{{CollapsedCode|File reference_type.h|
{{CollapsedCode|File reference_type.h|
<source lang="c++">
<syntaxhighlight lang="c++">
#ifndef __CDK15_TYPES_REFERENCE_TYPE_H__
#pragma once
#define __CDK15_TYPES_REFERENCE_TYPE_H__


#include <cdk/types/basic_type.h>
#include <cdk/types/basic_type.h>
Line 244: Line 241:
namespace cdk {
namespace cdk {


   /**
   /** This class represents a reference type concept (such as a C pointer or a C++ reference). */
  * This class represents a reference type concept (such as a C pointer or a C++ reference).
  */
   struct reference_type: public basic_type {
   struct reference_type: public basic_type {
     std::shared_ptr<basic_type> _referenced = nullptr;
     std::shared_ptr<basic_type> _referenced = nullptr;


   public:
   public:
     reference_type(size_t size,  std::shared_ptr<basic_type> referenced) :
     explicit reference_type(explicit_call_disabled, size_t size,  std::shared_ptr<basic_type> referenced) :
         basic_type(size, TYPE_POINTER), _referenced(referenced) {
         basic_type(size, TYPE_POINTER), _referenced(referenced) {
     }
     }
Line 257: Line 252:
     ~reference_type() = default;
     ~reference_type() = default;


     std::shared_ptr<basic_type> referenced() const {
     std::shared_ptr<basic_type> referenced() const { return _referenced; }
       return _referenced;
 
  public:
    std::string to_string() const { return "@" + _referenced->to_string(); }
 
  public:
 
    static auto create(size_t size, std::shared_ptr<basic_type> referenced) {
       return std::make_shared<reference_type>(explicit_call_disabled(), size, referenced);
    }
 
    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<reference_type>(type);
     }
     }


Line 264: Line 270:


} // cdk
} // cdk
 
</syntaxhighlight>
#endif
</source>
}}
}}


=== Class cdk::structured_type ===
=== Class cdk::structured_type ===
{{CollapsedCode|File structured_type.h|
{{CollapsedCode|File structured_type.h|
<source lang="c++">
<syntaxhighlight lang="c++">
#ifndef __CDK15_TYPES_STRUCTURED_TYPE_H__
#pragma once
#define __CDK15_TYPES_STRUCTURED_TYPE_H__


#include <vector>
#include <vector>
Line 281: Line 284:
namespace cdk {
namespace cdk {


   /**
   /** This class represents a structured type concept. */
  * This class represents a structured type concept.
  */
   class structured_type: public basic_type {
   class structured_type: public basic_type {
     std::vector<std::shared_ptr<basic_type>> _components;
     std::vector<std::shared_ptr<basic_type>> _components;
Line 296: Line 297:


   public:
   public:
     structured_type(const std::vector<std::shared_ptr<basic_type>> &components) :
     explicit structured_type(explicit_call_disabled, const std::vector<std::shared_ptr<basic_type>> &components) :
         basic_type(compute_size(components), TYPE_STRUCT), _components(components) {
         basic_type(compute_size(components), TYPE_STRUCT), _components(components) {
       // EMPTY
       // EMPTY
Line 303: Line 304:
     ~structured_type() = default;
     ~structured_type() = default;


     std::shared_ptr<basic_type> component(size_t ix) {
  public:
       return _components[ix];
     std::shared_ptr<basic_type> component(size_t ix) { return _components[ix]; }
    const std::vector<std::shared_ptr<basic_type>>& components() const { return _components; }
    size_t length() const { return _components.size(); }
 
  public:
    std::string to_string() const {
      std::string result = "<";
       return std::accumulate(_components.begin(), _components.end(), result,
                            [] (auto a, auto b) { return a + "," + b->to_string(); });
     }
     }


     const std::vector<std::shared_ptr<basic_type>>& components() const {
  public:
       return _components;
 
     static auto create(const std::vector<std::shared_ptr<basic_type>> &types) {
       return std::make_shared<structured_type>(explicit_call_disabled(), types);
     }
     }


     size_t length() const {
     static auto cast(std::shared_ptr<basic_type> type) {
       return _components.size();
       return std::dynamic_pointer_cast<structured_type>(type);
     }
     }


Line 318: Line 329:


} // cdk
} // cdk
</syntaxhighlight>
}}
=== Class cdk::functional_type ===
{{CollapsedCode|File functional_type.h|
<syntaxhighlight lang="c++">
#pragma once


#endif
#include <vector>
</source>
#include <numeric>
#include <cdk/types/basic_type.h>
#include <cdk/types/structured_type.h>
 
namespace cdk {
 
  /** This class represents a functional type concept. */
  class functional_type: public basic_type {
    std::shared_ptr<structured_type> _input;
    std::shared_ptr<structured_type> _output;
 
  public:
    // size 4 is because this is actually just a pointer
    explicit functional_type(explicit_call_disabled, const std::vector<std::shared_ptr<basic_type>> &input,
                            const std::vector<std::shared_ptr<basic_type>> &output) :
        basic_type(4, TYPE_FUNCTIONAL), _input(structured_type::create(input)), _output(structured_type::create(output)) {
      // EMPTY
    }
 
    ~functional_type() = default;
 
  public:
    std::shared_ptr<basic_type> input(size_t ix) { return _input->component(ix); }
    const std::shared_ptr<structured_type> &input() const { return _input; }
    size_t input_length() const { return _input->length(); }
    std::shared_ptr<basic_type> output(size_t ix) { return _output->component(ix); }
    const std::shared_ptr<structured_type> &output() const { return _output; }
    size_t output_length() const { return _output->length(); }
 
  public:
    std::string to_string() const { return _input->to_string() + ":" + _output->to_string(); }
 
  public:
    static auto create(const std::vector<std::shared_ptr<basic_type>> &input_types,
                      const std::vector<std::shared_ptr<basic_type>> &output_types) {
      return std::make_shared<functional_type>(explicit_call_disabled(), input_types, output_types);
    }
 
    static auto create(const std::vector<std::shared_ptr<basic_type>> &input_types,
                      const std::shared_ptr<basic_type> &output_type) {
      std::vector<std::shared_ptr<basic_type>> output_types = { output_type };
      return std::make_shared<functional_type>(explicit_call_disabled(), input_types, output_types);
    }
 
    static auto create(const std::vector<std::shared_ptr<basic_type>> &output_types) {
      std::vector<std::shared_ptr<basic_type>> input_types;
      return std::make_shared<functional_type>(explicit_call_disabled(), input_types, output_types);
    }
 
    static auto create(const std::shared_ptr<basic_type> &output_type) {
      std::vector<std::shared_ptr<basic_type>> output_types = { output_type };
      return create(output_types);
    }
 
    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<functional_type>(type);
    }
 
  };
 
} // cdk
</syntaxhighlight>
}}
 
=== Class cdk::tensor_type ===
{{CollapsedCode|File tensor_type.h|
<syntaxhighlight lang="c++">
#pragma once
 
#include <vector>
#include <numeric>
#include <cdk/types/basic_type.h>
 
namespace cdk {
 
  class tensor_type: public basic_type {
    std::vector<size_t> _dims;
 
  public:
 
    // size 4 is because this is actually just a pointer
    explicit tensor_type(explicit_call_disabled, const std::vector<size_t> &dims) :
        basic_type(4, TYPE_TENSOR), _dims(dims) {
      // EMPTY
    }
 
    ~tensor_type() = default;
 
  public:
    size_t dim(size_t ix) const { return _dims.at(ix); }
    const std::vector<size_t> &dims() const { return _dims; }
    size_t n_dims() const { return _dims.size(); }
 
    size_t size() const {
      size_t size = 0;
      if (_dims.size() >= 1) size = _dims.at(0);
      for (size_t ix = 1; ix < _dims.size(); ix++)
        size *= _dims.at(ix);
      return size;
    }
 
  public:
    std::string to_string() const {
      std::string s = "[";
      if (_dims.size() >= 1) s += std::to_string(_dims.at(0));
      for (size_t ix = 1; ix < _dims.size(); ix++)
        s += ',' + std::to_string(_dims.at(ix));
      return s + "]";
    }
 
  public:
 
    static auto create(const std::vector<size_t> &dims) {
      return std::make_shared<tensor_type>(explicit_call_disabled(), dims);
    }
 
    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<tensor_type>(type);
    }
 
  };
 
} // cdk
</syntaxhighlight>
}}
 
=== Class cdk::balanced3_type ===
{{CollapsedCode|File balanced3_type.h|
<syntaxhighlight lang="c++">
#pragma once
 
#include <string>
#include <cdk/types/basic_type.h>
#include <cdk/types/treg_value.h>
#include <cdk/types/treg_alu.h>
 
namespace cdk {
 
  /**
  * Balanced ternary integer type (40-trit, stored as treg_t).
  *
  * Usage in the scanner / parser:
  *  balanced3_type::value_type *v = new balanced3_type::value_type(42);
  *  // ... in parser: new balanced3_node(LINE, *$1); delete $1;
  */
  struct balanced3_type : public basic_type {
 
    struct value_type : public treg_value {
 
      /** Zero (all trits zero = bias in each half). */
      value_type() { treg_alu::clear(*this); }
 
      /** From raw halves. */
      value_type(uint64_t h0, uint64_t h1, uint64_t h2) { set_half(0, h0); set_half(1, h1); set_half(2, h2); }
 
      /**
      * From integer — the primary scanner constructor.
      * Converts a C integer to 40-trit balanced ternary via the ALU.
      */
      explicit value_type(int val) : value_type(static_cast<long long>(val)) {}
      explicit value_type(long long val) {
        treg_alu::load_int(*this, val);
      }
 
      /** Back to integer (Horner's method, 40 trits). */
      long long to_int64() const {
        return treg_alu::store_int(*this);
      }
 
      int to_int() const {
        long long v = to_int64();
        if (v > 2147483647LL) return 2147483647;
        if (v < -2147483648LL) return -2147483648;
        return static_cast<int>(v);
      }
 
      /** Decimal string representation of the integer value. */
      std::string to_string() const {
        return treg_alu::balanced3_to_string(*this);
      }
 
      bool operator==(const value_type &o) const {
        return half(0) == o.half(0) && half(1) == o.half(1) && half(2) == o.half(2);
      }
      bool operator!=(const value_type &o) const { return !(*this == o); }
    };
 
  public:
 
    explicit balanced3_type(explicit_call_disabled) :
        basic_type(8, TYPE_BALANCED3) {  // packed: single uint64_t (40 trits)
    }
 
    ~balanced3_type() = default;
 
  public:
 
    static auto create() {
      return std::make_shared<balanced3_type>(explicit_call_disabled());
    }
 
    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<balanced3_type>(type);
    }
 
  public:
 
    std::string to_string() const override { return "balanced3"; }
 
  };
 
} // cdk
</syntaxhighlight>
}}
 
=== Class cdk::posit3_type ===
{{CollapsedCode|File posit3_type.h|
<syntaxhighlight lang="c++">
#pragma once
 
#include <cdk/types/basic_type.h>
#include <cdk/types/treg_value.h>
#include <cdk/types/treg_alu.h>
 
namespace cdk {
 
  /**
  * Logarithmic ternary posit real type (80-trit, stored as treg_t).
  *
  * The value_type wraps treg_value, exposing constructors for the
  * scanner to create values from decimal string literals:
  *
  *  auto *v = new posit3_type::value_type("3.14");
  *  // in parser: new posit3_node(LINE, *$1); delete $1;
  */
  struct posit3_type : public basic_type {
 
    struct value_type : public treg_value {
 
      /** Zero. */
      value_type() { treg_alu::clear(*this); }
 
      /** From raw halves. */
      value_type(uint64_t h0, uint64_t h1, uint64_t h2) { set_half(0, h0); set_half(1, h1); set_half(2, h2); }
 
      /**
      * From decimal string — the primary scanner constructor.
      * Parses the string and converts to 80-trit logarithmic posit
      * entirely via balanced ternary arithmetic (no floating-point).
      */
      explicit value_type(const char *s) {
        treg_alu::load_posit3(*this, s);
      }
 
      /** From balanced3 integer value. */
      explicit value_type(const balanced3_type::value_type &b3) {
        treg_alu::load_posit3_from_balanced3(*this, b3);
      }
 
      /** Decimal string representation of the real value. */
      std::string to_string() const {
        return treg_alu::posit3_to_string(*this);
      }
 
      bool operator==(const value_type &o) const {
        return half(0) == o.half(0) && half(1) == o.half(1) && half(2) == o.half(2);
      }
      bool operator!=(const value_type &o) const { return !(*this == o); }
    };
 
  public:
 
    explicit posit3_type(explicit_call_disabled) :
        basic_type(16, TYPE_POSIT3) {
      // packed: two uint64_t halves (80 trits)
    }
 
    ~posit3_type() = default;
 
  public:
 
    static auto create() {
      return std::make_shared<posit3_type>(explicit_call_disabled());
    }
 
    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<posit3_type>(type);
    }
 
  public:
 
    std::string to_string() const override { return "posit3"; }
 
  };
 
} // cdk
</syntaxhighlight>
}}
 
=== Class cdk::takum3_type ===
{{CollapsedCode|File takum3_type.h|
<syntaxhighlight lang="c++">
#pragma once
 
#include <cdk/types/basic_type.h>
#include <cdk/types/treg_value.h>
#include <cdk/types/treg_alu.h>
 
namespace cdk {
 
  /**
  * Logarithmic ternary takum real type (80-trit, stored as treg_t).
  *
  * Hunhold Definition 2 (2024) adapted for balanced ternary with base 3.
  * Fixed 2-trit regime field; variable characteristic and mantissa.
  */
  struct takum3_type : public basic_type {
 
    struct value_type : public treg_value {
 
      /** Zero. */
      value_type() { treg_alu::clear(*this); }
 
      /** From raw halves. */
      value_type(uint64_t h0, uint64_t h1, uint64_t h2) { set_half(0, h0); set_half(1, h1); set_half(2, h2); }
 
      /**
      * From decimal string — the primary scanner constructor.
      * Parses the string and converts to 80-trit logarithmic takum
      * entirely via balanced ternary arithmetic (no floating-point).
      */
      explicit value_type(const char *s) {
        treg_alu::load_takum3(*this, s);
      }
 
      /** From balanced3 integer value. */
      explicit value_type(const balanced3_type::value_type &b3) {
        treg_alu::load_takum3_from_balanced3(*this, b3);
      }
 
      /** Decimal string representation of the real value. */
      std::string to_string() const {
        return treg_alu::takum3_to_string(*this);
      }
 
      bool operator==(const value_type &o) const {
        return half(0) == o.half(0) && half(1) == o.half(1) && half(2) == o.half(2);
      }
      bool operator!=(const value_type &o) const { return !(*this == o); }
    };
 
  public:
 
    explicit takum3_type(explicit_call_disabled) :
        basic_type(16, TYPE_TAKUM3) {
    }
 
    ~takum3_type() = default;
 
  public:
 
    static auto create() {
      return std::make_shared<takum3_type>(explicit_call_disabled());
    }
 
    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<takum3_type>(type);
    }
 
  public:
 
    std::string to_string() const override { return "takum3"; }
 
  };
 
} // cdk
</syntaxhighlight>
}}
 
=== Convenience functions for handling types ===
The following functions are provided for convenience: they allow for writing clearer code.
 
Implementations have been omitted for the sake of clarity (they are available in the CDK).
{{CollapsedCode|File types.h|
<syntaxhighlight lang="c++">
#pragma once
#include <memory>
#include <cdk/types/basic_type.h>
#include <cdk/types/primitive_type.h>
#include <cdk/types/reference_type.h>
#include <cdk/types/structured_type.h>
#include <cdk/types/functional_type.h>
#include <cdk/types/tensor_type.h>
#include <cdk/types/balanced3_type.h>
#include <cdk/types/posit3_type.h>
#include <cdk/types/takum3_type.h>
 
namespace cdk {
 
  inline std::string to_string(std::shared_ptr<basic_type> type) {
    return type->to_string();
  }
 
} // cdk
</syntaxhighlight>
}}
}}


Line 336: Line 758:


{{CollapsedCode|File symbol_table.h (interface summary)|
{{CollapsedCode|File symbol_table.h (interface summary)|
 
<syntaxhighlight lang="c++">
<source lang="c++">
namespace cdk {
namespace cdk {


Line 358: Line 779:


};
};
</source>
</syntaxhighlight>
}}
}}


Line 368: Line 789:


{{CollapsedCode|File symbol.h (Tiny language)|
{{CollapsedCode|File symbol.h (Tiny language)|
<source lang="c++">
<syntaxhighlight lang="c++">
#ifndef __TINY_TARGETS_SYMBOL_H__
#ifndef __TINY_TARGETS_SYMBOL_H__
#define __TINY_TARGETS_SYMBOL_H__
#define __TINY_TARGETS_SYMBOL_H__
Line 398: Line 819:
} // tiny
} // tiny
#endif
#endif
</source>
</syntaxhighlight>
}}
}}


Line 408: Line 829:


In the approach followed by CDK-based compilers, code generation is carried out by visitors that are responsible for traversing the abstract syntax tree and generate, evaluating each node. Node evaluation may depend on the specificities of the data types being manipulated, the simplest of which is the data type's size, important in all memory-related operations.
In the approach followed by CDK-based compilers, code generation is carried out by visitors that are responsible for traversing the abstract syntax tree and generate, evaluating each node. Node evaluation may depend on the specificities of the data types being manipulated, the simplest of which is the data type's size, important in all memory-related operations.
== Vídeo Explicativo ==
O vídeo seguinte explica os tópicos apresentados nesta página (aplicação a várias linguagens).
* Linguagem UDF (2024/2025): https://www.youtube.com/watch?v=x9uJ_Wn6360


== Examples ==
== Examples ==

Latest revision as of 17:32, 14 May 2026

Compiladores
Introdução ao Desenvolvimento de Compiladores
Aspectos Teóricos de Análise Lexical
A Ferramenta Flex
Introdução à Sintaxe
Análise Sintáctica Descendente
Gramáticas Atributivas
A Ferramenta YACC
Análise Sintáctica Ascendente
Análise Semântica
Geração de Código
Tópicos de Optimização

Semantic analysis is mostly concerned with types associated with language objects and how these types are used by the language constructs that depend on them, such as functions and arithmetic operators.

Types can be implicitly specified (e.g., in literals) and inferred (e.g., from operations). This is the case of languages such as Python and other scripting languages, able to make type inference at run time. It can also in languages such as C++ (auto) and Java (var), that make type inference at compile time.

On the other hand, typed entities may be explicitly declared. This is how most statically compiled languages work: the program's entities are explicitly typed and types may be verified by the compiler.

This section focuses on type checking, based on the abstract syntax tree's nodes, specifically those that declare typed entities (declarations of typed program entities, such as functions and variables), and those that use those entities (functions and operators). The entities themselves, of course, must remember their own types, so that they may require compliance.

Representing Typed Information in the AST

Type information is present in the AST itself. This information may be directly set by the parser, during syntactic analysis, e.g. in declarations, or it may be set -- the most usual way -- during semantic analysis.

The main nodes involved in representing types are the following:

  • typed_node -- this is the superclass of any node that bears a type. It also provides a convenient interface for checking and managing types.
  • expression_node -- this is a subclass of typed_node that represents program expressions, that is, any value that can be used by a program. Expressions may be primitive, e.g. literals, or composed by other expressions, e.g. operators.
  • lvalue_node -- left-values denote the write-compatible memory locations, these are not the usual values denoted by expression nodes, although any left-value can be converted into an expression, either by considering the memory address it represents (a pointer), or the value at that location (rvalue_node). Left-values are usually known as variables:variable_node, in the simplest case; index_node (for instance) in a more elaborate one.
  • Other cases of typed nodes correspond, in certain languages, to function and variable declarations.

Class cdk::typed_node

File typed_node.h
#pragma once

#include <cdk/ast/basic_node.h>
#include <cdk/types/types.h>
#include <memory>

namespace cdk {

  /**
   * Typed nodes store a type description.
   */
  class typed_node: public basic_node {
  protected:
    // This must be a pointer, so that we can anchor a dynamic
    // object and be able to change/delete it afterwards.
    std::shared_ptr<basic_type> _type;

  public:
    /**
     * @param lineno the source code line number corresponding to
     * the node
     */
    typed_node(int lineno) :
        basic_node(lineno), _type(nullptr) {
    }

    std::shared_ptr<basic_type> type() {
      return _type;
    }
    void type(std::shared_ptr<basic_type> type) {
      _type = type;
    }

    bool is_typed(typename_type name) const {
      return _type->name() == name;
    }

  };

} // cdk

Class cdk::expression_node

File expression_node.h
#pragma once

#include <cdk/ast/typed_node.h>

namespace cdk {

  /**
   * Expressions are typed nodes that have a value.
   */
  class expression_node: public typed_node {

  protected:
    /**
     * @param lineno the source code line corresponding to the node
     */
    expression_node(int lineno) :
        typed_node(lineno) {
    }

  };

} // cdk

Class cdk::lvalue_node

File lvalue_node.h
#pragma once

#include <cdk/ast/typed_node.h>
#include <string>

namespace cdk {

  /**
   * Class for describing syntactic tree leaves for lvalues.
   */
  class lvalue_node: public typed_node {
  protected:
    lvalue_node(int lineno) :
        typed_node(lineno) {
    }

  };

} // cdk

Declarations and definitions

Declarations and definitions can be seen as typed nodes since they declare entities that bear types. The precise definition of these nodes depends on the language and, thus, the nodes are not provided by the CDK. In general, though, they all have to be able to store one or more names (the entity or entities) being declared/defined (variables, functions, and so on) and, possibly, other information (e.g., access qualifiers).

Representing and Manipulating Types

Types are used to characterize the memory used by the various language entities (described by one or more AST nodes).

Types should not be confused with AST nodes.

The CDK has four base definitions. They are, in general, sufficient for most languages, and are easily extended.

  • basic_type -- this is the abstract superclass. It is used mostly to refer to unknown or general types.
  • primitive_type -- this class is used to represent any "atomic" data type (that is, unstructured or non-reference types).
  • reference_type -- this class is used to describe reference/pointer types.
  • structured_type -- this class allows for the definition of complex (i.e., hierarchical) data types: it is suitable for describing tuples or structures/classes.
  • functional_type -- this class allows for the definition of types for function objects: it is suitable for describing input/output types for functions.
  • tensor_type -- this class allows for the definition of types for tensor objects (multidimensional arrays).

Class cdk::basic_type

In addition to providing a base representation for all type references, it also provides two operators for comparing any two types.

File basic_type.h
#pragma once

#include <cdk/types/typename_type.h>
#include <cstdlib>
#include <memory>
#include <string>

namespace cdk {

  /** This class represents a general type concept. */
  class basic_type {
    size_t _size = 0; // in bytes
    typename_type _name = TYPE_UNSPEC;

  protected:
    struct explicit_call_disabled {};

  protected:

    basic_type() : _size(0), _name(TYPE_UNSPEC) { }
    basic_type(size_t size, typename_type name) : _size(size), _name(name) { }

    virtual ~basic_type() noexcept = 0;

  public:
    size_t size() const { return _size; }
    typename_type name() const { return _name; }

  public:
    virtual std::string to_string() const = 0;
  };

  inline bool operator==(const std::shared_ptr<basic_type> t1, const std::shared_ptr<basic_type> t2) {
    return t1->size() == t2->size() && t1->name() == t2->name();
  }
  inline bool operator!=(const std::shared_ptr<basic_type> t1, const std::shared_ptr<basic_type> t2) {
    return !(t1 == t2);
  }

} // cdk

Class cdk::primitive_type

File primitive_type.h
#pragma once

#include <cdk/types/typename_type.h>
#include <cdk/types/basic_type.h>
#include <cstdlib>

namespace cdk {

  /** Primitive (i.e., non-structured non-indirect) types. */
  class primitive_type: public basic_type {
  public:
    explicit primitive_type(explicit_call_disabled, size_t size, typename_type name) :
        basic_type(size, name) {
    }

    ~primitive_type() = default;

  public:
    static auto create(size_t size, typename_type name) {
      return std::make_shared<primitive_type>(explicit_call_disabled(), size, name);
    }

    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<primitive_type>(type);
    }

  public:
    std::string to_string() const {
      if (name() == TYPE_INT) return "integer";
      if (name() == TYPE_DOUBLE) return "double";
      if (name() == TYPE_STRING) return "string";
      if (name() == TYPE_VOID) return "void";
      return "UNKNOWN-TYPE:" + std::to_string(name());
    }

  };

} // cdk

Class cdk::reference_type

File reference_type.h
#pragma once

#include <cdk/types/basic_type.h>

namespace cdk {

  /** This class represents a reference type concept (such as a C pointer or a C++ reference). */
  struct reference_type: public basic_type {
    std::shared_ptr<basic_type> _referenced = nullptr;

  public:
    explicit reference_type(explicit_call_disabled, size_t size,  std::shared_ptr<basic_type> referenced) :
        basic_type(size, TYPE_POINTER), _referenced(referenced) {
    }

    ~reference_type() = default;

    std::shared_ptr<basic_type> referenced() const { return _referenced; }

  public:
    std::string to_string() const { return "@" + _referenced->to_string(); }

  public:

    static auto create(size_t size, std::shared_ptr<basic_type> referenced) {
      return std::make_shared<reference_type>(explicit_call_disabled(), size, referenced);
    }

    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<reference_type>(type);
    }

  };

} // cdk

Class cdk::structured_type

File structured_type.h
#pragma once

#include <vector>
#include <numeric>
#include <cdk/types/basic_type.h>

namespace cdk {

  /** This class represents a structured type concept. */
  class structured_type: public basic_type {
    std::vector<std::shared_ptr<basic_type>> _components;

  private:
    size_t compute_size(const std::vector<std::shared_ptr<basic_type>> &components) {
      size_t size = 0;
      for (auto component : components)
        size += component->size();
      return size;
    }

  public:
    explicit structured_type(explicit_call_disabled, const std::vector<std::shared_ptr<basic_type>> &components) :
        basic_type(compute_size(components), TYPE_STRUCT), _components(components) {
      // EMPTY
    }

    ~structured_type() = default;

  public:
    std::shared_ptr<basic_type> component(size_t ix) { return _components[ix]; }
    const std::vector<std::shared_ptr<basic_type>>& components() const { return _components; }
    size_t length() const { return _components.size(); }

  public:
    std::string to_string() const {
      std::string result = "<";
      return std::accumulate(_components.begin(), _components.end(), result,
                             [] (auto a, auto b) { return a + "," + b->to_string(); });
    }

  public:

    static auto create(const std::vector<std::shared_ptr<basic_type>> &types) {
      return std::make_shared<structured_type>(explicit_call_disabled(), types);
    }

    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<structured_type>(type);
    }

  };

} // cdk

Class cdk::functional_type

File functional_type.h
#pragma once

#include <vector>
#include <numeric>
#include <cdk/types/basic_type.h>
#include <cdk/types/structured_type.h>

namespace cdk {

  /** This class represents a functional type concept. */
  class functional_type: public basic_type {
    std::shared_ptr<structured_type> _input;
    std::shared_ptr<structured_type> _output;

  public:
    // size 4 is because this is actually just a pointer
    explicit functional_type(explicit_call_disabled, const std::vector<std::shared_ptr<basic_type>> &input,
                             const std::vector<std::shared_ptr<basic_type>> &output) :
        basic_type(4, TYPE_FUNCTIONAL), _input(structured_type::create(input)), _output(structured_type::create(output)) {
      // EMPTY
    }

    ~functional_type() = default;

  public:
    std::shared_ptr<basic_type> input(size_t ix) { return _input->component(ix); }
    const std::shared_ptr<structured_type> &input() const { return _input; }
    size_t input_length() const { return _input->length(); }
    std::shared_ptr<basic_type> output(size_t ix) { return _output->component(ix); }
    const std::shared_ptr<structured_type> &output() const { return _output; }
    size_t output_length() const { return _output->length(); }

  public:
    std::string to_string() const { return _input->to_string() + ":" + _output->to_string(); }

  public:
    static auto create(const std::vector<std::shared_ptr<basic_type>> &input_types, 
                       const std::vector<std::shared_ptr<basic_type>> &output_types) {
      return std::make_shared<functional_type>(explicit_call_disabled(), input_types, output_types);
    }

    static auto create(const std::vector<std::shared_ptr<basic_type>> &input_types, 
                       const std::shared_ptr<basic_type> &output_type) {
      std::vector<std::shared_ptr<basic_type>> output_types = { output_type };
      return std::make_shared<functional_type>(explicit_call_disabled(), input_types, output_types);
    }

    static auto create(const std::vector<std::shared_ptr<basic_type>> &output_types) {
      std::vector<std::shared_ptr<basic_type>> input_types;
      return std::make_shared<functional_type>(explicit_call_disabled(), input_types, output_types);
    }

    static auto create(const std::shared_ptr<basic_type> &output_type) {
      std::vector<std::shared_ptr<basic_type>> output_types = { output_type };
      return create(output_types);
    }

    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<functional_type>(type);
    }

  };

} // cdk

Class cdk::tensor_type

File tensor_type.h
#pragma once

#include <vector>
#include <numeric>
#include <cdk/types/basic_type.h>

namespace cdk {

  class tensor_type: public basic_type {
    std::vector<size_t> _dims;

  public:

    // size 4 is because this is actually just a pointer
    explicit tensor_type(explicit_call_disabled, const std::vector<size_t> &dims) :
        basic_type(4, TYPE_TENSOR), _dims(dims) {
      // EMPTY
    }

    ~tensor_type() = default;

  public:
    size_t dim(size_t ix) const { return _dims.at(ix); }
    const std::vector<size_t> &dims() const { return _dims; }
    size_t n_dims() const { return _dims.size(); }

    size_t size() const {
      size_t size = 0;
      if (_dims.size() >= 1) size = _dims.at(0);
      for (size_t ix = 1; ix < _dims.size(); ix++)
        size *= _dims.at(ix);
      return size;
    }

  public:
    std::string to_string() const {
      std::string s = "[";
      if (_dims.size() >= 1) s += std::to_string(_dims.at(0));
      for (size_t ix = 1; ix < _dims.size(); ix++)
        s += ',' + std::to_string(_dims.at(ix));
      return s + "]";
    }

  public:

    static auto create(const std::vector<size_t> &dims) {
      return std::make_shared<tensor_type>(explicit_call_disabled(), dims);
    }

    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<tensor_type>(type);
    }

  };

} // cdk

Class cdk::balanced3_type

File balanced3_type.h
#pragma once

#include <string>
#include <cdk/types/basic_type.h>
#include <cdk/types/treg_value.h>
#include <cdk/types/treg_alu.h>

namespace cdk {

  /**
   * Balanced ternary integer type (40-trit, stored as treg_t).
   *
   * Usage in the scanner / parser:
   *   balanced3_type::value_type *v = new balanced3_type::value_type(42);
   *   // ... in parser: new balanced3_node(LINE, *$1); delete $1;
   */
  struct balanced3_type : public basic_type {

    struct value_type : public treg_value {

      /** Zero (all trits zero = bias in each half). */
      value_type() { treg_alu::clear(*this); }

      /** From raw halves. */
      value_type(uint64_t h0, uint64_t h1, uint64_t h2) { set_half(0, h0); set_half(1, h1); set_half(2, h2); }

      /**
       * From integer — the primary scanner constructor.
       * Converts a C integer to 40-trit balanced ternary via the ALU.
       */
      explicit value_type(int val) : value_type(static_cast<long long>(val)) {}
      explicit value_type(long long val) {
        treg_alu::load_int(*this, val);
      }

      /** Back to integer (Horner's method, 40 trits). */
      long long to_int64() const {
        return treg_alu::store_int(*this);
      }

      int to_int() const {
        long long v = to_int64();
        if (v > 2147483647LL) return 2147483647;
        if (v < -2147483648LL) return -2147483648;
        return static_cast<int>(v);
      }

      /** Decimal string representation of the integer value. */
      std::string to_string() const {
        return treg_alu::balanced3_to_string(*this);
      }

      bool operator==(const value_type &o) const {
        return half(0) == o.half(0) && half(1) == o.half(1) && half(2) == o.half(2);
      }
      bool operator!=(const value_type &o) const { return !(*this == o); }
    };

  public:

    explicit balanced3_type(explicit_call_disabled) :
        basic_type(8, TYPE_BALANCED3) {  // packed: single uint64_t (40 trits)
    }

    ~balanced3_type() = default;

  public:

    static auto create() {
      return std::make_shared<balanced3_type>(explicit_call_disabled());
    }

    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<balanced3_type>(type);
    }

  public:

    std::string to_string() const override { return "balanced3"; }

  };

} // cdk

Class cdk::posit3_type

File posit3_type.h
#pragma once

#include <cdk/types/basic_type.h>
#include <cdk/types/treg_value.h>
#include <cdk/types/treg_alu.h>

namespace cdk {

  /**
   * Logarithmic ternary posit real type (80-trit, stored as treg_t).
   *
   * The value_type wraps treg_value, exposing constructors for the
   * scanner to create values from decimal string literals:
   *
   *   auto *v = new posit3_type::value_type("3.14");
   *   // in parser: new posit3_node(LINE, *$1); delete $1;
   */
  struct posit3_type : public basic_type {

    struct value_type : public treg_value {

      /** Zero. */
      value_type() { treg_alu::clear(*this); }

      /** From raw halves. */
      value_type(uint64_t h0, uint64_t h1, uint64_t h2) { set_half(0, h0); set_half(1, h1); set_half(2, h2); }

      /**
       * From decimal string — the primary scanner constructor.
       * Parses the string and converts to 80-trit logarithmic posit
       * entirely via balanced ternary arithmetic (no floating-point).
       */
      explicit value_type(const char *s) {
        treg_alu::load_posit3(*this, s);
      }

      /** From balanced3 integer value. */
      explicit value_type(const balanced3_type::value_type &b3) {
        treg_alu::load_posit3_from_balanced3(*this, b3);
      }

      /** Decimal string representation of the real value. */
      std::string to_string() const {
        return treg_alu::posit3_to_string(*this);
      }

      bool operator==(const value_type &o) const {
        return half(0) == o.half(0) && half(1) == o.half(1) && half(2) == o.half(2);
      }
      bool operator!=(const value_type &o) const { return !(*this == o); }
    };

  public:

    explicit posit3_type(explicit_call_disabled) :
        basic_type(16, TYPE_POSIT3) {
      // packed: two uint64_t halves (80 trits)
    }

    ~posit3_type() = default;

  public:

    static auto create() {
      return std::make_shared<posit3_type>(explicit_call_disabled());
    }

    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<posit3_type>(type);
    }

  public:

    std::string to_string() const override { return "posit3"; }

  };

} // cdk

Class cdk::takum3_type

File takum3_type.h
#pragma once

#include <cdk/types/basic_type.h>
#include <cdk/types/treg_value.h>
#include <cdk/types/treg_alu.h>

namespace cdk {

  /**
   * Logarithmic ternary takum real type (80-trit, stored as treg_t).
   *
   * Hunhold Definition 2 (2024) adapted for balanced ternary with base 3.
   * Fixed 2-trit regime field; variable characteristic and mantissa.
   */
  struct takum3_type : public basic_type {

    struct value_type : public treg_value {

      /** Zero. */
      value_type() { treg_alu::clear(*this); }

      /** From raw halves. */
      value_type(uint64_t h0, uint64_t h1, uint64_t h2) { set_half(0, h0); set_half(1, h1); set_half(2, h2); }

      /**
       * From decimal string — the primary scanner constructor.
       * Parses the string and converts to 80-trit logarithmic takum
       * entirely via balanced ternary arithmetic (no floating-point).
       */
      explicit value_type(const char *s) {
        treg_alu::load_takum3(*this, s);
      }

      /** From balanced3 integer value. */
      explicit value_type(const balanced3_type::value_type &b3) {
        treg_alu::load_takum3_from_balanced3(*this, b3);
      }

      /** Decimal string representation of the real value. */
      std::string to_string() const {
        return treg_alu::takum3_to_string(*this);
      }

      bool operator==(const value_type &o) const {
        return half(0) == o.half(0) && half(1) == o.half(1) && half(2) == o.half(2);
      }
      bool operator!=(const value_type &o) const { return !(*this == o); }
    };

  public:

    explicit takum3_type(explicit_call_disabled) :
        basic_type(16, TYPE_TAKUM3) {
    }

    ~takum3_type() = default;

  public:

    static auto create() {
      return std::make_shared<takum3_type>(explicit_call_disabled());
    }

    static auto cast(std::shared_ptr<basic_type> type) {
      return std::dynamic_pointer_cast<takum3_type>(type);
    }

  public:

    std::string to_string() const override { return "takum3"; }

  };

} // cdk

Convenience functions for handling types

The following functions are provided for convenience: they allow for writing clearer code.

Implementations have been omitted for the sake of clarity (they are available in the CDK).

File types.h
#pragma once
#include <memory>
#include <cdk/types/basic_type.h>
#include <cdk/types/primitive_type.h>
#include <cdk/types/reference_type.h>
#include <cdk/types/structured_type.h>
#include <cdk/types/functional_type.h>
#include <cdk/types/tensor_type.h>
#include <cdk/types/balanced3_type.h>
#include <cdk/types/posit3_type.h>
#include <cdk/types/takum3_type.h>

namespace cdk {

  inline std::string to_string(std::shared_ptr<basic_type> type) {
    return type->to_string();
  }

} // cdk

The Symbol Table

A interface pública da tabela de símbolos é a seguinte (foram omitidas todas as partes não públicas, assim como os métodos de construção/destruição):

  • push - create a new context and make it current.
  • pop - destroy the current context: the previous context becomes the current one. If the first context is reached no operation is performed.
  • insert - define a new identifier in the local (current) context: name is the symbol's name; symbol is the symbol. Returns true if this is a new identifier (may shadow another defined in an upper context). Returns false if the identifier already exists in the current context.
  • replace_local - replace the data corresponding to a symbol in the current context: name is the symbol's name; symbol is the symbol. Returns true if the symbol exists; false if the symbol does not exist in any of the contexts.
  • replace - replace the data corresponding to a symbol (look for the symbol in all available contexts, starting with the innermost one): name is the symbol's name; symbol is the symbol. Returns true if the symbol exists; false if the symbol does not exist in any of the contexts.
  • find_local - search for a symbol in the local (current) context: name is the symbol's name; symbol is the symbol. Returns the symbol if it exists; and nullptr if the symbol does not exist in the current context.
  • find - search for a symbol in the avaible contexts, starting with the first one and proceeding until reaching the outermost context. name is the symbol's name; from how many contexts up from the current one (zero). Returns nullptr if the symbol cannot be found in any of the contexts; or, the symbol and corresponding attributes.
File symbol_table.h (interface summary)
namespace cdk {

  template<typename Symbol>
  class symbol_table {
  public:
    void push();

    void pop();

    bool insert(const std::string &name, std::shared_ptr<Symbol> symbol);

    bool replace_local(const std::string &name, std::shared_ptr<Symbol> symbol);

    bool replace(const std::string &name, std::shared_ptr<Symbol> symbol);

    std::shared_ptr<Symbol> find_local(const std::string &name);

    std::shared_ptr<Symbol> find(const std::string &name, size_t from = 0) const;

};

Symbol representation

Symbols describe named program entities and store their properties. They provide support for the semantic processor: declarations create new symbols. Expressions and left-values refer to those symbols.

A simple representation in this case could be done in the following way. Note that this definition is just an example and contains only minimal information. It should be extended to account for the needs of the language being implemented.

File symbol.h (Tiny language)
#ifndef __TINY_TARGETS_SYMBOL_H__
#define __TINY_TARGETS_SYMBOL_H__

#include <string>
#include <memory>
#include <cdk/types/basic_type.h>

namespace tiny {

  class symbol {
    std::string _name; // identifier
    std::shared_ptr<cdk::basic_type> _type; // type (type id + type size)
  public:
    // constructors, destructor, getters, etc.

  public:
    // critical for type checking (interface similar to that of class cdk::typed_node)
    std::shared_ptr<cdk::basic_type> type() const { return _type; }
    void set_type(std::shared_ptr<cdk::basic_type> t) { _type = t; }
    bool is_typed(cdk::typename_type name) const { return _type->name() == name; }
  };

  // this function simplifies symbol creation in the type_checker visitor (see below)
  inline auto make_symbol(const std::string &name, std::shared_ptr<cdk::basic_type> type, /* rest of ctor args */) {
    return std::make_shared<symbol>(name, type, /* rest of ctor args */);
  }

} // tiny
#endif

Type Checking: Using Visitors

Type checking is the process of verifying whether the types used in the various language constructs are appropriate. It can be performed at compile time (static type checking) or at run time.

The type checking discussed here is the static approach, i.e., checking whether the types used for objects and the operations that manipulate them at compile time are consistent.

In the approach followed by CDK-based compilers, code generation is carried out by visitors that are responsible for traversing the abstract syntax tree and generate, evaluating each node. Node evaluation may depend on the specificities of the data types being manipulated, the simplest of which is the data type's size, important in all memory-related operations.

Vídeo Explicativo

O vídeo seguinte explica os tópicos apresentados nesta página (aplicação a várias linguagens).

Examples

Type checking example: the Tiny language

The following example considers a simple grammar and performs the whole of the semantic analysis process and, finally, generates the corresponding C code. The semantic analysis process must account for variables (they must be declared before they can be used) and for their types (all types must be used correctly).

Type checking example: the Simple language

The following example considers an evolution of Compact, called Simple. Where Compact forces some verification via syntactic analysis (thus, presenting low flexibility), Simple has a richer grammar and, consequently, admits constructions that may not be correct in what concerns types of operators, functions, and their arguments. Type checking in this case is built-in, since, without it, it would be impossible to guarantee the correctness of any expression.

Exercises