SEN2001 · Week 1 · Introduction

Implementation methods and the evolution of languages

Compilation, pure interpretation and hybrid implementation; compiler phases, linker and preprocessor, JIT; programming environments and the milestones in the evolution of languages from Fortran to Java that are asked on exams.

compilercompilationlexical analyzersyntax analyzersemantic analyzerintermediate codecode generatorsymbol tablelinkerload modulepure interpretationhybrid implementationbytecodevirtual machinejust-in-time compilerpreprocessormacroprogramming environmentFortranLISPALGOL 60COBOLSIMULA 67SmalltalkPrologAdaC++Java

Writing a language is one thing; running it on a computer is another. This page covers two topics: how languages are implemented (compilation, interpretation, hybrid) and the milestones from the evolution of languages that are asked on exams. On the midterm it appears as short-answer and matching questions; points won with little effort.

Golden rules:

  1. Compilation: slow translation, fast execution. Interpretation: no translation, slow execution.
  2. Hybrid = compile to an intermediate language + interpret. JIT speeds this up.
  3. The preprocessor does textual replacement; it does not compute.
  4. Know the “firsts” in history: Fortran the first compiled language, ALGOL 60 the first BNF, SIMULA 67 the first class, LISP the first functional.

Implementation methods

A computer’s hardware runs only machine language. A high-level language can be run in three ways.

Compilation

The source program is translated to machine language once, then runs directly. Translation is slow, execution is fast. C, C++, COBOL, Ada.

source program
   │  lexical analyzer      characters → lexical units (lexemes)
   ▼
lexical units
   │  syntax analyzer       → parse trees
   ▼
parse trees
   │  intermediate code generator and semantic analyzer
   ▼
intermediate code   ──(optimization, optional)
   │  code generator
   ▼
machine language ── linker (+ library and system code) ──► load module ──► runs

symbol table: provides all phases with the types and attributes of user-defined names
  • Lexical analyzer: gathers characters into units such as identifiers, special words, operators and punctuation; skips comments.
  • Syntax analyzer: builds a parse tree from these units.
  • Semantic analyzer: checks errors that syntax cannot catch, such as type errors; produces intermediate code.
  • Optimization: makes the program smaller or faster (usually on the intermediate code).
  • Code generator: produces the machine code.
  • Linker: connects the user program with system and library programs; the output is a load module (executable image).

While a program runs, instructions move from memory to the processor; this connection is usually slower than the processor and limits the speed (the von Neumann bottleneck).

Pure interpretation

No translation. An interpreter program reads and runs the source program statement by statement; like a virtual machine simulated in software.

  • Pro: an error message points directly to the source line and variable; easy debugging.
  • Con: 10 to 100 times slower. The bottleneck is that every statement is decoded again each time it runs. Also, the source program and the symbol table stay in memory while running.
  • Examples: early JavaScript and PHP, early versions of LISP, APL. It came back with web scripting languages.

Hybrid implementation

The source program is compiled to an intermediate language that is easy to interpret; the intermediate language is interpreted. Faster than pure interpretation, because source statements are decoded only once.

  • Perl: first partially compiled to catch errors.
  • Java: source → bytecode → interpreted by the JVM (Java Virtual Machine). Bytecode is portable: it runs on any machine that has a JVM.

Just-in-time

A JIT compiler translates a subprogram’s intermediate code to machine code when it is called for the first time and saves it; later calls use the machine code. Java and .NET languages (C#) work this way. The result: portable intermediate code + the speed of compiled code.

MethodTranslationExecution speedExample
CompilationOnce, slowFastestC, C++, COBOL
Pure interpretationNone10-100x slowerEarly JavaScript, PHP
HybridTo an intermediate languageIn betweenPerl, early Java
JITIntermediate language + to machine code at run timeClose to compiledJava, C#

Preprocessor

A program that processes the program just before compilation. It includes other files (#include) and expands macros (#define). Replacement is textual; the expression is not evaluated, and precedence rules apply after the expansion.

#define SQ(x) x*x
#define SQP(x) ((x)*(x))
#define DOUBLE(x) x+x

SQ(2+3)        /* 2+3*2+3       */
SQP(2+3)       /* ((2+3)*(2+3)) */
10 * DOUBLE(3) /* 10 * 3+3      */
11
25
33

PITFALL · a macro is not a function. SQ(2+3) gives 11, not 25, because the argument is pasted as text without being evaluated. Put the parameters and the whole body in parentheses.

Programming environments

The collection of tools used in software development: file system, editor, linker, compiler, debugger. UNIX is an old environment; Eclipse, NetBeans, Microsoft Visual Studio .NET are integrated (IDE) examples.

Evolution of languages: what is asked on exams

YearLanguageWhy it matters
1945Plankalkül (Zuse)First high-level language design; not implemented
1957Fortran I (Backus, IBM)First widely used compiled high-level language; showed compiled code could be efficient
1958LISP (McCarthy)First functional language; list processing, garbage collection; artificial intelligence
1958-60ALGOL 58 / ALGOL 60Machine independent; the first language described with BNF; block structure, recursion, pass-by-value and pass-by-name
1960COBOLBusiness applications; records, decimal arithmetic, readable English-like syntax
1964BASICTime-shared, easy to learn
1964PL/IScientific + business; first attempts at exception handling and concurrency; very complex
1967SIMULA 67Classes and objects; the basis of data abstraction
1971Pascal (Wirth)For teaching; structured programming
1972CSystems programming (UNIX); powerful operators, weak type checking
1972PrologLogic programming
1980Smalltalk-80First fully OOP: everything is an object, message passing; graphical interface
1983AdaU.S. Department of Defense; packages, exception handling, generics, tasks
1985C++C + classes, multiple inheritance, operator overloading, templates
1995JavaBased on C++, simpler and safer: no pointers, garbage collection, applets, bytecode
2000C#.NET; ideas from Java and C++, delegates, properties

Scripting languages: Perl (1987, text processing), JavaScript (1995, browser), PHP (1995, server), Python (1991), Ruby (1996, everything is an object). Functional: Scheme (1975, a small dialect of LISP), ML (static typing, type inference), Haskell (pure and lazy).

TIP · the “first” questions. First compiled: Fortran. First BNF: ALGOL 60. First class: SIMULA 67. First fully OOP: Smalltalk. First functional and garbage collection: LISP. Logic: Prolog. Business: COBOL. These seven pairings bring most of the points in this section.

Common mistakes

WrongCorrect
Interpretation is slow because machine instructions are slowEvery statement is decoded each time
Java is purely interpretedBytecode + JVM (hybrid), today with JIT
JIT compiles the whole program at the startIt compiles a subprogram on its first call and saves it
Optimization is a mandatory phaseIt is optional
The linker compiles source codeIt connects compiled pieces with libraries
SQ(2+3) = 25With the x*x macro it is 11
The first BNF was in FortranALGOL 60 (BNF was used for ALGOL 58)
The first OOP language is SmalltalkClasses are in SIMULA 67; the first fully OOP is Smalltalk

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

Put the phases of a compiler in order: code generation, syntax analysis, lexical analysis, semantic analysis / intermediate code generation, optimization (optional).

Show answer
lexical analysis, syntax analysis, semantic analysis / intermediate code, optimization, code generation

The lexer gathers characters into lexemes. The parser builds a parse tree from them. The semantic analyzer checks type errors and produces intermediate code. The optional optimization makes the program smaller or faster. The code generator produces the machine code. The symbol table serves all the phases.

Question 2easy

Which compiler phase turns the characters of the source program into lexical units, and which builds parse trees?

Show answer
lexical analyzer; syntax analyzer

Lexical units: identifiers, special words, operators, punctuation. The lexer skips comments and whitespace. The syntax analyzer (parser) builds, from the lexical units, parse trees that show the syntactic structure of the program.

Question 3medium

Why is pure interpretation typically 10 to 100 times slower than running compiled code?

Show answer
every statement must be decoded each time it is executed

The interpreter has to re-decode a source statement every time it runs it; in a loop the same line is decoded thousands of times. The bottleneck is decoding the statement, not the machine instructions themselves. Also, the source program and the symbol table stay in memory while running. The upside: error messages point to the source line, and debugging is easy.

Question 4easy

Classify the implementation method: (a) C and COBOL; (b) early JavaScript and PHP; (c) Perl and early Java.

Show answer
(a) compilation (b) pure interpretation (c) hybrid

Compilation: the source is translated to machine language once, and execution is fast. Pure interpretation: no translation, the interpreter runs the source directly. Hybrid: the source is translated to an intermediate language that is easy to interpret (Java bytecode), and then this intermediate language is interpreted (the JVM).

Question 5medium

What does a just-in-time (JIT) compiler do?

Show answer
it translates intermediate code (e.g. bytecode) into machine code the first time a method is called, and keeps it for later calls

JIT removes the slowness of the hybrid system: the program is compiled to the intermediate language; when a subprogram is called for the first time, its intermediate code is translated to machine code and saved, and later calls run the machine code directly. Java and .NET languages work this way. So JIT is really a delayed compiler.

Question 6medium

With the C macro #define SQ(x) x*x, what is the value of SQ(2+3)? How should the macro be written?

Show answer
11; #define SQ(x) ((x)*(x)) gives 25

The preprocessor does textual replacement before compilation; it does not evaluate the expression. SQ(2+3) → 2+3*2+3; multiplication first: 2 + 6 + 3 = 11. With the parenthesized definition it becomes ((2+3)*(2+3)) = 25.

Question 7hard

With #define DOUBLE(x) x+x, what is the value of 10 * DOUBLE(3)?

Show answer
33

Expansion: 10 * 3+3. Multiplication first: 30 + 3 = 33 (not the expected 60). The whole body of the macro must also be in parentheses: #define DOUBLE(x) ((x)+(x)).

Question 8easy

What does a linker do?

Show answer
it combines the user's compiled program with system and library programs to produce a load module (executable image)

A user program usually needs other programs (operating system routines, library functions). The linker connects them and produces a single executable file (load module). That is why the binding of a library call to the function's code happens at link time.

Question 9easy

Name the language: (a) the first widely used compiled high-level language (1957); (b) the first language described with BNF; (c) the language that introduced classes (1967); (d) designed for business data processing.

Show answer
(a) Fortran I (b) ALGOL 60 (c) SIMULA 67 (d) COBOL

Fortran (John Backus, IBM 704) proved that compiled code could be fast. ALGOL 60 was described with BNF and brought block structure, recursion, pass-by-value/name. SIMULA 67 brought classes and objects for simulation. COBOL (1960) brought records and decimal numbers for business applications.

Question 10easy

Which language introduced list processing and garbage collection, and which was the first purely object-oriented language?

Show answer
LISP; Smalltalk

LISP (1958, John McCarthy) was designed for artificial intelligence: the only data structure is the list, computation is function application, and unused memory is collected automatically. Smalltalk (Xerox PARC, Smalltalk-80) was the first fully OOP language, where everything is an object and computation is done by sending messages; it also introduced the graphical windowing interface.

Question 11medium

Order these chronologically: Java, Prolog, Fortran I, SIMULA 67, Smalltalk-80.

Show answer
Fortran I, SIMULA 67, Prolog, Smalltalk-80, Java

Fortran I 1957, SIMULA 67 1967, Prolog 1972, Smalltalk-80 1980, Java 1995. The exam asks for the order and "which idea first appeared where?", not memorized years.

Question 12easy

Which language was designed for the U.S. Department of Defense and is known for packages, exception handling, generic units and tasks (concurrency)?

Show answer
Ada

Ada (1983) is the product of a very large design process: data abstraction with packages, detailed exception handling, generic program units, concurrency with tasks. Ada 95 added OOP support. Its complexity and compiler cost limited its spread.

Verification: The preprocessor macro questions were verified by expanding the macro body textually and evaluating the resulting expression with node (verify/SEN2001/01.mjs); the real cpp was not run because no C compiler is installed. The phase orders and years are conceptual/historical knowledge; they were compared with the textbook's standard account.

Review card

compiler phases: lexical → syntax → semantic/intermediate code → (optimization) → code generation the symbol table serves all of them. linker: with libraries → load module compilation: slow translation, FAST execution (C, C++, COBOL) pure interpretation: no translation, 10-100x slower (decoding), easy debugging (early JS, PHP) hybrid: intermediate language + interpreter (Perl, Java bytecode + JVM) JIT: translate intermediate code to machine code on first call, keep it (Java, .NET) preprocessor: textual replacement BEFORE compilation. #define SQ(x) x*x → SQ(2+3) = 11 correct macro: ((x)*(x)) and outer parentheses Fortran I 1957 first compiled high-level language LISP 1958 functional, lists, garbage collection ALGOL 60: first language described with BNF, block structure, recursion COBOL 1960 business; SIMULA 67 classes; Prolog 1972 logic Smalltalk-80 first fully OOP; C 1972 systems; C++ 1985; Ada 1983; Java 1995