mewmew / uc

A compiler for the µC language.
58 stars 5 forks source link

parser: Postpone type checking to the semantic analysis phase #33

Closed mewmew closed 8 years ago

mewmew commented 8 years ago

To simplify the design of the parser and the parser generator, I suggest we postpone the type checking to the semantic analysis phase. This design decision would be supported by the KISS mentality, let each component focus on one thing, and do it well.

Alex, what are your thoughts? We could go either direction, and for uC it would be trivial to enforce type checking already in the parser. My main concern is that such an approach could make the parser sufficiently more complex in the future, once we start supporting a larger subset of C.

Below is an example of how array type declarations may be simplified by this design decision (extracts taken from the ast/astx package).

// Code before:

// NewArrayType returns a new array type based on the given element type and
// length.
func NewArrayType(elem interface{}, length interface{}) (*types.Array, error) {
    // Parse array length.
    s, err := tokenString(length)
    if err != nil {
        return nil, errutil.Newf("invalid array length; %v", err)
    }
    n, err := strconv.Atoi(s)
    if err != nil {
        return nil, errutil.Newf("invalid array length; %v", err)
    }

    // Validate element type.
    switch elem := elem.(type) {
    case *types.Basic:
        switch elem.Kind {
        case types.Char, types.Int:
            // Valid element type.
        default:
            return nil, errutil.Newf("invalid array element type; %v", elem.Kind)
        }
        return &types.Array{Elem: elem, Len: n}, nil
    default:
        return nil, errutil.Newf("invalid array element type; %v", elem)
    }
}
// Code after:

// NewArrayType returns a new array type based on the given element type and
// length.
func NewArrayType(elem interface{}, length interface{}) (*types.Array, error) {
    rawlen, err := tokenString(length)
    if err != nil {
        return nil, errutil.Newf("invalid array length; %v", err)
    }
    typ := new(types.Array)
    typ.Len, err = strconv.Atoi(rawlen)
    if err != nil {
        return nil, errutil.Newf("invalid array length; %v", err)
    }
    typ.Elem, err = NewType(elem)
    if err != nil {
        return nil, errutil.Newf("invalid array element type; %v", err)
    }
    return typ, nil
}

// NewType returns a new type of µC.
func NewType(typ interface{}) (types.Type, error) {
    if typ, ok := typ.(types.Type); ok {
        return typ, nil
    }
    return nil, errutil.Newf("invalid type; expected types.Type, got %T", typ)
}

Note that several production rules would make use of the NewType function, which is also more generic and allows more types than those specifically valid for array types, thus mandating a subsequent type check in the semantic analysis phase.

mewmew commented 8 years ago

If we were to postpone the type checking to the semantic analysis phase, it should be possible to merge VarDecl and ParamDecl production rules, and let the type checker validate their uses within function signatures and bodies.

mewmew commented 8 years ago

If we were to postpone the type checking to the semantic analysis phase, it should be possible to merge VarDecl and ParamDecl production rules, and let the type checker validate their uses within function signatures and bodies.

I experimented with this and an initial implementation is available in commit ee9cebb. Lets see where we end up and which approach feels like the right one.

mewmew commented 8 years ago

We ended up taking this route, so the syntactic analysis phase is more permissive with types, which will be validated during the type checker of the semantic analysis phase.

mewmew commented 8 years ago

The type-checker is now implemented.