Please consider a donation to the Higher Intellect project. See https://preterhuman.net/donate.php or the Donate to Higher Intellect page for more info.

Compilers and How They Work: An Overview

From Higher Intellect Vintage Wiki
Jump to navigation Jump to search
Compilers and How They Work: An Overview

                         Lou Morgan
                Madison IBM-PC User's Group

What  are compilers  and how  do  they work?  Many  computer
users ask themselves this  question  after  the  programming
bug  has  bitten  them.  To most people,  a  compiler  is  a
"black box program" which takes source code  written in some
high-level language,  such as FORTRAN, BASIC, Pascal,  or C,
and translates (compiles)  it into a  language  the computer
can understand and  execute.  Compilers take source code and
transform  it into virtual machine  language.  With the  IBM
PC, this virtual language is 8088 machine code.

Compilers vs. Interpreters

Computers  cannot  understand  English  words  and  grammar.
Even  the   highly   structured   words  and  sentences   of
programming languages  must be translated before  a computer
can understand them.  The compiler  or interpreter must look
up  each "word" of your  programming language  in a  kind of
dictionary  (or  lexicon)  and,  in   a   series  of  steps,
translate  it  into machine  code.  Each  word  initiates  a
separate logical task.

An interpreter translates one line of source code at  a time
into  machine  code, and then  executes  it.  Debugging  and
testing  is  relatively   fast  and  easy   in   interpreted
languages,  since the entire  program  doesn't  have  to  be
reprocessed each  time  a change is  made.  The BASIC(A).COM
program is  an  interpreter.  Interpreted programs run  much
slower  than   compiled  programs,   because  they  must  be
translated each time they  are run.  Programmers  often test
and  debug  their  programs  using an interpreter  and  then
compile them for production use.

How Compilers Work

Most compilers convert programs in three  steps.  Each  step
is  called a  pass.  A  particular  compiler  may  have  one
program  per  pass, or may  combine two or  three steps in a
single program.  For a very complex  language, a step may be
so  difficult to  perform that it  is  broken up  into  many
smaller  steps.  Regardless of how many passes  or  programs
are  required,   the  compiler  performs  only   three  main
functions:   first,   lexical   analysis;   second,   syntax
analysis; and third, code generation.  During  each  pass of
the  compiler,  the source code  moves  closer  to  becoming
virtual machine  language (or whatever language the compiler
is designed to generate).


Lexical Analysis

In the first  pass  of  the  compiler,  the  source  code is
passed  through  a  lexical  analyzer,  which  converts  the
source code  to a set  of  tokens.  A  token  is generally a
number  representing   some  keyword   in  the  language.  A
compiler  has  a  unique number for each keyword  (i.e.  IF,
WHILE, END), and  each  arithmetic or logical operator (i.e.
+,  -, *, AND, OR,  etc.).  Numbers  are  represented  by  a
token  which  indicates  that  what  follows  it  should  be
interpreted  as a number.  The  tokens  put  the programming
language  into  a  form  that  can  be  checked  for  proper
structure and order.

The  other  important  task of the  lexical  analyzer  is to
build  a  symbol  table.  This  is   a   table  of  all  the
identifiers  (variable  names,  procedures,  and  constants)
used   in   the   program.  When   an  identifier  is  first
recognized by the analyzer,  it is inserted into  the symbol
table, along  with information about  its type, where  it is
to be stored, and so  forth.  This information  is  used  in
subsequent passes of the compiler.

Syntax Analysis

After the lexical analyzer translates  a program into tokens
of  keywords,  variables,  constants,  symbols  and  logical
operators, the compiler  makes  its next  pass.  To describe
what happens during  this  function, I will briefly  explain
grammars.

Grammars.  Like  any  language, programming languages have a
set of  rules governing  the structure of the program.  Each
different computer language  has its own grammar which makes
it unique.  Some grammars are complex  (PL/I) and others are
relatively  easy (Pascal).  The  programmer must observe all
the structural rules of  a language to make logical sense to
the computer.  The  next  step  of  the  compiling  process,
parsing, checks to be sure all the rules were followed.

Parsing.  The  parsing routines of a compiler check  to  see
that  the  program  is  written  correctly (according to the
language  rules).  The  parser reads in the tokens generated
by  the  lexical  analyzer and  compares  them  to  the  set
grammar  of  the  programming   language.  If   the  program
follows the rules of  the language, then it is syntactically
correct.  When the parser encounters  a mistake, it issues a
warning  or  error  message  and  tries  to  continue.  Some

parsers  try  to correct a faulty program,  others  do  not.
When the parser  reaches  the  end of the  token  stream, it
will  tell   the   compiler  that  either   the  program  is
grammatically  correct  and  compiling can continue  or  the
program  contains too  many errors  and  compiling  must  be
aborted.  If  the  program  is  grammatically  correct,  the
parser will call for semantic routines.

Semantic Routines.  The  semantic  routines  of  a  compiler
perform two tasks:  checking to make  sure that  each series
of tokens  will  be understood  by the  computer when it  is
fully translated to machine code, and converting the  series
of tokens  one step closer to machine code.  The first  task
takes a  series of tokens,  called  a production, and checks
it to see if  it makes sense.  For example, a production may
be  correct  as  far  as the  parser  is  concerned, but the
semantic routines  check  whether  the variables  have  been
declared,   and   are  of  the   right  type,  etc.  If  the
production  makes sense, the semantic  routine  reduces  the
production  for  the  next   phase   of  compilation,   code
generation.  Most of the  code for the compiler lies here in
the semantic  routines and thus takes  up  a majority of the
compilation time.

Summary.  Two major routines comprise  syntax analysis:  the
parsing  routine  and  the  semantic   routine.  The  parser
checks for the correct  order  of the tokens and then  calls
the  semantic routines to check whether the series of tokens
(a  production)  will   make  sense  to  the  computer.  The
semantic routine  then reduces the production  another  step
toward complete translation to machine code.

Code Generation

The  code generation process determines  how fast  the  code
will run and  how large it will be.  The  first part of code
generation involves  optimization, and  the  second involves
actual machine code generation.

Optimization.  In this step, the compiler  tries to make the
intermediate code  generated by  the  semantic routines more
efficient.  This  process  can be  very  slow and may not be
able  to  improve  the code  much.  Because  of  this,  many
compilers don't include optimizers, and, if  they  do,  they
look only for areas that are easy to optimize.

Code Generation.  This process takes  the intermediate  code
produced  by  the  optimizer  (or semantic routines  if  the
compiler  has no optimizer) and  generates  virtual  machine
code, which in  our  case  is 8088 machine code.  It is this
part  of  the compilation phase  that is machine  dependent.
Each  type  of  computer  has  an   operating   system  that
processes virtual machine code  differently; therefore,  the
code  generator   must  be   different   for  each  type  of
computer.  The  choice  of  instructions  for   the  fastest
execution  and smallest  code  size  are made at this point,
according  to  the  machine's operating  system.  Each  code
generator  is  designed  specifically for  the  machine  and
operating system the final code will run on.

If  the  program  is  free  from  syntactical  errors,  code
generation should take place without  any problem.  When the
code generator  is finished, the  code produced will  be  in
8088 machine code, but the format of  the  code  is  not yet
executable.  It is in  a  format (an .OBJ file in our  case)
that is  ready  to go  to  a  linker,  which  will create an
executable  *.EXE or *.COM file  from the  machine  code the
compiler has generated.