A programming language is a formal notation for precisely describing solutions to problems. Problem solving on a computer involves two steps: first, given a problem, one has to develop a solution for that problem. The solution may involve considerable thinking on part of the problem solver. At the end of this process, the problem solver will come up with a solution procedure (an algorithm) for solving the problem. Once a solution procedure has been thought of, it needs to be coded in a formal notation (a programming language) so that it can be executed on a computer.

The difficulty involved in designing the solution procedure is not in our control, it really depends on how hard/easy the problem is. The difficulty involved in the second step is determined by which programming language one uses for coding the solution procedure. Programming languages have to be carefully designed to make sure that this step is easy. The ease of programming the solution procedure in the programming language chosen depends on how narrow is the ``semantic gap" between this programming language and the level of abstraction at which the solution procedure has been conceived by the user. That is, how easy it is for the user to put down his/her thoughts using the constructs of this programming language. In general, the more lower level a language, the bigger the semantic gap. At one extreme, we may want our solution to be implemented in hardware (very big semantic gap), at the other we may design a domain specific language (e.g., Tcl/Tk for programming interfaces) in which a user can program at the level of abstraction at which they think.

Different Programming Languages have constructs to support facilities needed for solving problems in specific domains. For example, COBOL has support for solving business related problems, C for systems programming problems, and Lisp/Prolog for Artificial Intelligence applications.

The programming language chosen for expressing the solution procedure for a given problem has a great impact on the quality of the code written (its readability, writability, and maintainability) and productivity of the programmer.

Broadly speaking, computer programming languages have been divided into two categories---imperative languages and declarative languages.

Examples of imperative languages are Pascal, C, Java, etc. Examples of declarative languages are ML, pure Lisp and pure Prolog.

The programming model in imperative languages is based on a statement-at-a-time paradigm where each statement has some effect on a memory store. Imperative programming is centered around the assignment statement, which allows one to change the content of cells in the memory store. Programs written in imperative languages are generally harder to write, debug, and maintain compared to those written in declarative languages. Imperative programming lays more stress on "how" a solution procedure is specified. Programs written in imperative languages are generally larger in terms of code size and run faster compared to programs written in declarative languages. Imperative languages do not have a solid mathematical basis (although in the semantics part of the course, we will see how we can resolve this issue).

The programming model in declarative languages is based on stating the relationship between inputs and outputs. The actual computation procedure adopted is left to the runtime system. A declarative program can be viewed as a high level specification. Declarative programs are shorter, supposedly easier to write, debug, and maintain. Declarative programs are generally slower than imperative programs in execution speed.

There are two major programming paradigms that are declarative: functional programming (with its formal basis in mathematical functions and the lambda calculus) and logic programming (with its formal basis in first order logic).

The questions we want to ask are: what is "programming language semantics" and why is it important? Semantics refers to "formal meaning," thus programming language semantics refers to formal meaning of programming languages. The semantics of a program is a mathematical object that captures the meaning of that program. This mathematical object can be any mathematical entity with sound mathematical properties, such as a set, a function, or a relation.

For example, consider the piece of code (in C) for computing the factorial function:

int factorial(int n)

{int t=1;

while n>0

{t = t*n;

n--;

}

return t;

}

The semantics of this program can be captured by the set

{(0,1), (1,1), (2,2), (3,6), (4,24), ...}

U

{(-1,1), (-2,1), (-3,1),...}

This set consists of a series of pairs of the form (n, n!). Note that negative numbers are all mapped to 1.

In this course we will study how to obtain the mathematical object that captures the meaning of a given program.

The next question we wish to ask is: what is the use of semantics? There are two major uses: (i) in program verification, (ii) compilation.

Suppose we want to verify the following property for the factorial function: if the input number is larger than 2, then factorial(n) is greater than n. That is,

(n>2) ==> (factorial(n) > n)

One simple way to verify this property is to obtain the semantics of the factorial function, then use the set to check that the property holds for every element in that set. One problem, of course, is that the set is infinite.

The mathematical object that represent the meaning of a program can also be used to obtain more efficient versions of the program. This, in effect, is compilation. We will see in this course how compilers can be automatically generated from formal language descriptions (just like parsers can be generated from syntax descriptions using tools such as YACC).

In the semantics part of this course, we will study:

- How the mathematical objects that describe semantics are represented.
- Given a program, how the mathematical object that captures its semantics is obtained.
- The applications to which semantics can be put to.