CSC 530 Lecture Notes Week 5
More on Formal Semantics with Attribute Grammars
where expression is a standard ML expression, with the addition of attribute access terms, of the form$n.attr = expression
which may appear anywhere that an expression is syntactically valid.$n.attr
A : B {$1.x = $$.x $$.x = $1.x}
A : B {$1.x = $$.x $$.x' = $1.x'}
Figure 1: Attribute flow in languages like SIL.
/* * This is a Yacc-style attribute grammar for type checking and interpretation * of a simple imperative language (SIL). SIL has the same basic semantics as * Lisp with setq. The differences between SIL and imperative Lisp are: (1) * SIL has more Pascal-like syntax; (2) SIL has explicit type declarations for * variables and function parameters; (3) SIL distinguishes between statements * and expressions, where statements are executed solely for there effect on * the store, and do not return a value. * * The following semantic attributes are used in the SIL definition. The meta * notation for semantic data definitions and auxiliary functions is standard * ML, except for the "one of" notation, which is a simplified form of the ML * union-defining datatype construct. * * NAME DESCRIPTION * =================================================================== * * state A tuple of the form (env, store). * * env A list of the form [ env_binding ...]. Note that env is * actually two separate attributes, denoted env and env`. Env is * an inherited attribute representing the incoming environment; * env` is a synthesized attribute representing the outgoing * environment. * * store A list of the form [ act_rec ... ]. As with env, store is * actually two attributes: an inherited store and a synthesized * store`. Note the store is managed in a LIFO discipline, with * the bottomost (earliest added) act_rec representing the global * store, and the topmost (most recently added) act_rec * representing the current function activation record. * * env_binding * A tuple of the form (name, def). * * def One of var_def or fun_def. * * var_def * A type. * * fun_def * A triple form (type, formals, body). * * formals * A list of the form [ var_binding ... ] used in a function def. * * var_binding * A tuple of the form (name, var_def). * * type One of "integer", "real", "string", "boolean", or "OK". * * body A function (env*store)->store`. * * act_rec * A list of the form [ value_binding, ... ] * * value_binding * A tuple of the form (name, value). * * value One of integer or real or string or boolean, where these are * considered primitive value types of the meta-language. * * op_fun A function (value*value)->value representing the built-in * binary operators of the language. * * op_fun_1 A function value->value representing the built-in unary * operators of the language. * * name A string. * * nil_X, error_X * Attribute values built-in to the metalanguage for each * attribute type X. nil_X is the nil or empty value for * attributes of type X; error_X is the error value for attributes * of type X. * * The following auxiliary functions are used in the definition: * * fun assoc(name, alist) = * if null(alist) then nil_binding * else if name = #1(hd(alist)) then hd(alist) * else assoc(name, tl(alist)) * * fun last(l) = hd(nthtail(l, length(l)-1)) * * fun butlast(l) = * if (null(l) orelse null(tl(l))) then nil * else hd(l) :: butlast(tl(l)) * * fun reassign(name, value, alist) = * if name = #1(hd(alist)) then (name, value) :: tl(alist) * else hd(alist) :: reassign(name, value, tl(alist) * * fun assign(name, value, alist) = (name, value) :: alist * * fun chk_apply(fun_name, actual_types, env) * let * val fun_binding = assoc(fun_name, env) * val formals = make_type_list(#2(fun_binding)) * val fun-type = #1(fun_binding) * in * if chk_bindings(formals, actuals) then * if fun_type = nil_type then * "OK" * else * fun_type * else * error_type * end * * fun make_type_list(formals) = * if formals = nil then nil * else #2(hd(formals)) :: make_type_list(tl(formals)) * * fun chk_bindings(formals, actuals) = * if formals = nil then true * else (hd(formals) = hd(actuals)) and * chk_bindings(tl(formals), tl(actuals)) * * fun apply(fun_name, actuals, env, store) = * let * val fun_binding = assoc(fun_name, env) * val fun_body = #3(fun_binding) * val formals = make_name_list(#2(fun_binding)) * val bindings = bind(formals, actuals) * in * fun_body(env, bindings @ store) * end * * fun make_name_list(formals) = * if formals = nil then nil * else #1(hd(formals)) :: make_name_list(tl(formals)) * * fun bind(formals, actuals) = * if formals = nil then nil * else (hd(formals), hd(actuals)) :: bind(tl(formals), tl(actuals)) * * fun functionize(tree,ins,outs) = a meta-function that transforms an * attributed parse tree denoted by T into a function * fT(ia<1>*...*ia<m>)->(sa<1>*...*sa<n>) * where ia<i> are some or all of the inherited attributes of T and * sa<j> are some or all of the synthesized attributes of T, as * specified by ins and outs, resp. In practice within this * definition, functionize is used to transform the parse tree of a * function body into a function fn:(env*store)->store`. * * fun init_env() = [] */ %token PROGRAM %token END %token VAR %token INTEGER %token REAL %token CHAR %token BOOLEAN %token IDENTIFIER %token PROCEDURE %token BEGIN %token IF %token THEN %token ENDIF %token ELSE %token OR %token AND %token LEQ %token GEG %token NEW %token REALVAL %token INTEGERVAL %token CHARVAL %token BOOLVAL %left '=' '<' '>' LEQ GEQ NEQ %left '+' '-' OR %left '*' '/' AND %% program : PROGRAM decls stmts END {$2.env = init_env() $3.env = $2.env` $3.store = [] $$.state = if ($2.type != error_type) and ($3.type != error_type) then ($2.env`, $3.store`) else error_state} ; decls : /* empty */ {$$.env` = [] $$.type = "OK"} | decl ';' decls {$1.env = $$.env $3.env = $1.env` $$.env` = $1.env` @ $3.env` $$.type = if ($1.type != error_type) and ($3.type != error_type) then "OK" else error_type} ; decl : vardecl {$$.env` = $1.env` $$.type = $1.type} | procdecl {$1.env = $$.env $$.env` = $1.env` $$.type = $1.type} ; vardecl : VAR vars ':' type {$2.type = $4.type $$.env` = $2.env` $$.type = $4.type} ; type : INTEGER {$$.type = "integer"} | REAL {$$.type = "real"} | CHAR {$$.type = "char"} | BOOLEAN {$$.type = "boolean"} ; vars : var {$$.env` = [($1.name, $$.type)]} | var ',' vars {$3.type = $$.type $$.env` = [($1.name, $$.type)] @ $3.env`} ; var : IDENTIFIER {$$.name = $1.name} /* NOTE: The lexer provides ident string names. */ ; procdecl : PROCEDURE prochdr procbody {$3.env = $2.formals @ $$.env $$.env` = [($2.name, nil_type, $2.formals, $3.fun_body)] $$.type = $3.type} | PROCEDURE prochdr ':' type procbody {$5.env = $2.formals @ [($2.name, $4.type)] @ $$.env $$.env` = [($2.name, $4.type, $2.formals @ [($2.name, $4.type)], /* ^^^^^^^^^^^^^^^^^^ return val */ $5.fun_body)] $$.type = $5.type} ; prochdr : IDENTIFIER '(' formals ')' {$$.name = $1.name $$.formals = $3.formals} ; formals : /* empty */ {$$.formals = []} | formal {$$.formals = [$1.env_binding]} | formal ',' formals {$$.formals = $1.env_binding @ $3.formals} ; formal : var ':' type {$$.env_binding = ($1.name, $3.type)} ; procbody : BEGIN stmts END {$$.type = $2.type $$.fun_body = functionize($2,(env*store),store`)} ; stmts : stmt ';' {$1.env = $$.env $1.store = $$.store $$.type = $1.type $$.store` = $1.store`} | stmt ';' stmts {$1.env = $3.env = $$.env $$.type = if $1.(type = "OK") and ($3.type = "OK") then "OK" else error_type $1.store = $$.store $3.store = $1.store` $$.store` = $3.store`} ; stmt : /* empty */ | var ':=' expr {$3.env = $$.env $$.type = if #2(assoc($1.name, $$.env)) = $3.type then "OK" else error_type $3.store = $$.store $$.store` = if (length($3.store`) > 1) andalso assoc($1.name, hd($3.store`)) then reassign($1,name, $3.value, hd($3.store`)) @ tl($3.store`) else if assoc($1.name, last($3.store`)) then butlast($3.store`) @ reassign($1.name, $3.value, last($3.store`)) else butlast($3.store`) @ assign($1.name, $3.value, last($3.store`))} | IDENTIFIER '(' actuals ')' {$$.type = if chk_apply($1,name, $3.types, $$.env) $$.store` = tl(apply( $1.name, $3.values, $$.env, $$.store))} | IF expr THEN stmts ENDIF {$2.env = $4.env = $$.env $$.type = if $2.type = "boolean" then $4.type else error_type (* NOTE WEAKNESS HERE *) $4.store = $2.store` $$.store` = if $2.value then $4.store` else $2.store`} | IF expr THEN stmts ELSE stmts ENDIF {$2.env = $4.env = $6.env = $$.env $$.type = if $2.type = "boolean" then if $4.type = "OK" and $6.type = "OK" then "OK" else error_type (* NOTE WEAKNESS HERE *) $4.store = $6.store = $2.store` $$.store` = if $2.value then $4.store` else $6.store`} ; expr : number {$$.type = $1.type $$.store` = $$.store $$.value = $1.value} | char {$$.type = $1.type $$.store` = $$.store $$.value = $1.value} | bool {$$.type = $1.type $$.store` = $$.store $$.value = $1.value} | var {$$.type = if assoc($1.name, $$.env) then #2(assoc($1.name, $$.env)) else error_type $$.store` = $$.store $$.value = if (length($$.store) > 1) and also assoc($1.name, hd($$.store)) then #2(assoc($1.name, hd($$.store))) else if assoc($1.name, last($$.store)) then #2(assoc($1.name, last($$.store))) else error_value} | IDENTIFIER '(' actuals ')' {$3.env = $$.env $3.store = $$.store $$.type = chk_apply($1,name, $3.types, $$.env) $$.store` = tl(apply( $1.name, $3.values, $$.env, $$.store)) $$.value = last(hd(apply( $1.name, $3.values, $$.env, $$.store)))} | expr rel_op expr %prec '<' {$1.env = $3.env = $$.env $$.type = if ($1.type = $2.type) then $1.type else error_type $1.store = $$.store $3.store = $1.store` (* NOTE *) $$.store` = $3.store ` $$.value = $2.op_fun($1.value, $3.value)} | expr add_op expr %prec '+' {$1.env = $3.env = $$.env $$.type = if ($1.type = $2.type) and (($1.type = "real") or ($1.type = "integer")) then $1.type else error_type $1.store = $$.store $3.store = $1.store` $$.store` = $3.store ` $$.value = $2.op_fun($1.value, $3.value)} | expr mult_op expr %prec '*' {$1.env = $3.env = $$.env $$.type = if ($1.type = $2.type) and (($1.type = "real") or ($1.type = "integer")) then $1.type else error_type $1.store = $$.store $3.store = $1.store` $$.store` = $3.store ` $$.value = $2.op_fun($1.value, $3.value)} | '(' expr ')' {$2.env = $$.env $$.type = $2.type $2.store = $$.store $$.store` = $2.store` $$.value = $2.value} ; add_op : '+' {$$.op_fun = $1.op_fun} | '-' {$$.op_fun = $1.op_fun} | OR {$$.op_fun = $1.op_fun} /* NOTE: The lexer provides function literals. */ ; mult_op : '*' {$$.op_fun = $1.op_fun} | '/' {$$.op_fun = $1.op_fun} | AND {$$.op_fun = $1.op_fun} ; rel_op : '<' {$$.op_fun = $1.op_fun} | '>' {$$.op_fun = $1.op_fun} | '=' {$$.op_fun = $1.op_fun} | LEQ {$$.op_fun = $1.op_fun} | GEQ {$$.op_fun = $1.op_fun} | NEQ {$$.op_fun = $1.op_fun} ; actuals : /* empty */ {$$.types = [] $$.store` = $$.store $$.values = []} | actual {$1.env = $$.env $1.store = $$.store $$.types = [$1.type] $$.store` = $1.store` $$.values = [$1.value]} | actual ',' actuals {$1.env = $3.env = $$.env $1.store = $$.store $3.store = $1.store` /* NOTE sequential eval */ $$.store` = $3.store` $$.values = $1.value @ $3.values} ; actual : expr {$$.type = $1.type $$.store` = $1.store` $$.value = $1.value} ; number : real {$$.type = $1.type $$.value = $1.value} | integer {$$.type = $1.type $$.value = $1.value} ; real : REALVAL {$$.type = "real" $$.value = $1.value} /* The lexer provides real literals. */ ; integer : INTEGERVAL {$$.type = "integer" $$.value = $1.value} /* The lexer provides integer literals. */ ; char : CHARVAL {$$.type = "char" $$.value = $1.value} /* The lexer provides char literals. */ ; bool : BOOLVAL {$$.type = "boolean" $$.value = $1.value} /* The lexer provides boolean literals. */ ;